diff options
| author | gabrhr <[email protected]> | 2022-04-20 10:19:29 -0500 |
|---|---|---|
| committer | gabrhr <[email protected]> | 2022-04-20 10:19:29 -0500 |
| commit | e13e630cd6e4fc0b1ff92098a28a770794c7bb9a (patch) | |
| tree | e68ad2f947d1b3ec454529b35f37ca2f223e5431 /front/odiparpack/app | |
| parent | 457816ac1129fcc6019d2fc795b6693ee6776d59 (diff) | |
| download | DP1_project-e13e630cd6e4fc0b1ff92098a28a770794c7bb9a.tar.gz DP1_project-e13e630cd6e4fc0b1ff92098a28a770794c7bb9a.tar.bz2 DP1_project-e13e630cd6e4fc0b1ff92098a28a770794c7bb9a.zip | |
Añadir plantilla
Base para front
Diffstat (limited to 'front/odiparpack/app')
550 files changed, 54280 insertions, 0 deletions
diff --git a/front/odiparpack/app/.DS_Store b/front/odiparpack/app/.DS_Store Binary files differnew file mode 100644 index 0000000..b0366eb --- /dev/null +++ b/front/odiparpack/app/.DS_Store diff --git a/front/odiparpack/app/.htaccess b/front/odiparpack/app/.htaccess new file mode 100644 index 0000000..40252ae --- /dev/null +++ b/front/odiparpack/app/.htaccess @@ -0,0 +1,52 @@ +<ifModule mod_rewrite.c> + + + ####################################################################### + # GENERAL # + ####################################################################### + + # Make apache follow sym links to files + Options +FollowSymLinks + # If somebody opens a folder, hide all files from the resulting folder list + IndexIgnore */* + + + ####################################################################### + # REWRITING # + ####################################################################### + + # Enable rewriting + RewriteEngine On + + # If its not HTTPS + RewriteCond %{HTTPS} off + + # Comment out the RewriteCond above, and uncomment the RewriteCond below if you're using a load balancer (e.g. CloudFlare) for SSL + # RewriteCond %{HTTP:X-Forwarded-Proto} !https + + # Redirect to the same URL with https://, ignoring all further rules if this one is in effect + RewriteRule ^(.*) https://%{HTTP_HOST}/$1 [R,L] + + # If we get to here, it means we are on https:// + + # If the file with the specified name in the browser doesn't exist + RewriteCond %{REQUEST_FILENAME} !-f + + # and the directory with the specified name in the browser doesn't exist + RewriteCond %{REQUEST_FILENAME} !-d + + # and we are not opening the root already (otherwise we get a redirect loop) + RewriteCond %{REQUEST_FILENAME} !\/$ + + # Rewrite all requests to the root + RewriteRule ^(.*) / + +</ifModule> + +<IfModule mod_headers.c> + # Do not cache sw.js, required for offline-first updates. + <FilesMatch "sw\.js$"> + Header set Cache-Control "private, no-cache, no-store, proxy-revalidate, no-transform" + Header set Pragma "no-cache" + </FilesMatch> +</IfModule> diff --git a/front/odiparpack/app/.nginx.conf b/front/odiparpack/app/.nginx.conf new file mode 100644 index 0000000..6a71831 --- /dev/null +++ b/front/odiparpack/app/.nginx.conf @@ -0,0 +1,112 @@ +## +# Put this file in /etc/nginx/conf.d folder and make sure +# you have a line 'include /etc/nginx/conf.d/*.conf;' +# in your main nginx configuration file +## + +## +# Redirect to the same URL with https:// +## + +server { + + listen 80; + +# Type your domain name below + server_name example.com; + + return 301 https://$server_name$request_uri; + +} + +## +# HTTPS configurations +## + +server { + + listen 443 ssl; + +# Type your domain name below + server_name example.com; + +# Configure the Certificate and Key you got from your CA (e.g. Lets Encrypt) + ssl_certificate /path/to/certificate.crt; + ssl_certificate_key /path/to/server.key; + + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:50m; + ssl_session_tickets off; + +# Only use TLS v1.2 as Transport Security Protocol + ssl_protocols TLSv1.2; + +# Only use ciphersuites that are considered modern and secure by Mozilla + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; + +# Do not let attackers downgrade the ciphersuites in Client Hello +# Always use server-side offered ciphersuites + ssl_prefer_server_ciphers on; + +# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months) + add_header Strict-Transport-Security max-age=15768000; + +# Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits +# Uncomment if you want to use your own Diffie-Hellman parameter, which can be generated with: openssl ecparam -genkey -out dhparam.pem -name prime256v1 +# See https://wiki.mozilla.org/Security/Server_Side_TLS#DHE_handshake_and_dhparam +# ssl_dhparam /path/to/dhparam.pem; + + +## OCSP Configuration START +# If you want to provide OCSP Stapling, you can uncomment the following lines +# See https://www.digitalocean.com/community/tutorials/how-to-configure-ocsp-stapling-on-apache-and-nginx for more infos about OCSP and its use case +# fetch OCSP records from URL in ssl_certificate and cache them + +#ssl_stapling on; +#ssl_stapling_verify on; + +# verify chain of trust of OCSP response using Root CA and Intermediate certs (you will get this file from your CA) +#ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates; + +## OCSP Configuration END + +# To let nginx use its own DNS Resolver +# resolver <IP DNS resolver>; + + +# Always serve index.html for any request + location / { + # Set path + root /var/www/; + try_files $uri /index.html; + } + +# Do not cache sw.js, required for offline-first updates. + location /sw.js { + add_header Cache-Control "no-cache"; + proxy_cache_bypass $http_pragma; + proxy_cache_revalidate on; + expires off; + access_log off; + } + +## +# If you want to use Node/Rails/etc. API server +# on the same port (443) config Nginx as a reverse proxy. +# For security reasons use a firewall like ufw in Ubuntu +# and deny port 3000/tcp. +## + +# location /api/ { +# +# proxy_pass http://localhost:3000; +# proxy_http_version 1.1; +# proxy_set_header X-Forwarded-Proto https; +# proxy_set_header Upgrade $http_upgrade; +# proxy_set_header Connection 'upgrade'; +# proxy_set_header Host $host; +# proxy_cache_bypass $http_upgrade; +# +# } + +} diff --git a/front/odiparpack/app/a b/front/odiparpack/app/a new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/front/odiparpack/app/a diff --git a/front/odiparpack/app/actions/.DS_Store b/front/odiparpack/app/actions/.DS_Store Binary files differnew file mode 100644 index 0000000..5008ddf --- /dev/null +++ b/front/odiparpack/app/actions/.DS_Store diff --git a/front/odiparpack/app/actions/CalendarEventActions.js b/front/odiparpack/app/actions/CalendarEventActions.js new file mode 100644 index 0000000..ff4fa43 --- /dev/null +++ b/front/odiparpack/app/actions/CalendarEventActions.js @@ -0,0 +1,28 @@ +import * as types from './actionTypes'; + +export const fetchAction = items => ({ + type: types.FETCH_CALENDAR_DATA, + items, +}); + +export const addAction = { + type: types.ADD_EVENT +}; + +export const discardAction = { + type: types.DISCARD_EVENT +}; + +export const submitAction = newEvent => ({ + type: types.SUBMIT_EVENT, + newEvent, +}); + +export const deleteAction = event => ({ + type: types.DELETE_EVENT, + event, +}); + +export const closeNotifAction = { + type: types.CLOSE_NOTIF +}; diff --git a/front/odiparpack/app/actions/ChatActions.js b/front/odiparpack/app/actions/ChatActions.js new file mode 100644 index 0000000..57c7c9a --- /dev/null +++ b/front/odiparpack/app/actions/ChatActions.js @@ -0,0 +1,24 @@ +import * as types from './actionTypes'; + +export const fetchChatAction = items => ({ + type: types.FETCH_CHAT_DATA, + items, +}); + +export const showChatAction = person => ({ + type: types.SHOW_CHAT, + person, +}); + +export const hideDetailAction = { + type: types.HIDE_CHAT, +}; + +export const sendAction = message => ({ + type: types.SEND_CHAT, + message, +}); + +export const deleteAction = { + type: types.DELETE_CONVERSATION, +}; diff --git a/front/odiparpack/app/actions/ContactActions.js b/front/odiparpack/app/actions/ContactActions.js new file mode 100644 index 0000000..5c57209 --- /dev/null +++ b/front/odiparpack/app/actions/ContactActions.js @@ -0,0 +1,53 @@ +import * as types from './actionTypes'; + +export const fetchAction = items => ({ + type: types.FETCH_CONTACT_DATA, + items, +}); + +export const showDetailAction = item => ({ + type: types.SHOW_DETAIL_CONTACT, + item, +}); + +export const hideDetailAction = { + type: types.HIDE_DETAIL, +}; + +export const submitAction = (newData, avatar) => ({ + type: types.SUBMIT_CONTACT, + newData, + avatar +}); + +export const addAction = { + type: types.ADD_CONTACT, +}; + +export const editAction = item => ({ + type: types.EDIT_CONTACT, + item, +}); + +export const searchAction = keyword => ({ + type: types.SEARCH_CONTACT, + keyword, +}); + +export const removeAction = item => ({ + type: types.DELETE_CONTACT, + item, +}); + +export const closeAction = { + type: types.CLOSE_CONTACT_FORM, +}; + +export const addToFavoriteAction = item => ({ + type: types.TOGGLE_FAVORITE, + item, +}); + +export const closeNotifAction = { + type: types.CLOSE_NOTIF +}; diff --git a/front/odiparpack/app/actions/CrudTbActions.js b/front/odiparpack/app/actions/CrudTbActions.js new file mode 100644 index 0000000..969eff4 --- /dev/null +++ b/front/odiparpack/app/actions/CrudTbActions.js @@ -0,0 +1,37 @@ +import * as types from './actionTypes'; + +export const fetchAction = (items, branch) => ({ + branch, + type: `${branch}/${types.FETCH_DATA}`, + items +}); +export const addAction = (anchor, branch) => ({ + branch, + type: `${branch}/${types.ADD_EMPTY_ROW}`, + anchor +}); +export const removeAction = (item, branch) => ({ + branch, + type: `${branch}/${types.REMOVE_ROW}`, + item +}); +export const updateAction = (event, item, branch) => ({ + branch, + type: `${branch}/${types.UPDATE_ROW}`, + event, + item +}); +export const editAction = (item, branch) => ({ + branch, + type: `${branch}/${types.EDIT_ROW}`, + item +}); +export const saveAction = (item, branch) => ({ + branch, + type: `${branch}/${types.SAVE_ROW}`, + item +}); +export const closeNotifAction = branch => ({ + branch, + type: `${branch}/${types.CLOSE_NOTIF}`, +}); diff --git a/front/odiparpack/app/actions/CrudTbFrmActions.js b/front/odiparpack/app/actions/CrudTbFrmActions.js new file mode 100644 index 0000000..ec03bf7 --- /dev/null +++ b/front/odiparpack/app/actions/CrudTbFrmActions.js @@ -0,0 +1,35 @@ +import * as types from './actionTypes'; + +export const fetchAction = (items, branch) => ({ + branch, + type: `${branch}/${types.FETCH_DATA_FORM}`, + items +}); +export const addAction = (anchor, branch) => ({ + branch, + type: `${branch}/${types.ADD_NEW}`, + anchor +}); +export const closeAction = branch => ({ + branch, + type: `${branch}/${types.CLOSE_FORM}` +}); +export const submitAction = (newData, branch) => ({ + branch, + type: `${branch}/${types.SUBMIT_DATA}`, + newData +}); +export const removeAction = (item, branch) => ({ + branch, + type: `${branch}/${types.REMOVE_ROW_FORM}`, + item +}); +export const editAction = (item, branch) => ({ + branch, + type: `${branch}/${types.EDIT_ROW_FORM}`, + item +}); +export const closeNotifAction = branch => ({ + branch, + type: `${branch}/${types.CLOSE_NOTIF}`, +}); diff --git a/front/odiparpack/app/actions/EcommerceActions.js b/front/odiparpack/app/actions/EcommerceActions.js new file mode 100644 index 0000000..481a031 --- /dev/null +++ b/front/odiparpack/app/actions/EcommerceActions.js @@ -0,0 +1,34 @@ +import * as types from './actionTypes'; + +export const fetchAction = items => ({ + type: types.FETCH_PRODUCT_DATA, + items, +}); + +export const searchAction = keyword => ({ + type: types.SEARCH_PRODUCT, + keyword, +}); + +export const addAction = item => ({ + type: types.ADD_TO_CART, + item, +}); + +export const removeAction = item => ({ + type: types.DELETE_CART_ITEM, + item, +}); + +export const checkoutAction = ({ + type: types.CHECKOUT, +}); + +export const detailAction = item => ({ + type: types.SHOW_DETAIL_PRODUCT, + item +}); + +export const closeNotifAction = { + type: types.CLOSE_NOTIF +}; diff --git a/front/odiparpack/app/actions/EmailActions.js b/front/odiparpack/app/actions/EmailActions.js new file mode 100644 index 0000000..7bf50b9 --- /dev/null +++ b/front/odiparpack/app/actions/EmailActions.js @@ -0,0 +1,57 @@ +import * as types from './actionTypes'; + +export const fetchMailAction = items => ({ + type: types.FETCH_EMAIL_DATA, + items, +}); + +export const openMailAction = mail => ({ + type: types.OPEN_MAIL, + mail, +}); + +export const filterAction = filter => ({ + type: types.FILTER_MAIL, + filter, +}); + +export const composeAction = { + type: types.COMPOSE_MAIL, +}; + +export const sendAction = (to, subject, content, attachment) => ({ + type: types.SEND_MAIL, + to, + subject, + content, + attachment, +}); + +export const discardAction = { + type: types.DISCARD_MESSAGE, +}; + +export const searchAction = keyword => ({ + type: types.SEARCH_MAIL, + keyword, +}); + +export const deleteAction = mail => ({ + type: types.DELETE_MAIL, + mail, +}); + +export const toggleStaredAction = mail => ({ + type: types.TOGGLE_STARED, + mail, +}); + +export const moveAction = (mail, category) => ({ + type: types.MOVE_TO, + mail, + category +}); + +export const closeNotifAction = { + type: types.CLOSE_NOTIF +}; diff --git a/front/odiparpack/app/actions/ReduxFormActions.js b/front/odiparpack/app/actions/ReduxFormActions.js new file mode 100644 index 0000000..9d3c035 --- /dev/null +++ b/front/odiparpack/app/actions/ReduxFormActions.js @@ -0,0 +1,4 @@ +import * as types from './actionTypes'; + +export const initAction = (data) => ({ type: types.INIT, data }); +export const clearAction = { type: types.CLEAR }; diff --git a/front/odiparpack/app/actions/SocmedActions.js b/front/odiparpack/app/actions/SocmedActions.js new file mode 100644 index 0000000..b4d89d4 --- /dev/null +++ b/front/odiparpack/app/actions/SocmedActions.js @@ -0,0 +1,32 @@ +import * as types from './actionTypes'; + +export const fetchAction = items => ({ + type: types.FETCH_TIMELINE_DATA, + items, +}); + +export const postAction = (text, media, privacy) => ({ + type: types.POST, + text, + media, + privacy +}); + +export const toggleLikeAction = item => ({ + type: types.TOGGLE_LIKE, + item, +}); + +export const fetchCommentAction = item => ({ + type: types.FETCH_COMMENT_DATA, + item, +}); + +export const postCommentAction = (comment) => ({ + type: types.POST_COMMENT, + comment, +}); + +export const closeNotifAction = { + type: types.CLOSE_NOTIF +}; diff --git a/front/odiparpack/app/actions/TreeTableActions.js b/front/odiparpack/app/actions/TreeTableActions.js new file mode 100644 index 0000000..3aa0447 --- /dev/null +++ b/front/odiparpack/app/actions/TreeTableActions.js @@ -0,0 +1,10 @@ +import * as types from './actionTypes'; + +const openAction = (keyID, child, branch) => ({ + branch, + type: `${branch}/${types.TOGGLE_TREE}`, + keyID, + child +}); + +export default openAction; diff --git a/front/odiparpack/app/actions/UiActions.js b/front/odiparpack/app/actions/UiActions.js new file mode 100644 index 0000000..f7d62a2 --- /dev/null +++ b/front/odiparpack/app/actions/UiActions.js @@ -0,0 +1,15 @@ +import * as types from './actionTypes'; + +export const toggleAction = { type: types.TOGGLE_SIDEBAR }; +export const openAction = initialLocation => ({ + type: types.OPEN_SUBMENU, + initialLocation +}); +export const changeThemeAction = theme => ({ + type: types.CHANGE_THEME, + theme +}); +export const playTransitionAction = isLoaded => ({ + type: types.LOAD_PAGE, + isLoaded +}); diff --git a/front/odiparpack/app/actions/actionTypes.js b/front/odiparpack/app/actions/actionTypes.js new file mode 100644 index 0000000..08986f2 --- /dev/null +++ b/front/odiparpack/app/actions/actionTypes.js @@ -0,0 +1,84 @@ +// Global UI Action +export const TOGGLE_SIDEBAR = 'TOGGLE_SIDEBAR'; +export const OPEN_SUBMENU = 'OPEN_SUBMENU'; +export const CHANGE_THEME = 'CHANGE_THEME'; +export const LOAD_PAGE = 'LOAD_PAGE'; + +// Tree Table +export const TOGGLE_TREE = 'TOGGLE_TREE'; + +// Redux Form Sample +export const INIT = 'INIT'; +export const CLEAR = 'CLEAR'; + +// Crud Table +export const FETCH_DATA = 'FETCH_DATA'; +export const ADD_EMPTY_ROW = 'ADD_EMPTY_ROW'; +export const UPDATE_ROW = 'UPDATE_ROW'; +export const REMOVE_ROW = 'REMOVE_ROW'; +export const EDIT_ROW = 'EDIT_ROW'; +export const SAVE_ROW = 'SAVE_ROW'; + +// Crud Form Table +export const FETCH_DATA_FORM = 'FETCH_DATA_FORM'; +export const ADD_NEW = 'ADD_NEW'; +export const CLOSE_FORM = 'CLOSE_FORM'; +export const SUBMIT_DATA = 'SUBMIT_DATA'; +export const REMOVE_ROW_FORM = 'REMOVE_ROW_FORM'; +export const EDIT_ROW_FORM = 'EDIT_ROW_FORM'; + +// Social Media +export const FETCH_TIMELINE_DATA = 'FETCH_TIMELINE_DATA'; +export const FETCH_COMMENT_DATA = 'FETCH_COMMENT_DATA'; +export const POST = 'POST'; +export const POST_COMMENT = 'POST_COMMENT'; +export const TOGGLE_LIKE = 'TOGGLE_LIKE'; + +// Ecommerce +export const FETCH_PRODUCT_DATA = 'FETCH_PRODUCT_DATA'; +export const SEARCH_PRODUCT = 'SEARCH_PRODUCT'; +export const SHOW_DETAIL_PRODUCT = 'SHOW_DETAIL_PRODUCT'; +export const ADD_TO_CART = 'ADD_TO_CART'; +export const DELETE_CART_ITEM = 'DELETE_CART_ITEM'; +export const CHECKOUT = 'CHECKOUT'; + +// Contact +export const FETCH_CONTACT_DATA = 'FETCH_CONTACT_DATA'; +export const SHOW_DETAIL_CONTACT = 'SHOW_DETAIL_CONTACT'; +export const HIDE_DETAIL = 'HIDE_DETAIL'; +export const ADD_CONTACT = 'ADD_CONTACT'; +export const EDIT_CONTACT = 'EDIT_CONTACT'; +export const SUBMIT_CONTACT = 'SUBMIT_CONTACT'; +export const CLOSE_CONTACT_FORM = 'CLOSE_CONTACT_FORM'; +export const DELETE_CONTACT = 'DELETE_CONTACT'; +export const TOGGLE_FAVORITE = 'TOGGLE_FAVORITE'; +export const SEARCH_CONTACT = 'SEARCH_CONTACT'; + +// Chat +export const FETCH_CHAT_DATA = 'FETCH_CHAT_DATA'; +export const SHOW_CHAT = 'SHOW_CHAT'; +export const HIDE_CHAT = 'HIDE_CHAT'; +export const SEND_CHAT = 'SEND_CHAT'; +export const DELETE_CONVERSATION = 'DELETE_CONVERSATION'; + +// Email +export const FETCH_EMAIL_DATA = 'FETCH_EMAIL_DATA'; +export const OPEN_MAIL = 'OPEN_MAIL'; +export const FILTER_MAIL = 'FILTER_MAIL'; +export const COMPOSE_MAIL = 'COMPOSE_MAIL'; +export const DISCARD_MESSAGE = 'DISCARD_MESSAGE'; +export const SEARCH_MAIL = 'SEARCH_MAIL'; +export const SEND_MAIL = 'SEND_MAIL'; +export const MOVE_TO = 'MOVE_TO'; +export const DELETE_MAIL = 'DELETE_MAIL'; +export const TOGGLE_STARED = 'TOGGLE_STARED'; + +// Calendar +export const FETCH_CALENDAR_DATA = 'FETCH_CALENDAR_DATA'; +export const ADD_EVENT = 'ADD_EVENT'; +export const DISCARD_EVENT = 'DISCARD_EVENT'; +export const SUBMIT_EVENT = 'SUBMIT_EVENT'; +export const DELETE_EVENT = 'DELETE_EVENT'; + +// Notification Form +export const CLOSE_NOTIF = 'CLOSE_NOTIF'; diff --git a/front/odiparpack/app/api/.DS_Store b/front/odiparpack/app/api/.DS_Store Binary files differnew file mode 100644 index 0000000..5008ddf --- /dev/null +++ b/front/odiparpack/app/api/.DS_Store diff --git a/front/odiparpack/app/api/avatars.js b/front/odiparpack/app/api/avatars.js new file mode 100644 index 0000000..746bf45 --- /dev/null +++ b/front/odiparpack/app/api/avatars.js @@ -0,0 +1,15 @@ +const avatars = [ + '/images/avatars/pp_girl.svg', + '/images/avatars/pp_girl.svg', + '/images/avatars/pp_girl2.svg', + '/images/avatars/pp_girl3.svg', + '/images/avatars/pp_girl4.svg', + '/images/avatars/pp_girl5.svg', + '/images/avatars/pp_boy.svg', + '/images/avatars/pp_boy2.svg', + '/images/avatars/pp_boy3.svg', + '/images/avatars/pp_boy4.svg', + '/images/avatars/pp_boy5.svg', +]; + +export default avatars; diff --git a/front/odiparpack/app/api/brand.js b/front/odiparpack/app/api/brand.js new file mode 100644 index 0000000..883eed5 --- /dev/null +++ b/front/odiparpack/app/api/brand.js @@ -0,0 +1,8 @@ +module.exports = { + name: 'Boss Ultimate', + desc: 'Boss Ultimate - Material Admin Dashboard', + prefix: 'boss', + footerText: 'Boss Ultimate All Rights Reserved 2018', + logoText: 'Boss Ultimate', + needLogin: false +}; diff --git a/front/odiparpack/app/api/carouselData.js b/front/odiparpack/app/api/carouselData.js new file mode 100644 index 0000000..d21dede --- /dev/null +++ b/front/odiparpack/app/api/carouselData.js @@ -0,0 +1,58 @@ +const carouselData = [ + { + background: '#E91E63', + title: 'Feature', + desc: 'Vestibulum tempor, sem et molestie egestas, dui tortor laoreet tellus.', + icon: 'flag', + }, + { + background: '#0091EA', + title: 'Unlimited', + desc: 'Aliquam nec ex aliquet, aliquam neque non, gravida est.', + icon: 'all_inclusive', + }, + { + background: '#00BFA5', + title: 'Complete', + desc: 'Vestibulum bibendum nisi eget magna malesuada', + icon: 'done', + }, + { + background: '#F57F17', + title: 'Easy', + desc: 'Vestibulum tempor, sem et molestie egestas, dui tortor laoreet tellus.', + icon: 'extension', + }, + { + background: '#7CB342', + title: 'Satisfy', + desc: 'Aliquam nec ex aliquet, aliquam neque non, gravida est.', + icon: 'mood', + }, + { + background: '#546E7A', + title: 'Public', + desc: 'Quisque a consequat ante, at volutpat enim. Aenean sit amet magna vel magna', + icon: 'public', + }, + { + background: '#00ACC1', + title: 'Awesome', + desc: 'Vestibulum tempor, sem et molestie egestas, dui tortor laoreet tellus.', + icon: 'thumb_up', + }, + { + background: '#C51162', + title: 'Safeguard', + desc: 'Quisque a consequat ante, at volutpat enim. Aenean sit amet magna vel magna', + icon: 'lock', + }, + { + background: '#7C4DFF', + title: 'Favorite', + desc: ' Aenean facilisis vitae purus facilisis semper.', + icon: 'favorite', + }, +]; + +export default carouselData; diff --git a/front/odiparpack/app/api/chartData.js b/front/odiparpack/app/api/chartData.js new file mode 100644 index 0000000..87c5979 --- /dev/null +++ b/front/odiparpack/app/api/chartData.js @@ -0,0 +1,262 @@ +export const data1 = [ + { + name: 'Page A', + uv: 4000, + pv: 2400, + amt: 2400 + }, + { + name: 'Page B', + uv: 3000, + pv: 1398, + amt: 2210 + }, + { + name: 'Page C', + uv: 2000, + pv: 5800, + 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 + }, +]; + +export const data2 = [ + { + name: 'Page A', + uv: 4000, + female: 2400, + male: 2400 + }, { + name: 'Page B', + uv: 3000, + female: 1398, + male: 2210 + }, { + name: 'Page C', + uv: 2000, + female: 9800, + male: 2290 + }, { + name: 'Page D', + uv: 2780, + female: 3908, + male: 2000 + }, { + name: 'Page E', + uv: 1890, + female: 4800, + male: 2181 + }, { + name: 'Page F', + uv: 2390, + female: 3800, + male: 2500 + }, { + name: 'Page G', + uv: 3490, + female: 4300, + male: 2100 + }, +]; + +export const data3 = [ + { + 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 + }, +]; + +export const data4 = [ + { + name: 'Group A', + value: 400 + }, { + name: 'Group B', + value: 300 + }, + { + name: 'Group C', + value: 300 + }, { + name: 'Group D', + value: 200 + }, + { + name: 'Group E', + value: 278 + }, { + name: 'Group F', + value: 189 + } +]; + +export const data5 = [ + { + name: 'Group A', + value: 2400 + }, { + name: 'Group B', + value: 4567 + }, + { + name: 'Group C', + value: 1398 + }, { + name: 'Group D', + value: 9800 + }, + { + name: 'Group E', + value: 3908 + }, { + name: 'Group F', + value: 4800 + } +]; + +export const data6 = [ + { + name: 'Group A', + value: 400 + }, { + name: 'Group B', + value: 300 + }, + { + name: 'Group C', + value: 300 + }, { + name: 'Group D', + value: 200 + } +]; + +export const data7 = [ + { + subject: 'Math', + A: 120, + B: 110, + fullMark: 150 + }, { + subject: 'Chinese', + A: 98, + B: 130, + fullMark: 150 + }, { + subject: 'English', + A: 86, + B: 130, + fullMark: 150 + }, { + subject: 'Geography', + A: 99, + B: 100, + fullMark: 150 + }, { + subject: 'Physics', + A: 85, + B: 90, + fullMark: 150 + }, { + subject: 'History', + A: 65, + B: 85, + fullMark: 150 + }, +]; + +export const data8 = [ + { + x: 10, + y: 30 + }, { + x: 30, + y: 200 + }, { + x: 45, + y: 100 + }, { + x: 50, + y: 400 + }, { + x: 70, + y: 150 + }, { + x: 100, + y: 250 + } +]; + +export const data9 = [ + { + x: 30, + y: 20 + }, { + x: 50, + y: 180 + }, { + x: 75, + y: 240 + }, { + x: 100, + y: 100 + }, { + x: 120, + y: 190 + } +]; diff --git a/front/odiparpack/app/api/chartMiniData.js b/front/odiparpack/app/api/chartMiniData.js new file mode 100644 index 0000000..1ab5092 --- /dev/null +++ b/front/odiparpack/app/api/chartMiniData.js @@ -0,0 +1,49 @@ +export const data1 = [ + { + 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 + }, +]; + +export const data2 = [ + { + name: 'Group A', + value: 400 + }, { + name: 'Group B', + value: 300 + }, + { + name: 'Group C', + value: 300 + }, { + name: 'Group D', + value: 200 + } +]; diff --git a/front/odiparpack/app/api/chatData.js b/front/odiparpack/app/api/chatData.js new file mode 100644 index 0000000..72d1f9b --- /dev/null +++ b/front/odiparpack/app/api/chatData.js @@ -0,0 +1,137 @@ +const chatData = [ + { + with: '1', + chat: [ + { + id: '1_1', + from: 'contact', + date: 'May, 29 2018', + time: '22:45', + message: 'Lorem ipsum dolor sit amet' + }, + { + id: '1_2', + from: 'me', + date: 'May, 29 2018', + time: '22:45', + message: 'Pellentesque ac bibendum tortor' + }, + { + id: '1_3', + from: 'contact', + date: 'May, 30 2018', + time: '09:20', + message: 'Sed imperdiet enim ligula, vitae viverra justo porta vel. Duis eget felis bibendum, pretium mi sed, placerat ante. ' + }, + { + id: '1_4', + from: 'me', + date: 'May, 30 2018', + time: '09:55', + message: 'Curabitur egestas consequat lorem, vel fermentum augue porta id. Aliquam lobortis magna neque' + }, + { + id: '1_5', + from: 'me', + date: 'May, 30 2018', + time: '09:58', + message: 'Integer orci justo' + }, + { + id: '1_6', + from: 'contact', + date: 'May, 30 2018', + time: '09:58', + message: 'Nam posuere accumsan porta. Integer id orci sed ante tincidunt tincidunt sit amet sed libero. Quisque ut metus sit amet augue rutrum feugiat. Vestibulum bibendum nisi eget magna malesuada' + }, + ] + }, + { + with: '2', + chat: [ + { + id: '2_1', + from: 'contact', + date: 'May, 29 2018', + time: '22:45', + message: 'Lorem ipsum dolor sit amet' + }, + { + id: '2_2', + from: 'me', + date: 'May, 29 2018', + time: '22:45', + message: 'Pellentesque ac bibendum tortor' + }, + { + id: '2_3', + from: 'contact', + date: 'May, 30 2018', + time: '09:20', + message: 'Sed imperdiet enim ligula, vitae viverra justo porta vel. Duis eget felis bibendum, pretium mi sed, placerat ante. ' + }, + ] + }, + { + with: '3', + chat: [ + { + id: '3_1', + from: 'contact', + date: 'May, 29 2018', + time: '22:45', + message: 'Lorem ipsum dolor sit amet' + }, + { + id: '3_2', + from: 'me', + date: 'May, 29 2018', + time: '22:45', + message: 'Pellentesque ac bibendum tortor' + }, + ] + }, + { + with: '4', + chat: [ + { + id: '4_1', + from: 'contact', + date: 'May, 30 2018', + time: '09:20', + message: 'Sed imperdiet enim ligula, vitae viverra justo porta vel. Duis eget felis bibendum, pretium mi sed, placerat ante. ' + }, + { + id: '4_2', + from: 'me', + date: 'May, 30 2018', + time: '09:55', + message: 'Curabitur egestas consequat lorem, vel fermentum augue porta id. Aliquam lobortis magna neque' + }, + { + id: '4_3', + from: 'me', + date: 'May, 30 2018', + time: '09:58', + message: 'Integer orci justo' + }, + { + id: '4_4', + from: 'contact', + date: 'May, 30 2018', + time: '09:58', + message: 'Nam posuere accumsan porta. Integer id orci sed ante tincidunt tincidunt sit amet sed libero. Quisque ut metus sit amet augue rutrum feugiat. Vestibulum bibendum nisi eget magna malesuada' + }, + ] + }, + { + with: '5', + chat: [], + }, + { + with: '6', + chat: [], + }, +]; + +export default chatData; diff --git a/front/odiparpack/app/api/colorfull.js b/front/odiparpack/app/api/colorfull.js new file mode 100644 index 0000000..731961f --- /dev/null +++ b/front/odiparpack/app/api/colorfull.js @@ -0,0 +1,10 @@ +import { pink, purple, orange, teal, lightGreen, lightBlue } from '@material-ui/core/colors'; +const colorfull = [ + pink[400], + purple[500], + lightBlue[500], + teal[500], + orange[500], + lightGreen[500], +]; +export default colorfull; diff --git a/front/odiparpack/app/api/connectionData.js b/front/odiparpack/app/api/connectionData.js new file mode 100644 index 0000000..f79b718 --- /dev/null +++ b/front/odiparpack/app/api/connectionData.js @@ -0,0 +1,78 @@ +import imgApi from 'ba-api/images'; +import avatarApi from 'ba-api/avatars'; +const connectionData = [ + { + cover: imgApi[41], + avatar: avatarApi[6], + name: 'John Doe', + title: 'UX Designer', + connection: 203, + verified: false + }, + { + cover: imgApi[4], + avatar: avatarApi[2], + name: 'Jane Doe', + title: 'Administrator', + connection: 10, + verified: true + }, + { + cover: imgApi[42], + avatar: avatarApi[7], + name: 'James Doe', + title: 'Marketing', + connection: 18, + verified: false + }, + { + cover: imgApi[8], + avatar: avatarApi[10], + name: 'Mickey Joe', + title: 'Teacher Lecture', + connection: 6, + verified: true + }, + { + cover: imgApi[39], + avatar: avatarApi[5], + name: 'Janet Doe', + title: 'UI Designer', + connection: 18, + verified: false + }, + { + cover: imgApi[49], + avatar: avatarApi[1], + name: 'Michele Joe', + title: 'Designer', + connection: 100, + verified: true + }, + { + cover: imgApi[50], + avatar: avatarApi[9], + name: 'James Doe', + title: 'Programmer', + connection: 20, + verified: true + }, + { + cover: imgApi[45], + avatar: avatarApi[8], + name: 'Jimmy Doe', + title: 'Interior Designer', + connection: 1, + verified: false + }, + { + cover: imgApi[43], + avatar: avatarApi[3], + name: 'Maya Joe', + title: 'Contributor', + connection: 100, + verified: false + }, +]; + +export default connectionData; diff --git a/front/odiparpack/app/api/contactData.js b/front/odiparpack/app/api/contactData.js new file mode 100644 index 0000000..6baf807 --- /dev/null +++ b/front/odiparpack/app/api/contactData.js @@ -0,0 +1,83 @@ +import avatarApi from './avatars'; +const contactData = [ + { + id: '1', + avatar: avatarApi[6], + name: 'John Doe', + title: 'Administrator', + phone: '+6281234567890', + secondaryPhone: '+6280987654321', + personalEmail: '[email protected]', + companyEmail: '[email protected]', + address: 'Ipsum Street no.77 Block A/5A, New York', + website: 'http://doeclans.net', + favorited: false + }, + { + id: '2', + avatar: avatarApi[8], + name: 'Jim Doe', + title: 'System Engineer', + phone: '+657890321145', + secondaryPhone: '', + personalEmail: '[email protected]', + companyEmail: '[email protected]', + address: 'Lorem Street no.76 Block B/8B, Brooklyn', + website: 'http://doejim.com', + favorited: true + }, + { + id: '3', + avatar: avatarApi[2], + name: 'Jane Doe', + title: 'Executive', + phone: '+45353695', + secondaryPhone: '+678910111213', + personalEmail: '[email protected]', + companyEmail: '[email protected]', + address: 'Dolor Street no.76 Block B/8B, Tokyo', + website: 'http://janedoe.com', + favorited: false + }, + { + id: '4', + avatar: avatarApi[10], + name: 'Jinx Doe', + title: 'Security', + phone: '+678543210012', + secondaryPhone: '', + personalEmail: '[email protected]', + companyEmail: '', + address: 'Paskal Street no.101 Block B/10B, Samarinda', + website: '', + favorited: 'false' + }, + { + id: '5', + avatar: avatarApi[4], + name: 'Jihan Doe', + title: 'Marketing', + phone: '+45353695', + secondaryPhone: '+56743210468', + personalEmail: '[email protected]', + companyEmail: '[email protected]', + address: 'Sit amet Street no.76 Block B/8B, New York', + website: '', + favorited: true + }, + { + id: '6', + avatar: avatarApi[7], + name: 'Johny Doe', + title: 'Actor', + phone: '+2234561234', + secondaryPhone: '+6742234235666', + personalEmail: '[email protected]', + companyEmail: '', + address: 'Vivacus Street no.2 Block C/10A, Paris', + website: '', + favorited: true + }, +]; + +export default contactData; diff --git a/front/odiparpack/app/api/dummyContents.js b/front/odiparpack/app/api/dummyContents.js new file mode 100644 index 0000000..104cdfd --- /dev/null +++ b/front/odiparpack/app/api/dummyContents.js @@ -0,0 +1,17 @@ +import avatarApi from './avatars'; +const dummyContent = { + user: { + name: 'John Doe', + title: 'Administrator', + avatar: avatarApi[6] + }, + text: { + title: 'Lorem ipsum', + subtitle: 'Ut a lorem eu odio cursus laoreet.', + sentences: 'Donec lacus sem, scelerisque sed ligula nec, iaculis porttitor mauris.', + paragraph: 'Sed rutrum augue libero, id faucibus quam aliquet sed. Phasellus interdum orci quam, volutpat ornare eros rhoncus sed. Donec vestibulum leo a auctor convallis. In dignissim consectetur molestie. Vivamus interdum tempor dui, nec posuere augue consequat sit amet. Suspendisse quis semper quam. Nullam nec neque sem.', + date: 'Jan 9, 2018' + } +}; + +export default dummyContent; diff --git a/front/odiparpack/app/api/emailData.js b/front/odiparpack/app/api/emailData.js new file mode 100644 index 0000000..d921eed --- /dev/null +++ b/front/odiparpack/app/api/emailData.js @@ -0,0 +1,126 @@ +import avatarApi from './avatars'; +const emailData = [ + { + id: '1', + avatar: avatarApi[6], + name: 'John Doe', + date: 'May, 11 2018', + subject: 'Lorem ipsum dolor sit amet', + category: '', + content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum. Fusce placerat enim et odio molestie sagittis. Vestibulum dignissim orci vitae eros rutrum euismod.', + attachment: [], + stared: false, + }, + { + id: '2', + avatar: avatarApi[8], + name: 'Jim Doe', + date: 'May, 12 2018', + subject: 'Nunc quis sem quis velit tincidunt', + category: 'social', + content: 'Maecenas nisl libero, tincidunt id odio id, feugiat vulputate quam. Vestibulum feugiat rhoncus metus. In non erat et ipsum molestie porta sit amet ut felis. Vestibulum a massa vestibulum, gravida odio id, fringilla ipsum. Ut sed eros finibus, placerat orci id, dapibus mauris.', + attachment: [], + stared: true, + }, + { + id: '3', + avatar: avatarApi[2], + name: 'Jane Doe', + date: 'May, 13 2018', + subject: 'Vivamus sit', + category: 'updates', + content: 'Pellentesque ullamcorper aliquet ultrices.', + attachment: [], + stared: true, + }, + { + id: '4', + avatar: avatarApi[9], + name: 'Jinx Doe', + date: 'May, 13 2018', + subject: 'Vestibulum faucibus eget erat eget pretium', + category: 'social', + content: 'Nulla vehicula leo ut augue tincidunt, placerat tempus nulla rutrum. Integer orci justo, fringilla at faucibus vel, pulvinar in eros. Suspendisse eleifend nunc non varius rhoncus. Nam posuere accumsan porta. Integer id orci sed ante tincidunt tincidunt sit amet sed libero.', + attachment: [], + stared: false, + }, + { + id: '5', + avatar: avatarApi[4], + name: 'Jihan Doe', + date: 'May, 15 2018', + subject: 'Aliquam venenatis magna et odio lobortis maximus', + category: 'promos', + content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum. Fusce placerat enim et odio molestie sagittis. Vestibulum dignissim orci vitae eros rutrum euismod.', + attachment: [], + stared: false, + }, + { + id: '6', + avatar: avatarApi[7], + name: 'Johny Doe', + date: 'May, 15 2018', + subject: 'Vivamus sit amet interdum elit', + category: 'forums', + content: 'Integer orci justo, fringilla at faucibus vel, pulvinar in eros.', + attachment: [], + stared: false, + }, + { + id: '7', + avatar: avatarApi[5], + name: 'Jane Doe', + date: 'May, 16 2018', + subject: 'Lorem ipsum dolor sit amet', + category: 'spam', + content: 'Nam posuere accumsan porta.', + attachment: [], + stared: true, + }, + { + id: '8', + avatar: avatarApi[6], + name: 'Me', + date: 'May, 16 2018', + subject: 'Lorem ipsum dolor sit amet', + category: 'sent', + content: 'Nam posuere accumsan porta.', + attachment: [], + stared: false, + }, + { + id: '9', + avatar: avatarApi[7], + name: 'James Doe', + date: 'May, 17 2018', + subject: 'Pellentesque ullamcorper aliquet ultrices', + category: 'sent', + content: 'Cras convallis lacus orci, tristique tincidunt magna consequat in. In vel pulvinar est, at euismod libero. Quisque ut metus sit amet augue rutrum feugiat. Vestibulum bibendum nisi eget magna malesuada, at mattis eros efficitur. Vivamus facilisis quam ullamcorper iaculis gravida.', + attachment: [], + stared: true, + }, + { + id: '10', + avatar: avatarApi[3], + name: 'Jihan Doe', + date: 'May, 17 2018', + subject: 'Dolor', + category: 'sent', + content: 'Nulla vehicula leo ut augue tincidunt, placerat tempus nulla rutrum. Integer orci justo, fringilla at faucibus vel, pulvinar in eros.', + attachment: [], + stared: false, + }, + { + id: '11', + avatar: avatarApi[10], + name: 'Johny Doe', + date: 'May, 20 2018', + subject: 'Vivamus sit amet interdum elit', + category: 'spam', + content: 'Integer orci justo, fringilla at faucibus vel, pulvinar in eros.', + attachment: [], + stared: false, + }, +]; + +export default emailData; diff --git a/front/odiparpack/app/api/eventData.js b/front/odiparpack/app/api/eventData.js new file mode 100644 index 0000000..f6e5f1d --- /dev/null +++ b/front/odiparpack/app/api/eventData.js @@ -0,0 +1,117 @@ +const eventData = [ + { + id: 0, + title: 'All Day Event very long title', + allDay: true, + start: new Date(2015, 3, 0), + end: new Date(2015, 3, 1), + hexColor: 'FFCDD2' + }, + { + id: 1, + title: 'Long Event', + start: new Date(2015, 3, 7, 0, 0, 0), + end: new Date(2015, 3, 10, 0, 1, 0), + hexColor: 'FFCDD2' + }, + + { + id: 2, + title: 'DTS STARTS', + start: new Date(2016, 2, 13, 0, 0, 0), + end: new Date(2016, 2, 20, 0, 0, 0), + hexColor: 'FFCDD2' + }, + + { + id: 3, + title: 'DTS ENDS', + start: new Date(2016, 10, 6, 0, 0, 0), + end: new Date(2016, 10, 13, 0, 0, 0), + hexColor: 'E1BEE7' + }, + + { + id: 4, + title: 'Some Event', + start: new Date(2015, 3, 9, 0, 0, 0), + end: new Date(2015, 3, 9, 0, 0, 0), + hexColor: 'E1BEE7' + }, + { + id: 5, + title: 'Conference', + start: new Date(2015, 3, 11), + end: new Date(2015, 3, 13), + desc: 'Big conference for important people', + hexColor: 'BBDEFB' + }, + { + id: 6, + title: 'Meeting', + start: new Date(2015, 3, 12, 10, 30, 0, 0), + end: new Date(2015, 3, 12, 12, 30, 0, 0), + desc: 'Pre-meeting meeting, to prepare for the meeting', + hexColor: 'BBDEFB' + }, + { + id: 7, + title: 'Lunch', + start: new Date(2015, 3, 12, 12, 0, 0, 0), + end: new Date(2015, 3, 12, 13, 0, 0, 0), + desc: 'Power lunch', + hexColor: 'BBDEFB' + }, + { + id: 8, + title: 'Meeting', + start: new Date(2015, 3, 12, 14, 0, 0, 0), + end: new Date(2015, 3, 12, 15, 0, 0, 0), + hexColor: 'B2EBF2' + }, + { + id: 9, + title: 'Happy Hour', + start: new Date(2015, 3, 12, 17, 0, 0, 0), + end: new Date(2015, 3, 12, 17, 30, 0, 0), + desc: 'Most important meal of the day', + hexColor: 'B2EBF2' + }, + { + id: 10, + title: 'Dinner', + start: new Date(2015, 3, 12, 20, 0, 0, 0), + end: new Date(2015, 3, 12, 21, 0, 0, 0), + hexColor: 'B2EBF2' + }, + { + id: 11, + title: 'Birthday Party', + start: new Date(2015, 3, 13, 7, 0, 0), + end: new Date(2015, 3, 13, 10, 30, 0), + hexColor: 'C8E6C9' + }, + { + id: 12, + title: 'Late Night Event', + start: new Date(2015, 3, 17, 19, 30, 0), + end: new Date(2015, 3, 18, 2, 0, 0), + hexColor: 'C8E6C9' + }, + { + id: 13, + title: 'Multi-day Event', + start: new Date(2015, 3, 20, 19, 30, 0), + end: new Date(2015, 3, 22, 2, 0, 0), + hexColor: 'FFECB3' + }, + { + id: 14, + title: 'Today', + start: new Date(new Date().setHours(new Date().getHours() - 3)), + end: new Date(new Date().setHours(new Date().getHours() + 3)), + hexColor: 'FFECB3' + }, +]; + +export default eventData; diff --git a/front/odiparpack/app/api/helpers.js b/front/odiparpack/app/api/helpers.js new file mode 100644 index 0000000..10e78e0 --- /dev/null +++ b/front/odiparpack/app/api/helpers.js @@ -0,0 +1,68 @@ +export function fireEvent(node, eventName) { + let doc = {}; + if (node.ownerDocument) { + doc = node.ownerDocument; + } else if (node.nodeType === 9) { + doc = node; + } + if (node.dispatchEvent) { + let eventClass = ''; + switch (eventName) { + case 'click': + case 'mousedown': + case 'mouseup': + eventClass = 'MouseEvents'; + break; + case 'focus': + case 'change': + case 'blur': + case 'select': + eventClass = 'HTMLEvents'; + break; + default: + break; + } + const event = doc.createEvent(eventClass); + event.initEvent(eventName, true, true); // All events created as bubbling and cancelable. + + event.synthetic = true; // allow detection of synthetic events + // The second parameter says go ahead with the default action + node.dispatchEvent(event, true); + } else if (node.fireEvent) { + // IE-old school style + const event = doc.createEventObject(); + event.synthetic = true; // allow detection of synthetic events + node.fireEvent('on' + eventName, event); + } +} + +export function executionEnvironment() { + const canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement); + + return { + canUseDOM, + canUseWorkers: typeof Worker !== 'undefined', + canUseEventListeners: + canUseDOM && !!(window.addEventListener || window.attachEvent), + canUseViewport: canUseDOM && !!window.screen + }; +} + +export function loadImages(arr) { + if (!executionEnvironment().canUseDOM) return Promise.reject(Error('no server rendering for new Image')); + + const createImg = path => { + const img = new Image(); + img.src = path; + img.alt = 'img'; + + return new Promise(res => { + if (img.naturalWidth) res(img); + + img.onload = () => res(img); + img.onerror = () => res(img); + }); + }; + + return Promise.all(arr.map(c => createImg(c))); +} diff --git a/front/odiparpack/app/api/images.js b/front/odiparpack/app/api/images.js new file mode 100644 index 0000000..e79be0f --- /dev/null +++ b/front/odiparpack/app/api/images.js @@ -0,0 +1,71 @@ +const images = [ + // 1 + 'http://via.placeholder.com/1050x700/2196F3/FFFFFF/', + 'http://via.placeholder.com/1050x700/3F51B5/FFFFFF/', + 'http://via.placeholder.com/1050x700/00BCD4/FFFFFF/', + 'http://via.placeholder.com/1050x700/009688/FFFFFF/', + 'http://via.placeholder.com/1050x700/01579B/FFFFFF/', + 'http://via.placeholder.com/1050x700/0097A7/FFFFFF/', + 'http://via.placeholder.com/1050x700/43A047/FFFFFF/', + 'http://via.placeholder.com/1050x700/558B2F/FFFFFF/', + 'http://via.placeholder.com/1050x700/1DE9B6/767676/', + 'http://via.placeholder.com/1050x700/00E5FF/767676/', + 'http://via.placeholder.com/1050x700/C6FF00/767676/', + // 2 + 'http://via.placeholder.com/1050x700/D4E157/767676/', + 'http://via.placeholder.com/1050x700/F8BBD0/767676/', + 'http://via.placeholder.com/1050x700/FFCA28/767676/', + 'http://via.placeholder.com/1050x700/CFD8DC/767676/', + 'http://via.placeholder.com/1050x700/673AB7/FFFFFF/', + 'http://via.placeholder.com/1050x700/EF5350/FFFFFF/', + 'http://via.placeholder.com/1050x700/1E88E5/FFFFFF/', + 'http://via.placeholder.com/1050x700/3D5AFE/FFFFFF/', + 'http://via.placeholder.com/1050x700/EF6C00/FFFFFF/', + 'http://via.placeholder.com/1050x700/795548/FFFFFF/', + // 3 + 'http://via.placeholder.com/1050x700/FFE57F/767676/', + 'http://via.placeholder.com/1050x700/DCEDC8/767676/', + 'http://via.placeholder.com/1050x700/E1BEE7/767676/', + 'http://via.placeholder.com/1050x700/BBDEFB/767676/', + 'http://via.placeholder.com/1050x700/388E3C/FFFFFF/', + 'http://via.placeholder.com/1050x700/651FFF/FFFFFF/', + 'http://via.placeholder.com/1050x700/757575/FFFFFF/', + 'http://via.placeholder.com/1050x700/E91E63/FFFFFF/', + 'http://via.placeholder.com/1050x700/607D8B/FFFFFF/', + 'http://via.placeholder.com/1050x700/AA00FF/FFFFFF/', + // 4 + 'http://via.placeholder.com/1050x700/827717/FFFFFF/', + 'http://via.placeholder.com/1050x700/E64A19/FFFFFF/', + 'http://via.placeholder.com/1050x700/C2185B/FFFFFF/', + 'http://via.placeholder.com/1050x700/AA00FF/FFFFFF/', + 'http://via.placeholder.com/1050x700/1976D2/FFFFFF/', + 'http://via.placeholder.com/1050x700/D1C4E9/767676/', + 'http://via.placeholder.com/1050x700/81D4FA/767676/', + 'http://via.placeholder.com/1050x700/E0F2F1/767676/', + 'http://via.placeholder.com/1050x700/E6EE9C/767676/', + 'http://via.placeholder.com/1050x700/FFEB3B/767676/', + // 5 + 'http://via.placeholder.com/1050x700/E040FB/FFFFFF/', + 'http://via.placeholder.com/1050x700/C62828/FFFFFF/', + 'http://via.placeholder.com/1050x700/AD1457/FFFFFF/', + 'http://via.placeholder.com/1050x700/673AB7/FFFFFF/', + 'http://via.placeholder.com/1050x700/651FFF/FFFFFF/', + 'http://via.placeholder.com/1050x700/00BFA5/FFFFFF/', + 'http://via.placeholder.com/1050x700/A5D6A7/767676/', + 'http://via.placeholder.com/1050x700/AED581/767676/', + 'http://via.placeholder.com/1050x700/FFB74D/767676/', + 'http://via.placeholder.com/1050x700/00BFA5/767676/', + // 6 + 'http://via.placeholder.com/100x100/C6FF00/FFFFFF/', + 'http://via.placeholder.com/100x100/F44336/FFFFFF/', + 'http://via.placeholder.com/100x100/673AB7/FFFFFF/', + 'http://via.placeholder.com/100x100/03A9F4/FFFFFF/', + 'http://via.placeholder.com/100x100/4CAF50/FFFFFF/', + 'http://via.placeholder.com/100x100/FF5722/FFFFFF/', + 'http://via.placeholder.com/100x100/607D8B/FFFFFF/', + 'http://via.placeholder.com/100x100/795548/FFFFFF/', + 'http://via.placeholder.com/100x100/8BC34A/FFFFFF/', + 'http://via.placeholder.com/100x100/00BCD4/FFFFFF/', +]; + +export default images; diff --git a/front/odiparpack/app/api/imgData.js b/front/odiparpack/app/api/imgData.js new file mode 100644 index 0000000..82ae4cb --- /dev/null +++ b/front/odiparpack/app/api/imgData.js @@ -0,0 +1,82 @@ +import imgApi from './images'; +const imgData = [ + { + img: imgApi[3], + thumb: imgApi[3], + title: 'Breakfast', + author: 'jill111', + cols: 2, + featured: true, + }, + { + img: imgApi[5], + thumb: imgApi[5], + title: 'Tasty burger', + author: 'director90', + }, + { + img: imgApi[33], + thumb: imgApi[33], + title: 'Camera', + author: 'Danson67', + }, + { + img: imgApi[35], + thumb: imgApi[35], + title: 'Morning', + author: 'fancycrave1', + featured: true, + }, + { + img: imgApi[41], + thumb: imgApi[41], + title: 'Hats', + author: 'Hans', + }, + { + img: imgApi[43], + thumb: imgApi[43], + title: 'Honey', + author: 'fancycravel', + }, + { + img: imgApi[7], + thumb: imgApi[7], + title: 'Vegetables', + author: 'jill111', + cols: 2, + }, + { + img: imgApi[9], + thumb: imgApi[9], + title: 'Water plant', + author: 'BkrmadtyaKarki', + }, + { + img: imgApi[17], + thumb: imgApi[17], + title: 'Mushrooms', + author: 'PublicDomainPictures', + }, + { + img: imgApi[13], + thumb: imgApi[13], + title: 'Olive oil', + author: 'congerdesign', + }, + { + img: imgApi[19], + thumb: imgApi[19], + title: 'Sea star', + cols: 2, + author: '821292', + }, + { + img: imgApi[47], + thumb: imgApi[47], + title: 'Bike', + author: 'danfador', + }, +]; + +export default imgData; diff --git a/front/odiparpack/app/api/imgDataMasonry.js b/front/odiparpack/app/api/imgDataMasonry.js new file mode 100644 index 0000000..5297583 --- /dev/null +++ b/front/odiparpack/app/api/imgDataMasonry.js @@ -0,0 +1,68 @@ +import imgApi from './images'; +const imgData = [ + { + img: imgApi[41], + title: 'Breakfast', + author: 'jill111', + }, + { + img: imgApi[42], + title: 'Tasty burger', + author: 'director90', + }, + { + img: imgApi[50], + title: 'Camera', + author: 'Danson67', + }, + { + img: imgApi[39], + title: 'Morning', + author: 'fancycrave1', + featured: true, + }, + { + img: imgApi[2], + title: 'Hats', + author: 'Hans', + }, + { + img: imgApi[22], + title: 'Honey', + author: 'fancycravel', + }, + { + img: imgApi[46], + title: 'Vegetables', + author: 'jill111', + cols: 2, + }, + { + img: imgApi[20], + title: 'Water plant', + author: 'BkrmadtyaKarki', + }, + { + img: imgApi[10], + title: 'Mushrooms', + author: 'PublicDomainPictures', + }, + { + img: imgApi[27], + title: 'Olive oil', + author: 'congerdesign', + }, + { + img: imgApi[13], + title: 'Sea star', + cols: 2, + author: '821292', + }, + { + img: imgApi[30], + title: 'Bike', + author: 'danfador', + }, +]; + +export default imgData; diff --git a/front/odiparpack/app/api/link.js b/front/odiparpack/app/api/link.js new file mode 100644 index 0000000..c8dd260 --- /dev/null +++ b/front/odiparpack/app/api/link.js @@ -0,0 +1,13 @@ +module.exports = { + dashboard: '/app', + login: '/login', + market: '#', + email: '/app/pages/email', + profile: '/app/pages/user-profile', + calendar: '/app/pages/calendar', + twitter: '#', + github: '#', + pinterest: '#', + linkedin: '#', + buy: '#', +}; diff --git a/front/odiparpack/app/api/material-icon-cheat.txt b/front/odiparpack/app/api/material-icon-cheat.txt new file mode 100644 index 0000000..3c8b075 --- /dev/null +++ b/front/odiparpack/app/api/material-icon-cheat.txt @@ -0,0 +1,932 @@ +3d_rotation e84d +ac_unit eb3b +access_alarm e190 +access_alarms e191 +access_time e192 +accessibility e84e +accessible e914 +account_balance e84f +account_balance_wallet e850 +account_box e851 +account_circle e853 +adb e60e +add e145 +add_a_photo e439 +add_alarm e193 +add_alert e003 +add_box e146 +add_circle e147 +add_circle_outline e148 +add_location e567 +add_shopping_cart e854 +add_to_photos e39d +add_to_queue e05c +adjust e39e +airline_seat_flat e630 +airline_seat_flat_angled e631 +airline_seat_individual_suite e632 +airline_seat_legroom_extra e633 +airline_seat_legroom_normal e634 +airline_seat_legroom_reduced e635 +airline_seat_recline_extra e636 +airline_seat_recline_normal e637 +airplanemode_active e195 +airplanemode_inactive e194 +airplay e055 +airport_shuttle eb3c +alarm e855 +alarm_add e856 +alarm_off e857 +alarm_on e858 +album e019 +all_inclusive eb3d +all_out e90b +android e859 +announcement e85a +apps e5c3 +archive e149 +arrow_back e5c4 +arrow_downward e5db +arrow_drop_down e5c5 +arrow_drop_down_circle e5c6 +arrow_drop_up e5c7 +arrow_forward e5c8 +arrow_upward e5d8 +art_track e060 +aspect_ratio e85b +assessment e85c +assignment e85d +assignment_ind e85e +assignment_late e85f +assignment_return e860 +assignment_returned e861 +assignment_turned_in e862 +assistant e39f +assistant_photo e3a0 +attach_file e226 +attach_money e227 +attachment e2bc +audiotrack e3a1 +autorenew e863 +av_timer e01b +backspace e14a +backup e864 +battery_alert e19c +battery_charging_full e1a3 +battery_full e1a4 +battery_std e1a5 +battery_unknown e1a6 +beach_access eb3e +beenhere e52d +block e14b +bluetooth e1a7 +bluetooth_audio e60f +bluetooth_connected e1a8 +bluetooth_disabled e1a9 +bluetooth_searching e1aa +blur_circular e3a2 +blur_linear e3a3 +blur_off e3a4 +blur_on e3a5 +book e865 +bookmark e866 +bookmark_border e867 +border_all e228 +border_bottom e229 +border_clear e22a +border_color e22b +border_horizontal e22c +border_inner e22d +border_left e22e +border_outer e22f +border_right e230 +border_style e231 +border_top e232 +border_vertical e233 +branding_watermark e06b +brightness_1 e3a6 +brightness_2 e3a7 +brightness_3 e3a8 +brightness_4 e3a9 +brightness_5 e3aa +brightness_6 e3ab +brightness_7 e3ac +brightness_auto e1ab +brightness_high e1ac +brightness_low e1ad +brightness_medium e1ae +broken_image e3ad +brush e3ae +bubble_chart e6dd +bug_report e868 +build e869 +burst_mode e43c +business e0af +business_center eb3f +cached e86a +cake e7e9 +call e0b0 +call_end e0b1 +call_made e0b2 +call_merge e0b3 +call_missed e0b4 +call_missed_outgoing e0e4 +call_received e0b5 +call_split e0b6 +call_to_action e06c +camera e3af +camera_alt e3b0 +camera_enhance e8fc +camera_front e3b1 +camera_rear e3b2 +camera_roll e3b3 +cancel e5c9 +card_giftcard e8f6 +card_membership e8f7 +card_travel e8f8 +casino eb40 +cast e307 +cast_connected e308 +center_focus_strong e3b4 +center_focus_weak e3b5 +change_history e86b +chat e0b7 +chat_bubble e0ca +chat_bubble_outline e0cb +check e5ca +check_box e834 +check_box_outline_blank e835 +check_circle e86c +chevron_left e5cb +chevron_right e5cc +child_care eb41 +child_friendly eb42 +chrome_reader_mode e86d +class e86e +clear e14c +clear_all e0b8 +close e5cd +closed_caption e01c +cloud e2bd +cloud_circle e2be +cloud_done e2bf +cloud_download e2c0 +cloud_off e2c1 +cloud_queue e2c2 +cloud_upload e2c3 +code e86f +collections e3b6 +collections_bookmark e431 +color_lens e3b7 +colorize e3b8 +comment e0b9 +compare e3b9 +compare_arrows e915 +computer e30a +confirmation_number e638 +contact_mail e0d0 +contact_phone e0cf +contacts e0ba +content_copy e14d +content_cut e14e +content_paste e14f +control_point e3ba +control_point_duplicate e3bb +copyright e90c +create e150 +create_new_folder e2cc +credit_card e870 +crop e3be +crop_16_9 e3bc +crop_3_2 e3bd +crop_5_4 e3bf +crop_7_5 e3c0 +crop_din e3c1 +crop_free e3c2 +crop_landscape e3c3 +crop_original e3c4 +crop_portrait e3c5 +crop_rotate e437 +crop_square e3c6 +dashboard e871 +data_usage e1af +date_range e916 +dehaze e3c7 +delete e872 +delete_forever e92b +delete_sweep e16c +description e873 +desktop_mac e30b +desktop_windows e30c +details e3c8 +developer_board e30d +developer_mode e1b0 +device_hub e335 +devices e1b1 +devices_other e337 +dialer_sip e0bb +dialpad e0bc +directions e52e +directions_bike e52f +directions_boat e532 +directions_bus e530 +directions_car e531 +directions_railway e534 +directions_run e566 +directions_subway e533 +directions_transit e535 +directions_walk e536 +disc_full e610 +dns e875 +do_not_disturb e612 +do_not_disturb_alt e611 +do_not_disturb_off e643 +do_not_disturb_on e644 +dock e30e +domain e7ee +done e876 +done_all e877 +donut_large e917 +donut_small e918 +drafts e151 +drag_handle e25d +drive_eta e613 +dvr e1b2 +edit e3c9 +edit_location e568 +eject e8fb +email e0be +enhanced_encryption e63f +equalizer e01d +error e000 +error_outline e001 +euro_symbol e926 +ev_station e56d +event e878 +event_available e614 +event_busy e615 +event_note e616 +event_seat e903 +exit_to_app e879 +expand_less e5ce +expand_more e5cf +explicit e01e +explore e87a +exposure e3ca +exposure_neg_1 e3cb +exposure_neg_2 e3cc +exposure_plus_1 e3cd +exposure_plus_2 e3ce +exposure_zero e3cf +extension e87b +face e87c +fast_forward e01f +fast_rewind e020 +favorite e87d +favorite_border e87e +featured_play_list e06d +featured_video e06e +feedback e87f +fiber_dvr e05d +fiber_manual_record e061 +fiber_new e05e +fiber_pin e06a +fiber_smart_record e062 +file_download e2c4 +file_upload e2c6 +filter e3d3 +filter_1 e3d0 +filter_2 e3d1 +filter_3 e3d2 +filter_4 e3d4 +filter_5 e3d5 +filter_6 e3d6 +filter_7 e3d7 +filter_8 e3d8 +filter_9 e3d9 +filter_9_plus e3da +filter_b_and_w e3db +filter_center_focus e3dc +filter_drama e3dd +filter_frames e3de +filter_hdr e3df +filter_list e152 +filter_none e3e0 +filter_tilt_shift e3e2 +filter_vintage e3e3 +find_in_page e880 +find_replace e881 +fingerprint e90d +first_page e5dc +fitness_center eb43 +flag e153 +flare e3e4 +flash_auto e3e5 +flash_off e3e6 +flash_on e3e7 +flight e539 +flight_land e904 +flight_takeoff e905 +flip e3e8 +flip_to_back e882 +flip_to_front e883 +folder e2c7 +folder_open e2c8 +folder_shared e2c9 +folder_special e617 +font_download e167 +format_align_center e234 +format_align_justify e235 +format_align_left e236 +format_align_right e237 +format_bold e238 +format_clear e239 +format_color_fill e23a +format_color_reset e23b +format_color_text e23c +format_indent_decrease e23d +format_indent_increase e23e +format_italic e23f +format_line_spacing e240 +format_list_bulleted e241 +format_list_numbered e242 +format_paint e243 +format_quote e244 +format_shapes e25e +format_size e245 +format_strikethrough e246 +format_textdirection_l_to_r e247 +format_textdirection_r_to_l e248 +format_underlined e249 +forum e0bf +forward e154 +forward_10 e056 +forward_30 e057 +forward_5 e058 +free_breakfast eb44 +fullscreen e5d0 +fullscreen_exit e5d1 +functions e24a +g_translate e927 +gamepad e30f +games e021 +gavel e90e +gesture e155 +get_app e884 +gif e908 +golf_course eb45 +gps_fixed e1b3 +gps_not_fixed e1b4 +gps_off e1b5 +grade e885 +gradient e3e9 +grain e3ea +graphic_eq e1b8 +grid_off e3eb +grid_on e3ec +group e7ef +group_add e7f0 +group_work e886 +hd e052 +hdr_off e3ed +hdr_on e3ee +hdr_strong e3f1 +hdr_weak e3f2 +headset e310 +headset_mic e311 +healing e3f3 +hearing e023 +help e887 +help_outline e8fd +high_quality e024 +highlight e25f +highlight_off e888 +history e889 +home e88a +hot_tub eb46 +hotel e53a +hourglass_empty e88b +hourglass_full e88c +http e902 +https e88d +image e3f4 +image_aspect_ratio e3f5 +import_contacts e0e0 +import_export e0c3 +important_devices e912 +inbox e156 +indeterminate_check_box e909 +info e88e +info_outline e88f +input e890 +insert_chart e24b +insert_comment e24c +insert_drive_file e24d +insert_emoticon e24e +insert_invitation e24f +insert_link e250 +insert_photo e251 +invert_colors e891 +invert_colors_off e0c4 +iso e3f6 +keyboard e312 +keyboard_arrow_down e313 +keyboard_arrow_left e314 +keyboard_arrow_right e315 +keyboard_arrow_up e316 +keyboard_backspace e317 +keyboard_capslock e318 +keyboard_hide e31a +keyboard_return e31b +keyboard_tab e31c +keyboard_voice e31d +kitchen eb47 +label e892 +label_outline e893 +landscape e3f7 +language e894 +laptop e31e +laptop_chromebook e31f +laptop_mac e320 +laptop_windows e321 +last_page e5dd +launch e895 +layers e53b +layers_clear e53c +leak_add e3f8 +leak_remove e3f9 +lens e3fa +library_add e02e +library_books e02f +library_music e030 +lightbulb_outline e90f +line_style e919 +line_weight e91a +linear_scale e260 +link e157 +linked_camera e438 +list e896 +live_help e0c6 +live_tv e639 +local_activity e53f +local_airport e53d +local_atm e53e +local_bar e540 +local_cafe e541 +local_car_wash e542 +local_convenience_store e543 +local_dining e556 +local_drink e544 +local_florist e545 +local_gas_station e546 +local_grocery_store e547 +local_hospital e548 +local_hotel e549 +local_laundry_service e54a +local_library e54b +local_mall e54c +local_movies e54d +local_offer e54e +local_parking e54f +local_pharmacy e550 +local_phone e551 +local_pizza e552 +local_play e553 +local_post_office e554 +local_printshop e555 +local_see e557 +local_shipping e558 +local_taxi e559 +location_city e7f1 +location_disabled e1b6 +location_off e0c7 +location_on e0c8 +location_searching e1b7 +lock e897 +lock_open e898 +lock_outline e899 +looks e3fc +looks_3 e3fb +looks_4 e3fd +looks_5 e3fe +looks_6 e3ff +looks_one e400 +looks_two e401 +loop e028 +loupe e402 +low_priority e16d +loyalty e89a +mail e158 +mail_outline e0e1 +map e55b +markunread e159 +markunread_mailbox e89b +memory e322 +menu e5d2 +merge_type e252 +message e0c9 +mic e029 +mic_none e02a +mic_off e02b +mms e618 +mode_comment e253 +mode_edit e254 +monetization_on e263 +money_off e25c +monochrome_photos e403 +mood e7f2 +mood_bad e7f3 +more e619 +more_horiz e5d3 +more_vert e5d4 +motorcycle e91b +mouse e323 +move_to_inbox e168 +movie e02c +movie_creation e404 +movie_filter e43a +multiline_chart e6df +music_note e405 +music_video e063 +my_location e55c +nature e406 +nature_people e407 +navigate_before e408 +navigate_next e409 +navigation e55d +near_me e569 +network_cell e1b9 +network_check e640 +network_locked e61a +network_wifi e1ba +new_releases e031 +next_week e16a +nfc e1bb +no_encryption e641 +no_sim e0cc +not_interested e033 +note e06f +note_add e89c +notifications e7f4 +notifications_active e7f7 +notifications_none e7f5 +notifications_off e7f6 +notifications_paused e7f8 +offline_pin e90a +ondemand_video e63a +opacity e91c +open_in_browser e89d +open_in_new e89e +open_with e89f +pages e7f9 +pageview e8a0 +palette e40a +pan_tool e925 +panorama e40b +panorama_fish_eye e40c +panorama_horizontal e40d +panorama_vertical e40e +panorama_wide_angle e40f +party_mode e7fa +pause e034 +pause_circle_filled e035 +pause_circle_outline e036 +payment e8a1 +people e7fb +people_outline e7fc +perm_camera_mic e8a2 +perm_contact_calendar e8a3 +perm_data_setting e8a4 +perm_device_information e8a5 +perm_identity e8a6 +perm_media e8a7 +perm_phone_msg e8a8 +perm_scan_wifi e8a9 +person e7fd +person_add e7fe +person_outline e7ff +person_pin e55a +person_pin_circle e56a +personal_video e63b +pets e91d +phone e0cd +phone_android e324 +phone_bluetooth_speaker e61b +phone_forwarded e61c +phone_in_talk e61d +phone_iphone e325 +phone_locked e61e +phone_missed e61f +phone_paused e620 +phonelink e326 +phonelink_erase e0db +phonelink_lock e0dc +phonelink_off e327 +phonelink_ring e0dd +phonelink_setup e0de +photo e410 +photo_album e411 +photo_camera e412 +photo_filter e43b +photo_library e413 +photo_size_select_actual e432 +photo_size_select_large e433 +photo_size_select_small e434 +picture_as_pdf e415 +picture_in_picture e8aa +picture_in_picture_alt e911 +pie_chart e6c4 +pie_chart_outlined e6c5 +pin_drop e55e +place e55f +play_arrow e037 +play_circle_filled e038 +play_circle_outline e039 +play_for_work e906 +playlist_add e03b +playlist_add_check e065 +playlist_play e05f +plus_one e800 +poll e801 +polymer e8ab +pool eb48 +portable_wifi_off e0ce +portrait e416 +power e63c +power_input e336 +power_settings_new e8ac +pregnant_woman e91e +present_to_all e0df +print e8ad +priority_high e645 +public e80b +publish e255 +query_builder e8ae +question_answer e8af +queue e03c +queue_music e03d +queue_play_next e066 +radio e03e +radio_button_checked e837 +radio_button_unchecked e836 +rate_review e560 +receipt e8b0 +recent_actors e03f +record_voice_over e91f +redeem e8b1 +redo e15a +refresh e5d5 +remove e15b +remove_circle e15c +remove_circle_outline e15d +remove_from_queue e067 +remove_red_eye e417 +remove_shopping_cart e928 +reorder e8fe +repeat e040 +repeat_one e041 +replay e042 +replay_10 e059 +replay_30 e05a +replay_5 e05b +reply e15e +reply_all e15f +report e160 +report_problem e8b2 +restaurant e56c +restaurant_menu e561 +restore e8b3 +restore_page e929 +ring_volume e0d1 +room e8b4 +room_service eb49 +rotate_90_degrees_ccw e418 +rotate_left e419 +rotate_right e41a +rounded_corner e920 +router e328 +rowing e921 +rss_feed e0e5 +rv_hookup e642 +satellite e562 +save e161 +scanner e329 +schedule e8b5 +school e80c +screen_lock_landscape e1be +screen_lock_portrait e1bf +screen_lock_rotation e1c0 +screen_rotation e1c1 +screen_share e0e2 +sd_card e623 +sd_storage e1c2 +search e8b6 +security e32a +select_all e162 +send e163 +sentiment_dissatisfied e811 +sentiment_neutral e812 +sentiment_satisfied e813 +sentiment_very_dissatisfied e814 +sentiment_very_satisfied e815 +settings e8b8 +settings_applications e8b9 +settings_backup_restore e8ba +settings_bluetooth e8bb +settings_brightness e8bd +settings_cell e8bc +settings_ethernet e8be +settings_input_antenna e8bf +settings_input_component e8c0 +settings_input_composite e8c1 +settings_input_hdmi e8c2 +settings_input_svideo e8c3 +settings_overscan e8c4 +settings_phone e8c5 +settings_power e8c6 +settings_remote e8c7 +settings_system_daydream e1c3 +settings_voice e8c8 +share e80d +shop e8c9 +shop_two e8ca +shopping_basket e8cb +shopping_cart e8cc +short_text e261 +show_chart e6e1 +shuffle e043 +signal_cellular_4_bar e1c8 +signal_cellular_connected_no_internet_4_bar e1cd +signal_cellular_no_sim e1ce +signal_cellular_null e1cf +signal_cellular_off e1d0 +signal_wifi_4_bar e1d8 +signal_wifi_4_bar_lock e1d9 +signal_wifi_off e1da +sim_card e32b +sim_card_alert e624 +skip_next e044 +skip_previous e045 +slideshow e41b +slow_motion_video e068 +smartphone e32c +smoke_free eb4a +smoking_rooms eb4b +sms e625 +sms_failed e626 +snooze e046 +sort e164 +sort_by_alpha e053 +spa eb4c +space_bar e256 +speaker e32d +speaker_group e32e +speaker_notes e8cd +speaker_notes_off e92a +speaker_phone e0d2 +spellcheck e8ce +star e838 +star_border e83a +star_half e839 +stars e8d0 +stay_current_landscape e0d3 +stay_current_portrait e0d4 +stay_primary_landscape e0d5 +stay_primary_portrait e0d6 +stop e047 +stop_screen_share e0e3 +storage e1db +store e8d1 +store_mall_directory e563 +straighten e41c +streetview e56e +strikethrough_s e257 +style e41d +subdirectory_arrow_left e5d9 +subdirectory_arrow_right e5da +subject e8d2 +subscriptions e064 +subtitles e048 +subway e56f +supervisor_account e8d3 +surround_sound e049 +swap_calls e0d7 +swap_horiz e8d4 +swap_vert e8d5 +swap_vertical_circle e8d6 +switch_camera e41e +switch_video e41f +sync e627 +sync_disabled e628 +sync_problem e629 +system_update e62a +system_update_alt e8d7 +tab e8d8 +tab_unselected e8d9 +tablet e32f +tablet_android e330 +tablet_mac e331 +tag_faces e420 +tap_and_play e62b +terrain e564 +text_fields e262 +text_format e165 +textsms e0d8 +texture e421 +theaters e8da +thumb_down e8db +thumb_up e8dc +thumbs_up_down e8dd +time_to_leave e62c +timelapse e422 +timeline e922 +timer e425 +timer_10 e423 +timer_3 e424 +timer_off e426 +title e264 +toc e8de +today e8df +toll e8e0 +tonality e427 +touch_app e913 +toys e332 +track_changes e8e1 +traffic e565 +train e570 +tram e571 +transfer_within_a_station e572 +transform e428 +translate e8e2 +trending_down e8e3 +trending_flat e8e4 +trending_up e8e5 +tune e429 +turned_in e8e6 +turned_in_not e8e7 +tv e333 +unarchive e169 +undo e166 +unfold_less e5d6 +unfold_more e5d7 +update e923 +usb e1e0 +verified_user e8e8 +vertical_align_bottom e258 +vertical_align_center e259 +vertical_align_top e25a +vibration e62d +video_call e070 +video_label e071 +video_library e04a +videocam e04b +videocam_off e04c +videogame_asset e338 +view_agenda e8e9 +view_array e8ea +view_carousel e8eb +view_column e8ec +view_comfy e42a +view_compact e42b +view_day e8ed +view_headline e8ee +view_list e8ef +view_module e8f0 +view_quilt e8f1 +view_stream e8f2 +view_week e8f3 +vignette e435 +visibility e8f4 +visibility_off e8f5 +voice_chat e62e +voicemail e0d9 +volume_down e04d +volume_mute e04e +volume_off e04f +volume_up e050 +vpn_key e0da +vpn_lock e62f +wallpaper e1bc +warning e002 +watch e334 +watch_later e924 +wb_auto e42c +wb_cloudy e42d +wb_incandescent e42e +wb_iridescent e436 +wb_sunny e430 +wc e63d +web e051 +web_asset e069 +weekend e16b +whatshot e80e +widgets e1bd +wifi e63e +wifi_lock e1e1 +wifi_tethering e1e2 +work e8f9 +wrap_text e25b +youtube_searched_for e8fa +zoom_in e8ff +zoom_out e900 +zoom_out_map e56b diff --git a/front/odiparpack/app/api/menu.js b/front/odiparpack/app/api/menu.js new file mode 100644 index 0000000..dce8f58 --- /dev/null +++ b/front/odiparpack/app/api/menu.js @@ -0,0 +1,454 @@ +module.exports = [ + { + key: 'dashboard', + name: 'Dashboard', + icon: 'dashboard', + child: [ + { + key: 'dashboard_v1', + name: 'Dashboard V1', + link: '/app' + }, + { + key: 'dashboard_v2', + name: 'Dashboard V2', + link: '/app/dashboard-v2' + }, + ] + }, + { + key: 'layouts', + name: 'Layouts', + icon: 'view_column', + child: [ + { + key: 'grid', + name: 'Grid', + link: '/app/layouts/grid' + }, + { + key: 'application_layout', + name: 'App Layout', + link: '/app/layouts/app-layout' + }, + { + key: 'responsive', + name: 'Responsive', + link: '/app/layouts/responsive' + } + ] + }, + { + key: 'tables', + name: 'Tables', + icon: 'grid_on', + child: [ + { + key: 'basic_table', + name: 'Basic Table', + link: '/app/tables/basic-table' + }, + { + key: 'data_table', + name: 'Data Table', + link: '/app/tables/data-table' + }, + { + key: 'tree_table', + name: 'Tree Table', + link: '/app/tables/tree-table' + }, + { + key: 'crud_table', + name: 'CRUD Table', + link: '/app/tables/crud-table' + }, + { + key: 'table_playground', + name: 'Table Playgound', + link: '/app/tables/table-playground' + }, + ] + }, + { + key: 'forms', + name: 'Form Button', + icon: 'border_color', + child: [ + { + key: 'reduxform', + name: 'Redux Form', + link: '/app/forms/reduxform' + }, + { + key: 'datetimepicker', + name: 'Date Time Picker', + link: '/app/forms/date-time-picker' + }, + { + key: 'checkbox_radio', + name: 'Checkbox & Radio', + link: '/app/forms/checkbox-radio' + }, + { + key: 'switches', + name: 'Switches', + link: '/app/forms/switches' + }, + { + key: 'selectbox', + name: 'Select', + link: '/app/forms/selectbox' + }, + { + key: 'buttons', + name: 'Buttons', + link: '/app/forms/buttons' + }, + { + key: 'textfields', + name: 'Textfields', + link: '/app/forms/textfields' + }, + { + key: 'autocomplete', + name: 'Autocomplete & Tag', + link: '/app/forms/autocomplete' + }, + { + key: 'slider', + name: 'Slider Range', + link: '/app/forms/slider-range' + }, + { + key: 'upload', + name: 'Upload', + link: '/app/forms/upload' + }, + { + key: 'ratting', + name: 'Ratting', + link: '/app/forms/ratting' + }, + { + key: 'texteditor', + name: 'WYSIWYG Editor', + link: '/app/forms/wysiwyg-editor' + }, + ] + }, + { + key: 'ui', + name: 'UI Elements', + icon: 'flag', + child: [ + { + key: 'icons', + name: 'Icons', + link: '/app/ui/icons' + }, + { + key: 'avatars', + name: 'Avatars', + link: '/app/ui/avatars' + }, + { + key: 'badges', + name: 'Badges', + link: '/app/ui/badges' + }, + { + key: 'card_papper', + name: 'Card & Papper', + link: '/app/ui/card-papper' + }, + { + key: 'dialog_modal', + name: 'Dialog & Modal', + link: '/app/ui/dialog-modal' + }, + { + key: 'drawer_menu', + name: 'Drawer & Menu', + link: '/app/ui/drawer-menu' + }, + { + key: 'tab', + name: 'Tabs Navigation', + link: '/app/ui/tabs' + }, + { + key: 'accordion', + name: 'Accordion', + link: '/app/ui/accordion' + }, + { + key: 'image_gird', + name: 'Image Grid Gallery', + link: '/app/ui/image-grid' + }, + { + key: 'list_divider', + name: 'List & Divider', + link: '/app/ui/list' + }, + { + key: 'popover_tooltip', + name: 'Popover & Tooltip', + link: '/app/ui/popover-tooltip' + }, + { + key: 'progress', + name: 'Progress & Spinners', + link: '/app/ui/progress' + }, + { + key: 'tags', + name: 'Tags', + link: '/app/ui/tags' + }, + { + key: 'steppers', + name: 'Steppers', + link: '/app/ui/steppers' + }, + { + key: 'notification', + name: 'Notification', + link: '/app/ui/notification' + }, + { + key: 'breadcrumbs', + name: 'Breadcrumbs', + link: '/app/ui/breadcrumbs' + }, + { + key: 'dividers', + name: 'Dividers', + link: '/app/ui/dividers' + }, + { + key: 'typography', + name: 'Typography', + link: '/app/ui/typography' + }, + { + key: 'slider_carousel', + name: 'Slider & Carousel', + link: '/app/ui/slider-carousel' + }, + { + key: 'paginations', + name: 'Paginations', + link: '/app/ui/paginations' + }, + ] + }, + { + key: 'charts', + name: 'Charts', + icon: 'insert_chart', + child: [ + { + key: 'line_charts', + name: 'Line Charts', + link: '/app/charts/line-charts' + }, + { + key: 'bar_charts', + name: 'Bar Charts', + link: '/app/charts/bar-charts' + }, + { + key: 'area_charts', + name: 'Area Charts', + link: '/app/charts/area-charts' + }, + { + key: 'pie_charts', + name: 'Pie & Donuts Charts', + link: '/app/charts/pie-charts' + }, + { + key: 'radar_charts', + name: 'Radar Charts', + link: '/app/charts/radar-charts' + }, + { + key: 'scatter_charts', + name: 'Scatter Charts', + link: '/app/charts/scatter-charts' + }, + { + key: 'compossed_charts', + name: 'Compossed Charts', + link: '/app/charts/compossed-chart' + }, + { + key: 'responsive_charts', + name: 'Responsive Charts', + link: '/app/charts/responsive-chart' + }, + ] + }, + { + key: 'apps', + name: 'Apps', + icon: 'bubble_chart', + child: [ + { + key: 'social_media', + name: 'Social Media', + link: '/app/pages/social-media' + }, + { + key: 'ecommerce', + name: 'Ecommerce', + link: '/app/pages/ecommerce' + }, + { + key: 'contact', + name: 'Contact', + link: '/app/pages/contact' + }, + { + key: 'calendar', + name: 'Calendar', + link: '/app/pages/calendar' + }, + { + key: 'email', + name: 'Email', + link: '/app/pages/email' + }, + { + key: 'chat', + name: 'Chat', + link: '/app/pages/chat' + }, + ] + }, + { + key: 'pages', + name: 'Pages', + icon: 'library_books', + child: [ + { + key: 'user_profile', + name: 'User Profile', + link: '/app/pages/user-profile' + }, + { + key: 'gallery', + name: 'Photo Gallery', + link: '/app/pages/photo-gallery' + }, + { + key: 'not_found_page', + name: 'Not Found Page', + link: '/app/pages/not-found' + }, + { + key: 'error_page', + name: 'Error Page', + link: '/app/pages/error' + }, + { + key: 'maintenance', + name: 'Maintenance', + link: '/maintenance' + }, + { + key: 'login', + name: 'Login', + link: '/login' + }, + { + key: 'register', + name: 'Register', + link: '/register' + }, + { + key: 'reset', + name: 'Reset Password', + link: '/reset-password' + }, + { + key: 'lock', + name: 'Lock Screen', + link: '/lock-screen' + }, + { + key: 'blank', + name: 'Blank Page', + link: '/app/pages/blank-page' + }, + { + key: 'help_support', + name: 'Help & Support', + link: '/app/pages/help-support' + }, + ] + }, + { + key: 'maps', + name: 'Maps', + icon: 'maps', + child: [ + { + key: 'map_marker', + name: 'Map Marker', + link: '/app/maps/map-marker' + }, + { + key: 'map_direction', + name: 'Map Direction', + link: '/app/maps/map-direction' + }, + { + key: 'map_searchbox', + name: 'Map with Searchbox', + link: '/app/maps/map-searchbox' + }, + { + key: 'map_traffic', + name: 'Traffic Indicator', + link: '/app/maps/map-traffic' + }, + { + key: 'street_view', + name: 'Street View', + link: '/app/maps/street-view' + }, + ] + }, + { + key: 'menu_levels', + name: 'Menu Levels', + icon: 'sort', + child: [ + { + key: 'level_1', + name: 'Level 1', + link: '/#' + }, + { + key: 'level_2', + keyParent: 'menu_levels', + name: 'Level 2', + child: [ + { + key: 'sub_menu_1', + name: 'Sub Menu 1', + link: '/#' + }, + { + key: 'sub_menu_2', + name: 'Sub Menu 2', + link: '/#' + }, + ] + }, + ] + } +]; diff --git a/front/odiparpack/app/api/notifMessage.js b/front/odiparpack/app/api/notifMessage.js new file mode 100644 index 0000000..2d2b48b --- /dev/null +++ b/front/odiparpack/app/api/notifMessage.js @@ -0,0 +1,12 @@ +module.exports = { + saved: 'Data has been saved', + updated: 'Data has been updated', + removed: 'Item has been removed', + posted: 'Your post has been submitted', + commented: 'Your comment has been submitted', + discard: 'Action canceled', + addCart: 'Item added to cart', + checkout: 'Thank you for shopping', + sent: 'Email sent', + labeled: 'You just changed email label', +}; diff --git a/front/odiparpack/app/api/otherMenu.js b/front/odiparpack/app/api/otherMenu.js new file mode 100644 index 0000000..9a69704 --- /dev/null +++ b/front/odiparpack/app/api/otherMenu.js @@ -0,0 +1,12 @@ +module.exports = [ + { + key: 'settings', + name: 'Settings', + link: '/app/pages/settings' + }, + { + key: 'help_support', + name: 'Help & Support', + link: '/app/pages/help-support' + }, +]; diff --git a/front/odiparpack/app/api/productData.js b/front/odiparpack/app/api/productData.js new file mode 100644 index 0000000..4c1ee3c --- /dev/null +++ b/front/odiparpack/app/api/productData.js @@ -0,0 +1,126 @@ +import imgApi from './images'; +const productData = [ + { + id: '1', + name: 'Cras convallis lacus orc', + thumbnail: imgApi[21], + desc: 'Curabitur egestas consequat lorem, vel fermentum augue porta id.', + ratting: 4, + price: 30, + prevPrice: 0, + discount: '', + soldout: false, + }, + { + id: '2', + name: 'Vivamus sit amet interdum elit', + thumbnail: imgApi[22], + desc: 'Maecenas nisl libero, tincidunt id odio id, feugiat vulputate quam.', + ratting: 0, + price: 15, + prevPrice: 150, + discount: '90%', + soldout: false, + }, + { + id: '3', + name: 'Fusce placerat enim et odio molestie sagittis', + thumbnail: imgApi[23], + desc: 'Duis tristique metus magna, lobortis aliquam risus euismod sit amet', + ratting: 5, + price: 30, + prevPrice: 0, + discount: '', + soldout: true, + }, + { + id: '4', + name: 'Pellentesque ac bibendum tortor', + thumbnail: imgApi[24], + desc: 'Nam posuere accumsan porta', + ratting: 2, + price: 80, + prevPrice: 100, + discount: '20%', + soldout: false, + }, + { + id: '5', + name: 'Curabitur egestas consequat lorem', + thumbnail: imgApi[25], + desc: 'Aenean sit amet magna vel magna fringilla fermentum', + ratting: 5, + price: 50, + prevPrice: 0, + discount: '', + soldout: false, + }, + { + id: '6', + name: 'Aenean facilisis vitae purus facilisis semper', + thumbnail: imgApi[26], + desc: 'Vestibulum bibendum nisi eget magna malesuada', + ratting: 4, + price: 75, + prevPrice: 100, + discount: '25%', + soldout: false, + }, + { + id: '7', + name: 'Aenean sit amet magna vel magna fringilla fermentum', + thumbnail: imgApi[27], + desc: 'Nam posuere accumsan porta. Integer id orci sed ante tincidunt tincidunt sit amet sed libero.', + ratting: 5, + price: 20, + prevPrice: 16, + discount: '20%', + soldout: false, + }, + { + id: '8', + name: 'Ut sed eros finibus', + thumbnail: imgApi[28], + desc: 'Curabitur egestas consequat lorem, vel fermentum augue porta id.', + ratting: 1, + price: 30, + prevPrice: 0, + discount: '', + soldout: false, + }, + { + id: '9', + name: 'Nulla lobortis nunc vitae nisi', + thumbnail: imgApi[29], + desc: 'Sed mi neque, convallis at ipsum at, blandit pretium enim', + ratting: 4, + price: 50, + prevPrice: 100, + discount: '50%', + soldout: false, + }, + { + id: '10', + name: 'Nam posuere accumsan', + thumbnail: imgApi[30], + desc: 'Integer id orci sed ante tincidunt tincidunt sit amet sed libero.', + ratting: 5, + price: 30, + prevPrice: 0, + discount: '', + soldout: true, + }, + { + id: '11', + name: 'Cras convallis lacus orc', + thumbnail: imgApi[37], + desc: 'Curabitur egestas consequat lorem, vel fermentum augue porta id.', + ratting: 4, + price: 66, + prevPrice: 200, + discount: '67%', + soldout: false, + }, +]; + +export default productData; diff --git a/front/odiparpack/app/api/settingList.js b/front/odiparpack/app/api/settingList.js new file mode 100644 index 0000000..4bc4aac --- /dev/null +++ b/front/odiparpack/app/api/settingList.js @@ -0,0 +1,79 @@ +const settingList = [ + { + icon: 'chrome_reader_mode', + name: 'General', + caption: 'Lorem ipsum dolor sit amet', + }, + { + icon: 'computer', + name: 'Screen', + caption: 'Fusce placerat enim et odio molestie sagittis.', + }, + { + icon: 'language', + name: 'Language', + caption: 'Vestibulum bibendum', + }, + { + icon: 'security', + name: 'Security', + caption: 'Pellentesque ullamcorper aliquet ultrices', + }, + { + icon: 'power', + name: 'Power', + caption: 'Nam posuere accumsan porta', + }, + { + icon: 'keyboard', + name: 'Keyboard', + caption: 'Nulla vehicula leo ut augue tincidunt', + }, + { + icon: 'print', + name: 'Printer', + caption: 'Aenean facilisis vitae purus facilisis semper', + }, + { + icon: 'audiotrack', + name: 'Audio', + caption: 'Sed imperdiet enim ligula', + }, + { + icon: 'cloud', + name: 'Cloud', + caption: 'Nam posuere accumsan porta', + }, + { + icon: 'mail', + name: 'Email', + caption: 'Sed imperdiet enim ligula', + }, + { + icon: 'bluetooth_connected', + name: 'Bluetooth', + caption: 'Aenean facilisis vitae purus facilisis semper', + }, + { + icon: 'share', + name: 'Sharing', + caption: 'Sed imperdiet enim ligula', + }, + { + icon: 'supervisor_account', + name: 'Account', + caption: 'Lorem ipsum dolor sit amet', + }, + { + icon: 'access_time', + name: 'Date and Time', + caption: 'Fusce placerat enim et odio molestie sagittis.', + }, + { + icon: 'accessibility', + name: 'Accessibility', + caption: 'Vestibulum bibendum', + }, +]; + +export default settingList; diff --git a/front/odiparpack/app/api/themePalette.js b/front/odiparpack/app/api/themePalette.js new file mode 100644 index 0000000..c7971b3 --- /dev/null +++ b/front/odiparpack/app/api/themePalette.js @@ -0,0 +1,207 @@ +const palette = { + purpleRedTheme: { + palette: { + primary: { + light: '#E8EAF6', + main: '#3F51B5', + dark: '#283593', + contrastText: '#fff', + }, + secondary: { + light: '#B3E5FC', + main: '#03A9F4', + dark: '#0277BD', + contrastText: '#fff', + }, + }, + }, + greenTheme: { + palette: { + primary: { + light: '#F1F8E9', + main: '#689F38', + dark: '#33691E', + contrastText: '#fff', + }, + secondary: { + light: '#FFECB3', + main: '#FF8F00', + dark: '#E65100', + contrastText: '#fff', + }, + }, + }, + magentaTheme: { + palette: { + primary: { + light: '#FCE4EC', + main: '#EC407A', + dark: '#D81B60', + contrastText: '#fff', + }, + secondary: { + light: '#E0F7FA', + main: '#00BCD4', + dark: '#0097A7', + contrastText: '#fff', + }, + }, + }, + purpleTheme: { + palette: { + primary: { + light: '#EDE7F6', + main: '#AB47BC', + dark: '#8E24AA', + contrastText: '#fff', + }, + secondary: { + light: '#F1F8E9', + main: '#7CB342', + dark: '#558B2F', + contrastText: '#fff', + }, + }, + }, + blueTheme: { + palette: { + primary: { + light: '#E8EAF6', + main: '#3F51B5', + dark: '#283593', + contrastText: '#fff', + }, + secondary: { + light: '#B3E5FC', + main: '#03A9F4', + dark: '#0277BD', + contrastText: '#fff', + }, + }, + }, + orangeTheme: { + palette: { + primary: { + light: '#FFE0B2', + main: '#EF6C00', + dark: '#E65100', + contrastText: '#fff', + }, + secondary: { + light: '#F3E5F5', + main: '#9C27B0', + dark: '#7B1FA2', + contrastText: '#fff', + }, + }, + }, + cyanTheme: { + palette: { + primary: { + light: '#E0F7FA', + main: '#0097A7', + dark: '#00838F', + contrastText: '#fff', + }, + secondary: { + light: '#F1F8E9', + main: '#8BC34A', + dark: '#33691E', + contrastText: '#fff', + }, + }, + }, + redTheme: { + palette: { + primary: { + light: '#FFEBEE', + main: '#EF5350', + dark: '#E53935', + contrastText: '#fff', + }, + secondary: { + light: '#ECEFF1', + main: '#607D8B', + dark: '#455A64', + contrastText: '#fff', + }, + }, + }, + skyBlueTheme: { + palette: { + primary: { + light: '#E3F2FD', + main: '#2196F3', + dark: '#1565C0', + contrastText: '#fff', + }, + secondary: { + light: '#A7FFEB', + main: '#00BFA5', + dark: '#00796B', + contrastText: '#fff', + }, + }, + }, + greyTheme: { + palette: { + primary: { + light: '#ECEFF1', + main: '#607D8B', + dark: '#455A64', + contrastText: '#fff', + }, + secondary: { + light: '#F5F5F5', + main: '#757575', + dark: '#424242', + contrastText: '#fff', + }, + }, + }, + greenNatureTheme: { + palette: { + primary: { + light: '#E8F5E9', + main: '#43A047', + dark: '#2E7D32', + contrastText: '#fff', + }, + secondary: { + light: '#E0F2F1', + main: '#009688', + dark: '#00796B', + contrastText: '#fff', + }, + }, + }, + yellowCyanTheme: { + palette: { + primary: { + light: '#FFF3E0', + main: '#FF8F00', + dark: '#E65100', + contrastText: '#fff', + }, + secondary: { + light: '#E0F7FA', + main: '#00BCD4', + dark: '#006064', + contrastText: '#fff', + }, + }, + } +}; + + +const themePalette = (color) => ({ + palette: { + primary: palette[color].palette.primary, + secondary: palette[color].palette.secondary, + }, + typography: { + useNextVariants: true, + }, +}); + +export default themePalette; diff --git a/front/odiparpack/app/api/timelineData.js b/front/odiparpack/app/api/timelineData.js new file mode 100644 index 0000000..1eeb425 --- /dev/null +++ b/front/odiparpack/app/api/timelineData.js @@ -0,0 +1,155 @@ +import imgApi from './images'; +import avatarApi from './avatars'; +const timelineData = [ + { + id: '1', + name: 'John Doe', + date: 'September, 12 2018', + time: '08:01', + icon: 'add_circle', + avatar: avatarApi[6], + image: imgApi[19], + content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum. Fusce placerat enim et odio molestie sagittis.', + liked: true, + comments: [ + { + id: '1_1', + from: 'Jane Doe', + avatar: avatarApi[2], + date: 'May, 29 2018', + message: 'Maecenas nisl libero, tincidunt id odio id, feugiat vulputate quam. Vestibulum feugiat rhoncus metus. In non erat et ipsum molestie porta sit amet ut felis. Vestibulum a massa vestibulum, gravida odio id, fringilla ipsum.' + }, + { + id: '1_2', + from: 'Jim Doe', + avatar: avatarApi[10], + date: 'May, 29 2018', + message: 'Ut sed eros finibus, placerat orci id, dapibus mauris.' + }, + { + id: '1_3', + from: 'Jihan Doe', + avatar: avatarApi[4], + date: 'May, 29 2018', + message: 'Sed imperdiet enim ligula, vitae viverra justo porta vel. Duis eget felis bibendum, pretium mi sed, placerat ante. Vivamus sit amet interdum elit. Proin lacinia erat ac velit tempus auctor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aliquam nec ex aliquet, aliquam neque non, gravida est. ' + } + ] + }, + { + id: '2', + name: 'John Doe', + date: 'September, 10 2018', + time: '03:20', + icon: 'date_range', + avatar: avatarApi[6], + image: '', + content: 'Quisque ut metus sit amet augue rutrum feugiat. Vestibulum bibendum nisi eget magna malesuada, at mattis eros efficitur. Vivamus facilisis quam ullamcorper iaculis gravida.', + liked: true, + comments: [ + { + id: '2_1', + from: 'Jane Doe', + avatar: avatarApi[2], + date: 'May, 29 2018', + message: 'Maecenas nisl libero, tincidunt id odio id, feugiat vulputate quam. Vestibulum feugiat rhoncus metus. In non erat et ipsum molestie porta sit amet ut felis. Vestibulum a massa vestibulum, gravida odio id, fringilla ipsum.' + }, + { + id: '2_2', + from: 'Jihan Doe', + avatar: avatarApi[4], + date: 'May, 29 2018', + message: 'Sed imperdiet enim ligula, vitae viverra justo porta vel. Duis eget felis bibendum, pretium mi sed, placerat ante. Vivamus sit amet interdum elit. Proin lacinia erat ac velit tempus auctor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aliquam nec ex aliquet, aliquam neque non, gravida est. ' + } + ] + }, + { + id: '3', + name: 'John Doe', + date: 'Aug, 17 2018', + time: '04:10', + icon: 'description', + avatar: avatarApi[6], + image: imgApi[20], + content: 'Vivamus sit amet interdum elit. Proin lacinia erat ac velit tempus auctor. Interdum et malesuada fames ac ante ipsum primis in faucibus.', + liked: false, + comments: [ + { + id: '3_1', + from: 'Jack Doe', + avatar: avatarApi[8], + date: 'May, 29 2018', + message: 'Maecenas nisl libero, tincidunt id odio id, feugiat vulputate quam. Vestibulum feugiat rhoncus metus. In non erat et ipsum molestie porta sit amet ut felis. Vestibulum a massa vestibulum, gravida odio id, fringilla ipsum.' + }, + { + id: '3_2', + from: 'Jim Doe', + avatar: avatarApi[9], + date: 'May, 29 2018', + message: 'Ut sed eros finibus, placerat orci id, dapibus mauris.' + }, + { + id: '3_3', + from: 'Jihan Doe', + avatar: avatarApi[4], + date: 'May, 29 2018', + message: 'Sed imperdiet enim ligula, vitae viverra justo porta vel. Duis eget felis bibendum, pretium mi sed, placerat ante. Vivamus sit amet interdum elit. Proin lacinia erat ac velit tempus auctor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aliquam nec ex aliquet, aliquam neque non, gravida est. ' + }, + { + id: '3_4', + from: 'Janet Doe', + avatar: avatarApi[5], + date: 'May, 29 2018', + message: 'Aenean sit amet magna' + } + ] + }, + { + id: '4', + name: 'John Doe', + date: 'Aug, 10 2018', + time: '08:05', + icon: 'favorite', + avatar: avatarApi[6], + image: '', + content: 'Donec dignissim, odio ac imperdiet luctus, ante nisl accumsan justo, et venenatis ante metus pellentesque sem.', + liked: true, + comments: [ + { + id: '4_1', + from: 'Jane Doe', + avatar: avatarApi[2], + date: 'May, 29 2018', + message: 'Maecenas nisl libero, tincidunt id odio id, feugiat vulputate quam. Vestibulum feugiat rhoncus metus. In non erat et ipsum molestie porta sit amet ut felis. Vestibulum a massa vestibulum, gravida odio id, fringilla ipsum.' + } + ] + }, + { + id: '5', + name: 'John Doe', + date: 'Aug, 10 2018', + time: '02:50', + icon: 'lock', + avatar: avatarApi[6], + image: imgApi[12], + content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum. Fusce placerat enim et odio molestie sagittis.', + liked: false, + comments: [ + { + id: '5_1', + from: 'Jim Doe', + avatar: avatarApi[9], + date: 'May, 29 2018', + message: 'Ut sed eros finibus, placerat orci id, dapibus mauris.' + }, + { + id: '5_2', + from: 'Jihan Doe', + avatar: avatarApi[4], + date: 'May, 29 2018', + message: 'Sed imperdiet enim ligula, vitae viverra justo porta vel. Duis eget felis bibendum, pretium mi sed, placerat ante. Vivamus sit amet interdum elit. Proin lacinia erat ac velit tempus auctor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aliquam nec ex aliquet, aliquam neque non, gravida est. ' + } + ] + } +]; + +export default timelineData; diff --git a/front/odiparpack/app/api/validation.js b/front/odiparpack/app/api/validation.js new file mode 100644 index 0000000..6e69324 --- /dev/null +++ b/front/odiparpack/app/api/validation.js @@ -0,0 +1,82 @@ +const isEmpty = value => value === undefined || value === null || value === ''; // eslint-disable-line +const join = (rules) => (value, data) => rules.map(rule => rule(value, data)).filter(error => !!error)[0]; // eslint-disable-line + +export function email(value) { // eslint-disable-line + // Let's not start a debate on email regex. This is just for an example app! + if (!isEmpty(value) && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) { + return 'Invalid email address'; + } +} + +export function required(value) { // eslint-disable-line + if (isEmpty(value)) { + return 'Required'; + } +} + +export function minLength(min) { + return (value) => { // eslint-disable-line + if (!isEmpty(value) && value.length < min) { + return `Must be at least ${min} characters`; + } + }; +} + +export function maxLength(max) { + return value => { // eslint-disable-line + if (!isEmpty(value) && value.length > max) { + return `Must be no more than ${max} characters`; + } + }; +} + +export function integer(value) { // eslint-disable-line + if (!Number.isInteger(Number(value))) { + return 'Must be an integer'; + } +} + +export function oneOf(enumeration) { + return (value) => { // eslint-disable-line + if (!~enumeration.indexOf(value)) { // eslint-disable-line + return `Must be one of: ${enumeration.join(', ')}`; + } + }; +} + +export function match(field) { + return (value, data) => { // eslint-disable-line + if (data) { + if (value !== data[field]) { + return 'Do not match'; + } + } + }; +} + +export function phone(value) { // eslint-disable-line + if (!/^(\d|\+)[0-9+() -]+$/.test(value)) { + return 'Invalid phone format'; + } +} + +export function createValidator(rules, section, activate) { + return (data = {}) => { + data = data.toJS ? data.toJS() : data; // eslint-disable-line + data = section && data.section ? data[section] : data; // eslint-disable-line + if (activate && typeof data[activate] !== 'undefined') { + if (!data[activate]) { + return {}; + } + } + const errors = {}; + Object.keys(rules).forEach((key) => { + const rule = join([].concat(rules[key])); // concat enables both functions and arrays of functions + const error = rule(data[key], data); + if (error) { + errors[key] = error; + } + }); + return errors; + }; +} diff --git a/front/odiparpack/app/app.js b/front/odiparpack/app/app.js new file mode 100644 index 0000000..6845268 --- /dev/null +++ b/front/odiparpack/app/app.js @@ -0,0 +1,87 @@ +/** + * app.js + * + * This is the entry file for the application, only setup and boilerplate + * code. + */ + +// Needed for redux-saga es6 generator support +import '@babel/polyfill'; + +// Import all the third party stuff +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; +import { ConnectedRouter } from 'connected-react-router/immutable'; +import history from 'utils/history'; +import 'sanitize.css/sanitize.css'; +// Import root app +import App from 'containers/App'; + +// Import Language Provider +import LanguageProvider from 'containers/LanguageProvider'; + +// Load the favicon and the .htaccess file +/* eslint-disable import/no-unresolved, import/extensions */ +/* eslint-enable import/no-unresolved, import/extensions */ + +import configureStore from './redux/configureStore'; + +import './styles/layout/base.scss'; + +// Import i18n messages +import { translationMessages } from './i18n'; + +// Create redux store with history +const initialState = {}; +const store = configureStore(initialState, history); +const MOUNT_NODE = document.getElementById('app'); + +const render = messages => { + ReactDOM.render( + <Provider store={store}> + <LanguageProvider messages={messages}> + <ConnectedRouter history={history}> + <App /> + </ConnectedRouter> + </LanguageProvider> + </Provider>, + MOUNT_NODE, + ); +}; + +if (module.hot) { + // Hot reloadable React components and translation json files + // modules.hot.accept does not accept dynamic dependencies, + // have to be constants at compile-time + module.hot.accept(['./i18n', 'containers/App'], () => { + ReactDOM.unmountComponentAtNode(MOUNT_NODE); + render(translationMessages); + }); +} + +// Chunked polyfill for browsers without Intl support + +if (!window.Intl) { + new Promise(resolve => { + resolve(import('intl')); + }) + .then(() => Promise.all([ + import('intl/locale-data/jsonp/en.js'), + import('intl/locale-data/jsonp/de.js'), + ])) // eslint-disable-line prettier/prettier + .then(() => render(translationMessages)) + .catch(err => { + throw err; + }); +} else { + render(translationMessages); +} + + +// Install ServiceWorker and AppCache in the end since +// it's not most important operation and if main code fails, +// we do not want it installed +if (process.env.NODE_ENV === 'production') { + require('offline-plugin/runtime').install(); // eslint-disable-line global-require +} diff --git a/front/odiparpack/app/components/.DS_Store b/front/odiparpack/app/components/.DS_Store Binary files differnew file mode 100644 index 0000000..a82440f --- /dev/null +++ b/front/odiparpack/app/components/.DS_Store diff --git a/front/odiparpack/app/components/Badges/LimitedBadges.js b/front/odiparpack/app/components/Badges/LimitedBadges.js new file mode 100644 index 0000000..a0e0ecd --- /dev/null +++ b/front/odiparpack/app/components/Badges/LimitedBadges.js @@ -0,0 +1,27 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { Badge } from '@material-ui/core'; + +class LimitedBadges extends PureComponent { + render() { + const { + children, + limit, + value, + ...rest + } = this.props; + return ( + <Badge badgeContent={value > limit ? limit + '+' : value} {...rest}> + { children } + </Badge> + ); + } +} + +LimitedBadges.propTypes = { + children: PropTypes.node.isRequired, + value: PropTypes.number.isRequired, + limit: PropTypes.number.isRequired, +}; + +export default LimitedBadges; diff --git a/front/odiparpack/app/components/BreadCrumb/BreadCrumb.js b/front/odiparpack/app/components/BreadCrumb/BreadCrumb.js new file mode 100644 index 0000000..0d11753 --- /dev/null +++ b/front/odiparpack/app/components/BreadCrumb/BreadCrumb.js @@ -0,0 +1,55 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import { Link, Route } from 'react-router-dom'; +import styles from './breadCrumb-jss'; + +const Breadcrumbs = (props) => { + const { + classes, + theme, + separator, + location + } = props; + return ( + <section className={classNames(theme === 'dark' ? classes.dark : classes.light, classes.breadcrumbs)}> + <Route + path="*" + render={() => { + let parts = location.pathname.split('/'); + const place = parts[parts.length - 1]; + parts = parts.slice(1, parts.length - 1); + return ( + <p> + You are here: + <span> + { + parts.map((part, partIndex) => { + const path = ['', ...parts.slice(0, partIndex + 1)].join('/'); + return ( + <Fragment key={path}> + <Link to={path}>{part}</Link> + { separator } + </Fragment> + ); + }) + } + {place} + </span> + </p> + ); + }} + /> + </section> + ); +}; + +Breadcrumbs.propTypes = { + classes: PropTypes.object.isRequired, + location: PropTypes.object.isRequired, + theme: PropTypes.string.isRequired, + separator: PropTypes.string.isRequired, +}; + +export default withStyles(styles)(Breadcrumbs); diff --git a/front/odiparpack/app/components/BreadCrumb/breadCrumb-jss.js b/front/odiparpack/app/components/BreadCrumb/breadCrumb-jss.js new file mode 100644 index 0000000..fe2dc47 --- /dev/null +++ b/front/odiparpack/app/components/BreadCrumb/breadCrumb-jss.js @@ -0,0 +1,29 @@ +const styles = theme => ({ + dark: {}, + breadcrumbs: { + position: 'relative', + display: 'block', + fontSize: 12, + color: 'rgba(255, 255, 255, 0.5)', + '& p': { + display: 'block', + '& span': { + textTransform: 'capitalize', + marginLeft: 5, + }, + '& a': { + color: theme.palette.common.white, + textDecoration: 'none', + margin: '0 5px' + } + }, + '&$dark': { + color: theme.palette.grey[900], + '& a': { + color: theme.palette.grey[900] + } + } + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/Calendar/AddEvent.js b/front/odiparpack/app/components/Calendar/AddEvent.js new file mode 100644 index 0000000..ef1f5a5 --- /dev/null +++ b/front/odiparpack/app/components/Calendar/AddEvent.js @@ -0,0 +1,49 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Add from '@material-ui/icons/Add'; +import { Fab, Tooltip } from '@material-ui/core'; +import FloatingPanel from '../Panel/FloatingPanel'; +import AddEventForm from './AddEventForm'; +import styles from './calendar-jss.js'; + + +class AddEvent extends React.Component { + showResult(values) { + setTimeout(() => { + this.props.submit(values); + }, 500); // simulate server latency + } + + render() { + const { + classes, + openForm, + closeForm, + addEvent + } = this.props; + const branch = ''; + return ( + <div> + <Tooltip title="Add New Event"> + <Fab color="secondary" onClick={() => addEvent()} className={classes.addBtn}> + <Add /> + </Fab> + </Tooltip> + <FloatingPanel title="Add New Event" openForm={openForm} branch={branch} closeForm={() => closeForm()}> + <AddEventForm onSubmit={(values) => this.showResult(values)} /> + </FloatingPanel> + </div> + ); + } +} + +AddEvent.propTypes = { + classes: PropTypes.object.isRequired, + openForm: PropTypes.bool.isRequired, + addEvent: PropTypes.func.isRequired, + closeForm: PropTypes.func.isRequired, + submit: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(AddEvent); diff --git a/front/odiparpack/app/components/Calendar/AddEventForm.js b/front/odiparpack/app/components/Calendar/AddEventForm.js new file mode 100644 index 0000000..9edccca --- /dev/null +++ b/front/odiparpack/app/components/Calendar/AddEventForm.js @@ -0,0 +1,178 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { KeyboardDatePicker, MuiPickersUtilsProvider } from '@material-ui/pickers'; +import MomentUtils from '@date-io/moment'; +import { reduxForm, Field } from 'redux-form/immutable'; +import { connect } from 'react-redux'; +import css from 'ba-styles/Form.scss'; +import { Button, Radio, RadioGroup, FormLabel, FormControlLabel } from '@material-ui/core'; +import { TextFieldRedux } from '../Forms/ReduxFormMUI'; +import styles from './calendar-jss'; + + +// validation functions +const required = value => (value == null ? 'Required' : undefined); + +const DateTimePickerRow = props => { + const { + showErrorsInline, + dispatch, + input: { onChange, value }, + meta: { touched, error }, + ...other + } = props; + + const showError = showErrorsInline || touched; + return ( + <MuiPickersUtilsProvider utils={MomentUtils}> + <KeyboardDatePicker + error={!!(showError && error)} + helperText={showError && error} + value={value || new Date()} + onChange={onChange} + disablePast + label="DateTimePicker" + {...other} + /> + </MuiPickersUtilsProvider> + ); +}; + +DateTimePickerRow.propTypes = { + showErrorsInline: PropTypes.bool, + dispatch: PropTypes.func, + input: PropTypes.object.isRequired, + meta: PropTypes.object.isRequired, +}; + +const renderRadioGroup = ({ input, ...rest }) => ( + <RadioGroup + {...input} + {...rest} + valueselected={input.value} + onChange={(event, value) => input.onChange(value)} + /> +); + +renderRadioGroup.propTypes = { + input: PropTypes.object.isRequired, +}; + +DateTimePickerRow.defaultProps = { + showErrorsInline: false, + dispatch: () => {}, +}; + +class AddEventForm extends React.Component { + state = { + selectedDate: new Date(), + } + + onChangeDate = date => { + this.setState({ selectedDate: date }); + } + + saveRef = ref => { + this.ref = ref; + return this.ref; + }; + + render() { + const { + classes, + reset, + pristine, + submitting, + handleSubmit, + } = this.props; + const { selectedDate } = this.state; + return ( + <div> + <form onSubmit={handleSubmit}> + <section className={css.bodyForm}> + <div> + <Field + name="title" + component={TextFieldRedux} + placeholder="Event Name" + label="Event Name" + validate={required} + required + ref={this.saveRef} + className={classes.field} + /> + </div> + <div> + <Field + name="start" + component={DateTimePickerRow} + placeholder="Start Date" + value={selectedDate} + onChange={this.onChangeDate} + label="Start Date" + className={classes.field} + /> + </div> + <div> + <Field + name="end" + component={DateTimePickerRow} + placeholder="End Date" + value={selectedDate} + onChange={this.onChangeDate} + label="End Date" + className={classes.field} + /> + </div> + <div className={classes.fieldBasic}> + <FormLabel component="label">Label Color</FormLabel> + <Field name="hexColor" className={classes.inlineWrap} component={renderRadioGroup}> + <FormControlLabel value="F8BBD0" control={<Radio className={classes.redRadio} classes={{ root: classes.redRadio, checked: classes.checked }} />} label="Red" /> + <FormControlLabel value="C8E6C9" control={<Radio className={classes.greenRadio} classes={{ root: classes.greenRadio, checked: classes.checked }} />} label="Green" /> + <FormControlLabel value="B3E5FC" control={<Radio className={classes.blueRadio} classes={{ root: classes.blueRadio, checked: classes.checked }} />} label="Blue" /> + <FormControlLabel value="D1C4E9" control={<Radio className={classes.violetRadio} classes={{ root: classes.violetRadio, checked: classes.checked }} />} label="Violet" /> + <FormControlLabel value="FFECB3" control={<Radio className={classes.orangeRadio} classes={{ root: classes.orangeRadio, checked: classes.checked }} />} label="Orange" /> + </Field> + </div> + </section> + <div className={css.buttonArea}> + <Button variant="contained" color="secondary" type="submit" disabled={submitting}> + Submit + </Button> + <Button + type="button" + disabled={pristine || submitting} + onClick={reset} + > + Reset + </Button> + </div> + </form> + </div> + ); + } +} + +AddEventForm.propTypes = { + classes: PropTypes.object.isRequired, + handleSubmit: PropTypes.func.isRequired, + reset: PropTypes.func.isRequired, + pristine: PropTypes.bool.isRequired, + submitting: PropTypes.bool.isRequired, +}; + +const AddEventFormRedux = reduxForm({ + form: 'immutableAddCalendar', + enableReinitialize: true, +})(AddEventForm); + +const reducer = 'calendar'; +const AddEventInit = connect( + state => ({ + force: state, + initialValues: state.getIn([reducer, 'formValues']) + }), +)(AddEventFormRedux); + +export default withStyles(styles)(AddEventInit); diff --git a/front/odiparpack/app/components/Calendar/DetailEvent.js b/front/odiparpack/app/components/Calendar/DetailEvent.js new file mode 100644 index 0000000..b25d8b9 --- /dev/null +++ b/front/odiparpack/app/components/Calendar/DetailEvent.js @@ -0,0 +1,167 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import Today from '@material-ui/icons/Today'; +import { Typography, IconButton, Menu, MenuItem, Divider, Popover } from '@material-ui/core'; +import styles from './calendar-jss'; + + +const ITEM_HEIGHT = 48; + +class DetailEvent extends React.Component { + state = { + anchorElOpt: null, + }; + + handleClickOpt = event => { + this.setState({ anchorElOpt: event.currentTarget }); + }; + + handleCloseOpt = () => { + this.setState({ anchorElOpt: null }); + }; + + handleDeleteEvent = (event) => { + this.setState({ anchorElOpt: null }); + this.props.remove(event); + this.props.close(); + }; + + render() { + const getDate = date => { + if (date._isAMomentObject) { + return date.format('MMMM Do YYYY'); + } + let dd = date.getDate(); + const monthNames = [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ]; + const mm = monthNames[date.getMonth()]; // January is 0! + const yyyy = date.getFullYear(); + + if (dd < 10) { + dd = '0' + dd; + } + + const convertedDate = mm + ', ' + dd + ' ' + yyyy; + + return convertedDate; + }; + + const getTime = time => { + if (time._isAMomentObject) { + return time.format('LT'); + } + let h = time.getHours(); + let m = time.getMinutes(); + + if (h < 10) { + h = '0' + h; + } + + if (m < 10) { + m = '0' + m; + } + + const convertedTime = h + ':' + m; + return convertedTime; + }; + + const { + classes, + anchorEl, + event, + close, + anchorPos + } = this.props; + const { anchorElOpt } = this.state; + return ( + <Popover + open={anchorEl} + anchorReference="anchorPosition" + anchorPosition={anchorPos} + className={classes.eventDetail} + onClose={close} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'center', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'center', + }} + > + <IconButton + aria-label="More" + aria-owns={anchorElOpt ? 'long-menu' : null} + aria-haspopup="true" + className={classes.moreOpt} + onClick={this.handleClickOpt} + > + <MoreVertIcon /> + </IconButton> + {event !== null + && ( + <Fragment> + <Menu + id="long-menu" + anchorEl={anchorElOpt} + open={Boolean(anchorElOpt)} + onClose={this.handleCloseOpt} + PaperProps={{ + style: { + maxHeight: ITEM_HEIGHT * 4.5, + width: 200, + }, + }} + > + <MenuItem onClick={() => this.handleDeleteEvent(event)}> + Delete Event + </MenuItem> + </Menu> + <Typography variant="h5" style={{ background: `#${event.hexColor}` }} className={classes.eventName}> + <Today /> + {' '} + {event.title} + </Typography> + <div className={classes.time}> + <Typography> +Start: + {getDate(event.start)} + {' '} +- + {getTime(event.start)} + </Typography> + <Divider className={classes.divider} /> + <Typography> +End: + {getDate(event.end)} + {' '} +- + {getTime(event.end)} + </Typography> + </div> + </Fragment> + ) + } + </Popover> + ); + } +} + +DetailEvent.propTypes = { + classes: PropTypes.object.isRequired, + anchorEl: PropTypes.bool.isRequired, + anchorPos: PropTypes.object.isRequired, + event: PropTypes.object, + close: PropTypes.func.isRequired, + remove: PropTypes.func.isRequired, +}; + +DetailEvent.defaultProps = { + event: null, +}; + +export default withStyles(styles)(DetailEvent); diff --git a/front/odiparpack/app/components/Calendar/EventCalendar.js b/front/odiparpack/app/components/Calendar/EventCalendar.js new file mode 100644 index 0000000..fe7b76e --- /dev/null +++ b/front/odiparpack/app/components/Calendar/EventCalendar.js @@ -0,0 +1,70 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import BigCalendar from 'react-big-calendar'; +import moment from 'moment'; +import { Paper } from '@material-ui/core'; +import styles from './calendar-jss'; + +BigCalendar.setLocalizer(BigCalendar.momentLocalizer(moment)); + +function Event(event) { + return ( + <span className="eventBlock">{event.title}</span> + ); +} + +class EventCalendar extends React.Component { + eventStyleGetter = event => { + const backgroundColor = '#' + event.hexColor; + const style = { + backgroundColor, + }; + return { + style + }; + } + + render() { + const allViews = Object.keys(BigCalendar.Views).map(k => BigCalendar.Views[k]); + const { + classes, + events, + handleEventClick + } = this.props; + return ( + <Paper className={classes.root}> + <BigCalendar + className={classes.calendarWrap} + selectable + events={events} + defaultView="month" + views={allViews} + step={60} + showMultiDayTimes + scrollToTime={new Date(1970, 1, 1, 6)} + defaultDate={new Date(2015, 3, 12)} + onSelectEvent={(selectedEvent) => handleEventClick(selectedEvent)} + eventPropGetter={(this.eventStyleGetter)} + onSelectSlot={slotInfo => console.log( + `selected slot: \n\nstart ${slotInfo.start.toLocaleString()} ` + + `\nend: ${slotInfo.end.toLocaleString()}` + + `\naction: ${slotInfo.action}` + ) + } + components={{ + event: Event + }} + /> + </Paper> + ); + } +} + +EventCalendar.propTypes = { + classes: PropTypes.object.isRequired, + events: PropTypes.array.isRequired, + handleEventClick: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(EventCalendar); diff --git a/front/odiparpack/app/components/Calendar/calendar-jss.js b/front/odiparpack/app/components/Calendar/calendar-jss.js new file mode 100644 index 0000000..36ed0a4 --- /dev/null +++ b/front/odiparpack/app/components/Calendar/calendar-jss.js @@ -0,0 +1,118 @@ +import { + pink as red, + lightGreen as green, + lightBlue as blue, + deepPurple as violet, + orange, +} from '@material-ui/core/colors'; + +const styles = theme => ({ + root: { + padding: 20, + [theme.breakpoints.down('sm')]: { + padding: '20px 8px' + }, + }, + calendarWrap: { + minHeight: 600 + }, + addBtn: { + position: 'fixed', + bottom: 30, + right: 30, + zIndex: 100 + }, + typography: { + margin: theme.spacing(2), + }, + divider: { + margin: '5px 0', + textAlign: 'center' + }, + button: { + margin: theme.spacing(1), + }, + eventName: { + padding: '50px 20px 10px 30px', + minWidth: 400, + color: 'rgba(0, 0, 0, 0.7)', + '& svg': { + top: -2, + position: 'relative' + } + }, + time: { + padding: 20 + }, + moreOpt: { + position: 'absolute', + top: 10, + right: 10 + }, + field: { + width: '100%', + marginBottom: 20 + }, + fieldBasic: { + width: '100%', + marginBottom: 20, + marginTop: 10 + }, + inlineWrap: { + display: 'flex', + flexDirection: 'row' + }, + redRadio: { + color: red[600], + '& svg': { + borderRadius: '50%', + background: red[100], + }, + '&$checked': { + color: red[500], + }, + }, + greenRadio: { + color: green[600], + '& svg': { + borderRadius: '50%', + background: green[100], + }, + '&$checked': { + color: green[500], + }, + }, + blueRadio: { + color: blue[600], + '& svg': { + borderRadius: '50%', + background: blue[100], + }, + '&$checked': { + color: blue[500], + }, + }, + violetRadio: { + color: violet[600], + '& svg': { + borderRadius: '50%', + background: violet[100], + }, + '&$checked': { + color: violet[500], + }, + }, + orangeRadio: { + color: orange[600], + '& svg': { + borderRadius: '50%', + background: orange[100], + }, + '&$checked': { + color: orange[500], + }, + }, + checked: {}, +}); + +export default styles; diff --git a/front/odiparpack/app/components/CardPaper/GeneralCard.js b/front/odiparpack/app/components/CardPaper/GeneralCard.js new file mode 100644 index 0000000..85f7787 --- /dev/null +++ b/front/odiparpack/app/components/CardPaper/GeneralCard.js @@ -0,0 +1,52 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import FavoriteIcon from '@material-ui/icons/Favorite'; +import ShareIcon from '@material-ui/icons/Share'; +import Comment from '@material-ui/icons/Comment'; +import { Card, CardActions, CardContent, IconButton } from '@material-ui/core'; +import styles from './cardStyle-jss'; + + +class GeneralCard extends React.Component { + render() { + const { + classes, + children, + liked, + shared, + commented + } = this.props; + return ( + <Card className={classes.card}> + <CardContent> + {children} + </CardContent> + <CardActions className={classes.actions}> + <IconButton aria-label="Add to favorites" className={classes.button}> + <FavoriteIcon className={liked > 0 && classes.liked} /> + <span className={classes.num}>{liked}</span> + </IconButton> + <IconButton aria-label="Share" className={classes.button}> + <ShareIcon className={shared > 0 && classes.shared} /> + <span className={classes.num}>{shared}</span> + </IconButton> + <IconButton aria-label="Comment" className={classes.rightIcon}> + <Comment /> + <span className={classes.num}>{commented}</span> + </IconButton> + </CardActions> + </Card> + ); + } +} + +GeneralCard.propTypes = { + classes: PropTypes.object.isRequired, + children: PropTypes.node.isRequired, + liked: PropTypes.number.isRequired, + shared: PropTypes.number.isRequired, + commented: PropTypes.number.isRequired, +}; + +export default withStyles(styles)(GeneralCard); diff --git a/front/odiparpack/app/components/CardPaper/IdentityCard.js b/front/odiparpack/app/components/CardPaper/IdentityCard.js new file mode 100644 index 0000000..4f4bd6c --- /dev/null +++ b/front/odiparpack/app/components/CardPaper/IdentityCard.js @@ -0,0 +1,70 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import LocalPhone from '@material-ui/icons/LocalPhone'; +import LocationOn from '@material-ui/icons/LocationOn'; +import { + Card, Typography, CardContent, + ListItem, ListItemText, ListItemAvatar, + Avatar, Divider +} from '@material-ui/core'; +import styles from './cardStyle-jss'; + + +class IdentityCard extends React.Component { + render() { + const { + classes, + title, + name, + avatar, + phone, + address, + } = this.props; + return ( + <Card className={classes.card}> + <CardContent> + <Typography variant="subtitle1">{title}</Typography> + <Divider className={classes.divider} /> + <ListItem> + <ListItemAvatar> + <Avatar + alt={name} + src={avatar} + className={classes.avatar} + /> + </ListItemAvatar> + <ListItemText primary="Name" secondary={name} /> + </ListItem> + <ListItem> + <ListItemAvatar> + <Avatar> + <LocalPhone /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Phone" secondary={phone} /> + </ListItem> + <ListItem> + <ListItemAvatar> + <Avatar> + <LocationOn /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Address" secondary={address} /> + </ListItem> + </CardContent> + </Card> + ); + } +} + +IdentityCard.propTypes = { + classes: PropTypes.object.isRequired, + title: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + avatar: PropTypes.string.isRequired, + phone: PropTypes.string.isRequired, + address: PropTypes.string.isRequired, +}; + +export default withStyles(styles)(IdentityCard); diff --git a/front/odiparpack/app/components/CardPaper/NewsCard.js b/front/odiparpack/app/components/CardPaper/NewsCard.js new file mode 100644 index 0000000..f9994d8 --- /dev/null +++ b/front/odiparpack/app/components/CardPaper/NewsCard.js @@ -0,0 +1,46 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Card, CardMedia, CardActions, CardContent, Button } from '@material-ui/core'; +import styles from './cardStyle-jss'; + + +class NewsCard extends React.Component { + render() { + const { + classes, + children, + title, + image, + } = this.props; + return ( + <Card className={classes.cardMedia}> + <CardMedia + className={classes.media} + image={image} + title={title} + /> + <CardContent> + {children} + </CardContent> + <CardActions> + <Button size="small" color="primary"> + Share + </Button> + <Button size="small" color="primary"> + Learn More + </Button> + </CardActions> + </Card> + ); + } +} + +NewsCard.propTypes = { + classes: PropTypes.object.isRequired, + children: PropTypes.node.isRequired, + title: PropTypes.string.isRequired, + image: PropTypes.string.isRequired, +}; + +export default withStyles(styles)(NewsCard); diff --git a/front/odiparpack/app/components/CardPaper/PlayerCard.js b/front/odiparpack/app/components/CardPaper/PlayerCard.js new file mode 100644 index 0000000..eabbb3a --- /dev/null +++ b/front/odiparpack/app/components/CardPaper/PlayerCard.js @@ -0,0 +1,66 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import SkipPreviousIcon from '@material-ui/icons/SkipPrevious'; +import PlayArrowIcon from '@material-ui/icons/PlayArrow'; +import SkipNextIcon from '@material-ui/icons/SkipNext'; +import { Typography, Card, CardMedia, CardContent, IconButton } from '@material-ui/core'; +import styles from './cardStyle-jss'; + + +class PlayerCard extends React.Component { + state = { expanded: false }; + + handleExpandClick = () => { + this.setState({ expanded: !this.state.expanded }); + }; + + render() { + const { + classes, + theme, + title, + artist, + cover, + } = this.props; + + return ( + <Card className={classes.cardPlayer}> + <div className={classes.details}> + <CardContent className={classes.content}> + <Typography variant="h5">{title}</Typography> + <Typography variant="subtitle1" color="textSecondary"> + {artist} + </Typography> + </CardContent> + <div className={classes.controls}> + <IconButton aria-label="Previous"> + {theme.direction === 'rtl' ? <SkipNextIcon /> : <SkipPreviousIcon />} + </IconButton> + <IconButton aria-label="Play/pause"> + <PlayArrowIcon className={classes.playIcon} /> + </IconButton> + <IconButton aria-label="Next"> + {theme.direction === 'rtl' ? <SkipPreviousIcon /> : <SkipNextIcon />} + </IconButton> + </div> + </div> + <CardMedia + className={classes.cover} + image={cover} + title={title} + /> + </Card> + ); + } +} + +PlayerCard.propTypes = { + classes: PropTypes.object.isRequired, + theme: PropTypes.object.isRequired, + title: PropTypes.string.isRequired, + artist: PropTypes.string.isRequired, + cover: PropTypes.string.isRequired, +}; + +export default withStyles(styles, { withTheme: true })(PlayerCard); diff --git a/front/odiparpack/app/components/CardPaper/PostCard.js b/front/odiparpack/app/components/CardPaper/PostCard.js new file mode 100644 index 0000000..9c8f05b --- /dev/null +++ b/front/odiparpack/app/components/CardPaper/PostCard.js @@ -0,0 +1,142 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import FavoriteIcon from '@material-ui/icons/Favorite'; +import ShareIcon from '@material-ui/icons/Share'; +import Comment from '@material-ui/icons/Comment'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import { + Typography, + Card, + Menu, + MenuItem, + CardHeader, + CardMedia, + CardContent, + CardActions, + IconButton, + Avatar, +} from '@material-ui/core'; +import styles from './cardStyle-jss'; + + +const optionsOpt = [ + 'Report this post', + 'Hide this post', + 'Copy link', +]; + +const ITEM_HEIGHT = 48; + +class PostCard extends React.Component { + state = { anchorElOpt: null }; + + handleClickOpt = event => { + this.setState({ anchorElOpt: event.currentTarget }); + }; + + handleCloseOpt = () => { + this.setState({ anchorElOpt: null }); + }; + + render() { + const { + classes, + avatar, + name, + date, + image, + content, + liked, + shared, + commented + } = this.props; + const { anchorElOpt } = this.state; + return ( + <Card className={classes.cardSocmed}> + <CardHeader + avatar={ + <Avatar alt="avatar" src={avatar} className={classes.avatar} /> + } + action={( + <IconButton + aria-label="More" + aria-owns={anchorElOpt ? 'long-menu' : null} + aria-haspopup="true" + className={classes.button} + onClick={this.handleClickOpt} + > + <MoreVertIcon /> + </IconButton> + )} + title={name} + subheader={date} + /> + <Menu + id="long-menu" + anchorEl={anchorElOpt} + open={Boolean(anchorElOpt)} + onClose={this.handleCloseOpt} + PaperProps={{ + style: { + maxHeight: ITEM_HEIGHT * 4.5, + width: 200, + }, + }} + > + {optionsOpt.map(option => ( + <MenuItem key={option} selected={option === 'Edit Profile'} onClick={this.handleCloseOpt}> + {option} + </MenuItem> + ))} + </Menu> + { image !== '' + && ( + <CardMedia + className={classes.media} + image={image} + title="Contemplative Reptile" + /> + ) + } + <CardContent> + <Typography component="p"> + {content} + </Typography> + </CardContent> + <CardActions className={classes.actions}> + <IconButton aria-label="Add to favorites" className={classes.button}> + <FavoriteIcon className={liked > 0 && classes.liked} /> + <span className={classes.num}>{liked}</span> + </IconButton> + <IconButton aria-label="Share" className={classes.button}> + <ShareIcon className={shared > 0 && classes.shared} /> + <span className={classes.num}>{shared}</span> + </IconButton> + <IconButton aria-label="Comment" className={classes.rightIcon}> + <Comment /> + <span className={classes.num}>{commented}</span> + </IconButton> + </CardActions> + </Card> + ); + } +} + +PostCard.propTypes = { + classes: PropTypes.object.isRequired, + avatar: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + date: PropTypes.string.isRequired, + image: PropTypes.string, + content: PropTypes.string.isRequired, + liked: PropTypes.number.isRequired, + shared: PropTypes.number.isRequired, + commented: PropTypes.number.isRequired, +}; + +PostCard.defaultProps = { + image: '' +}; + +export default withStyles(styles)(PostCard); diff --git a/front/odiparpack/app/components/CardPaper/ProductCard.js b/front/odiparpack/app/components/CardPaper/ProductCard.js new file mode 100644 index 0000000..967e2fd --- /dev/null +++ b/front/odiparpack/app/components/CardPaper/ProductCard.js @@ -0,0 +1,134 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { isWidthUp } from '@material-ui/core/withWidth'; +import classNames from 'classnames'; +import AddShoppingCart from '@material-ui/icons/AddShoppingCart'; +import Type from 'ba-styles/Typography.scss'; +import { + Typography, + withWidth, + Card, + IconButton, + Tooltip, + CardMedia, + CardActions, + CardContent, + Chip, + Fab, + Button, +} from '@material-ui/core'; +import Rating from '../Rating/Rating'; +import styles from './cardStyle-jss'; + + +class ProductCard extends React.Component { + render() { + const { + classes, + discount, + soldout, + thumbnail, + name, + desc, + ratting, + price, + prevPrice, + list, + detailOpen, + addToCart, + width, + } = this.props; + return ( + <Card className={classNames(classes.cardProduct, isWidthUp('sm', width) && list ? classes.cardList : '')}> + <div className={classes.status}> + {discount !== '' && ( + <Chip label={'Discount ' + discount} className={classes.chipDiscount} /> + )} + {soldout && ( + <Chip label="Sold Out" className={classes.chipSold} /> + )} + </div> + <CardMedia + className={classes.mediaProduct} + image={thumbnail} + title={name} + /> + <CardContent className={classes.floatingButtonWrap}> + {!soldout && ( + <Tooltip title="Add to cart" placement="top"> + <Fab onClick={addToCart} size="small" color="secondary" aria-label="add" className={classes.buttonAdd}> + <AddShoppingCart /> + </Fab> + </Tooltip> + )} + <Typography noWrap gutterBottom variant="h5" className={classes.title} component="h2"> + {name} + </Typography> + <Typography component="p" className={classes.desc}> + {desc} + </Typography> + <div className={classes.ratting}> + <Rating value={ratting} max={5} readOnly /> + </div> + </CardContent> + <CardActions className={classes.price}> + <Typography variant="h5"> + <span> +$ + {price} + </span> + </Typography> + {prevPrice > 0 && ( + <Typography variant="caption" component="h5"> + <span className={Type.lineThrought}> +$ + {prevPrice} + </span> + </Typography> + )} + <div className={classes.rightAction}> + <Button size="small" variant="outlined" color="secondary" onClick={detailOpen}> + See Detail + </Button> + {!soldout && ( + <Tooltip title="Add to cart" placement="top"> + <IconButton color="secondary" onClick={addToCart} className={classes.buttonAddList}> + <AddShoppingCart /> + </IconButton> + </Tooltip> + )} + </div> + </CardActions> + </Card> + ); + } +} + +ProductCard.propTypes = { + classes: PropTypes.object.isRequired, + discount: PropTypes.string, + width: PropTypes.string.isRequired, + soldout: PropTypes.bool, + thumbnail: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + desc: PropTypes.string.isRequired, + ratting: PropTypes.number.isRequired, + price: PropTypes.number.isRequired, + prevPrice: PropTypes.number, + list: PropTypes.bool, + detailOpen: PropTypes.func, + addToCart: PropTypes.func, +}; + +ProductCard.defaultProps = { + discount: '', + soldout: false, + prevPrice: 0, + list: false, + detailOpen: () => (false), + addToCart: () => (false), +}; + +const ProductCardResponsive = withWidth()(ProductCard); +export default withStyles(styles)(ProductCardResponsive); diff --git a/front/odiparpack/app/components/CardPaper/ProfileCard.js b/front/odiparpack/app/components/CardPaper/ProfileCard.js new file mode 100644 index 0000000..7827941 --- /dev/null +++ b/front/odiparpack/app/components/CardPaper/ProfileCard.js @@ -0,0 +1,99 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Type from 'ba-styles/Typography.scss'; +import VerifiedUser from '@material-ui/icons/VerifiedUser'; +import SupervisorAccount from '@material-ui/icons/SupervisorAccount'; +import Favorite from '@material-ui/icons/Favorite'; +import PhotoLibrary from '@material-ui/icons/PhotoLibrary'; +import { + Typography, + Card, + CardMedia, + CardContent, + CardActions, + Button, + Avatar, + BottomNavigation, + BottomNavigationAction, + Divider, +} from '@material-ui/core'; +import styles from './cardStyle-jss'; + + +class ProfileCard extends React.Component { + state = { expanded: false }; + + handleExpandClick = () => { + this.setState({ expanded: !this.state.expanded }); + }; + + render() { + const { + classes, + cover, + avatar, + name, + title, + connection, + isVerified, + btnText + } = this.props; + + return ( + <Card className={classes.cardSocmed}> + <CardMedia + className={classes.mediaProfile} + image={cover} + title="cover" + /> + <CardContent className={classes.contentProfile}> + <Avatar alt="avatar" src={avatar} className={classes.avatarBig} /> + <Typography variant="h6" className={classes.name} gutterBottom> + {name} + {isVerified && <VerifiedUser className={classes.verified} />} + </Typography> + <Typography className={classes.subheading} gutterBottom> + <span className={Type.regular}>{title}</span> + </Typography> + <Typography variant="caption" component="p"> + {connection} + {' '} +connection + </Typography> + <Button className={classes.buttonProfile} size="large" variant="outlined" color="secondary"> + {btnText} + </Button> + </CardContent> + <Divider /> + <CardActions> + <BottomNavigation + showLabels + className={classes.bottomLink} + > + <BottomNavigationAction label="20 Connection" icon={<SupervisorAccount />} /> + <BottomNavigationAction label="10 Favorites" icon={<Favorite />} /> + <BottomNavigationAction label="5 Albums" icon={<PhotoLibrary />} /> + </BottomNavigation> + </CardActions> + </Card> + ); + } +} + +ProfileCard.propTypes = { + classes: PropTypes.object.isRequired, + cover: PropTypes.string.isRequired, + avatar: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + connection: PropTypes.number.isRequired, + btnText: PropTypes.string.isRequired, + isVerified: PropTypes.bool +}; + +ProfileCard.defaultProps = { + isVerified: false +}; + +export default withStyles(styles)(ProfileCard); diff --git a/front/odiparpack/app/components/CardPaper/VideoCard.js b/front/odiparpack/app/components/CardPaper/VideoCard.js new file mode 100644 index 0000000..69aa60c --- /dev/null +++ b/front/odiparpack/app/components/CardPaper/VideoCard.js @@ -0,0 +1,90 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import PlayArrowIcon from '@material-ui/icons/PlayArrow'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import { red } from '@material-ui/core/colors'; + +import { Card, CardHeader, CardMedia, IconButton, Avatar } from '@material-ui/core'; + +const styles = theme => ({ + playIcon: { + height: 38, + width: 38, + }, + cardSocmed: { + maxWidth: 400, + }, + media: { + height: 0, + paddingTop: '56.25%', // 16:9 + position: 'relative', + }, + avatar: { + backgroundColor: red[500], + }, + playBtn: { + position: 'absolute', + top: '50%', + left: '50%', + width: 64, + height: 64, + transform: 'translate(-50%, -50%)', + '& svg': { + color: theme.palette.common.white, + fontSize: 64 + } + } +}); + +class VideoCard extends React.Component { + state = { expanded: false }; + + handleExpandClick = () => { + this.setState({ expanded: !this.state.expanded }); + }; + + render() { + const { + classes, + title, + cover, + date + } = this.props; + + return ( + <Card className={classes.cardSocmed}> + <CardMedia + className={classes.media} + image={cover} + title={title} + > + <IconButton className={classes.playBtn}><PlayArrowIcon /></IconButton> + </CardMedia> + <CardHeader + avatar={( + <Avatar aria-label="Recipe" className={classes.avatar}> + R + </Avatar> + )} + action={( + <IconButton> + <MoreVertIcon /> + </IconButton> + )} + title={title} + subheader={date} + /> + </Card> + ); + } +} + +VideoCard.propTypes = { + classes: PropTypes.object.isRequired, + title: PropTypes.string.isRequired, + cover: PropTypes.string.isRequired, + date: PropTypes.string.isRequired, +}; + +export default withStyles(styles)(VideoCard); diff --git a/front/odiparpack/app/components/CardPaper/cardStyle-jss.js b/front/odiparpack/app/components/CardPaper/cardStyle-jss.js new file mode 100644 index 0000000..df367a0 --- /dev/null +++ b/front/odiparpack/app/components/CardPaper/cardStyle-jss.js @@ -0,0 +1,188 @@ +import { pink, lightGreen, blueGrey as dark } from '@material-ui/core/colors'; + +const styles = theme => ({ + divider: { + margin: `${theme.spacing(3)}px 0` + }, + card: { + minWidth: 275, + }, + liked: { + color: pink[500] + }, + shared: { + color: lightGreen[500] + }, + num: { + fontSize: 14, + marginLeft: 5 + }, + rightIcon: { + marginLeft: 'auto', + display: 'flex', + alignItems: 'center' + }, + button: { + marginRight: theme.spacing(1) + }, + media: { + height: 0, + paddingTop: '56.25%', // 16:9 + }, + cardPlayer: { + display: 'flex', + justifyContent: 'space-between' + }, + details: { + display: 'flex', + flexDirection: 'column', + }, + content: { + flex: '1 0 auto', + }, + cover: { + width: 150, + height: 150, + }, + controls: { + display: 'flex', + alignItems: 'center', + paddingLeft: theme.spacing(1), + paddingBottom: theme.spacing(1), + }, + playIcon: { + height: 38, + width: 38, + }, + cardSocmed: { + minWidth: 275, + }, + cardProduct: { + position: 'relative' + }, + mediaProduct: { + height: 0, + paddingTop: '60.25%', // 16:9 + }, + rightAction: { + '&:not(:first-child)': { + marginLeft: 'auto', + display: 'flex', + alignItems: 'center' + } + }, + floatingButtonWrap: { + position: 'relative', + paddingTop: 50 + }, + buttonAdd: { + position: 'absolute', + right: 20, + top: -20, + }, + buttonAddList: { + display: 'none', + marginLeft: 10 + }, + title: { + fontSize: 20, + height: 30, + }, + ratting: { + margin: '10px 0', + '& button': { + width: 24, + height: 24 + } + }, + status: { + position: 'absolute', + right: 0, + top: 0, + padding: 10, + '& > *': { + margin: 5 + } + }, + desc: { + height: 45, + overflow: 'hidden' + }, + chipDiscount: { + background: theme.palette.primary.light, + color: theme.palette.primary.dark, + }, + chipSold: { + background: dark[500], + color: theme.palette.getContrastText(dark[500]), + }, + contentProfle: { + flex: '1 0 auto', + textAlign: 'center' + }, + mediaProfile: { + height: 0, + paddingTop: '36.25%', // 16:9 + }, + actions: { + display: 'flex', + }, + avatarBig: { + width: 80, + height: 80, + margin: '-56px auto 10px', + background: theme.palette.secondary.dark + }, + name: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center' + }, + buttonProfile: { + margin: 20 + }, + bottomLink: { + width: '100%', + }, + price: { + padding: theme.spacing(2), + paddingBottom: 20 + }, + contentProfile: { + textAlign: 'center' + }, + verified: { + fontSize: 16, + color: theme.palette.primary.main + }, + cardList: { + display: 'flex', + justifyContent: 'space-between', + '& $buttonAddList': { + display: 'inline-block' + }, + '& $floatingButtonWrap': { + flex: 1, + }, + '& $buttonAdd': { + display: 'none' + }, + '& $status': { + right: 'auto', + left: 0, + }, + '& $mediaProduct': { + width: 300, + paddingTop: '21.25%' + }, + '& $price': { + flexDirection: 'column', + justifyContent: 'center', + '& button': { + marginTop: 20 + } + } + }, +}); + +export default styles; diff --git a/front/odiparpack/app/components/Cart/Cart.js b/front/odiparpack/app/components/Cart/Cart.js new file mode 100644 index 0000000..d477a51 --- /dev/null +++ b/front/odiparpack/app/components/Cart/Cart.js @@ -0,0 +1,137 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import DeleteIcon from '@material-ui/icons/Delete'; +import ShoppingCartIcon from '@material-ui/icons/ShoppingCart'; +import Type from 'ba-styles/Typography.scss'; +import { + Menu, + Typography, + Button, + ListSubheader, + List, + ListItem, + ListItemText, + ListItemSecondaryAction, + IconButton, + Divider, +} from '@material-ui/core'; +import styles from './cart-jss'; + + +class Cart extends React.Component { + render() { + const { + classes, + anchorEl, + close, + dataCart, + removeItem, + totalPrice, + checkout + } = this.props; + + const getCartItem = dataArray => dataArray.map((item, index) => ( + <Fragment key={index.toString()}> + <ListItem> + <figure> + <img src={item.get('thumbnail')} alt="thumb" /> + </figure> + <ListItemText + primary={item.get('name')} + secondary={`Quantity: ${item.get('quantity')} Item - USD ${item.get('price') * item.get('quantity')}`} + className={classes.itemText} + /> + <ListItemSecondaryAction> + <IconButton aria-label="Comments" onClick={() => removeItem(item)}> + <DeleteIcon /> + </IconButton> + </ListItemSecondaryAction> + </ListItem> + <li> + <Divider /> + </li> + </Fragment> + )); + return ( + <Menu + id="cart-menu" + anchorEl={anchorEl} + anchorOrigin={{ + vertical: 'top', + horizontal: 'right', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'right', + }} + open={Boolean(anchorEl)} + onClose={close} + className={classes.cartPanel} + > + <List + component="ul" + subheader={( + <ListSubheader component="div"> + <ShoppingCartIcon /> + {' '} +Total: + {' '} + {dataCart.size} + {' '} +Unique items in Cart + </ListSubheader> + )} + className={classes.cartWrap} + > + { + dataCart.size < 1 ? ( + <div className={classes.empty}> + <Typography variant="subtitle1">Empty Cart</Typography> + <Typography variant="caption">Your shopping items will be listed here</Typography> + </div> + ) : ( + <Fragment> + {getCartItem(dataCart)} + <ListItem className={classes.totalPrice}> + <Typography variant="subtitle1"> + Total : + {' '} + <span className={Type.bold}> +$ + {totalPrice} + </span> + </Typography> + </ListItem> + <li> + <Divider /> + </li> + <ListItem> + <Button fullWidth className={classes.button} variant="contained" onClick={() => checkout()} color="secondary"> + Checkout + </Button> + </ListItem> + </Fragment> + ) + } + </List> + </Menu> + ); + } +} + +Cart.propTypes = { + classes: PropTypes.object.isRequired, + dataCart: PropTypes.object.isRequired, + anchorEl: PropTypes.object, + close: PropTypes.func.isRequired, + removeItem: PropTypes.func.isRequired, + checkout: PropTypes.func.isRequired, + totalPrice: PropTypes.number.isRequired, +}; + +Cart.defaultProps = { + anchorEl: null, +}; + +export default withStyles(styles)(Cart); diff --git a/front/odiparpack/app/components/Cart/cart-jss.js b/front/odiparpack/app/components/Cart/cart-jss.js new file mode 100644 index 0000000..aef71cd --- /dev/null +++ b/front/odiparpack/app/components/Cart/cart-jss.js @@ -0,0 +1,38 @@ +const styles = theme => ({ + totalPrice: { + background: theme.palette.grey[200], + textAlign: 'right', + display: 'block' + }, + cartWrap: { + [theme.breakpoints.up('sm')]: { + width: 400, + }, + '&:focus': { + outline: 'none' + } + }, + itemText: { + marginRight: 30, + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + width: 220 + }, + cartPanel: { + '& figure': { + width: 120, + height: 70, + overflow: 'hidden', + '& img': { + maxWidth: '100%' + } + } + }, + empty: { + textAlign: 'center', + padding: 20 + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/Chat/ChatHeader.js b/front/odiparpack/app/components/Chat/ChatHeader.js new file mode 100644 index 0000000..b3456e9 --- /dev/null +++ b/front/odiparpack/app/components/Chat/ChatHeader.js @@ -0,0 +1,122 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import ArrowBack from '@material-ui/icons/ArrowBack'; +import { Typography, AppBar, Menu, MenuItem, Avatar, IconButton, Toolbar } from '@material-ui/core'; +import styles from '../Contact/contact-jss'; + + +const optionsOpt = [ + 'Delete Conversation', + 'Option 1', + 'Option 2', + 'Option 3', +]; + +const ITEM_HEIGHT = 48; + +class ChatHeader extends React.Component { + state = { + anchorElOpt: null, + }; + + handleClickOpt = event => { + this.setState({ anchorElOpt: event.currentTarget }); + }; + + handleCloseOpt = () => { + this.setState({ anchorElOpt: null }); + }; + + handleRemove = (person) => { + this.props.remove(person); + } + + render() { + const { + classes, + chatSelected, + dataContact, + showMobileDetail, + hideDetail, + } = this.props; + const { anchorElOpt } = this.state; + return ( + <AppBar + position="absolute" + className={classNames(classes.appBar, classes.appBarShift)} + > + <Toolbar> + {showMobileDetail && ( + <IconButton + color="inherit" + aria-label="open drawer" + onClick={() => hideDetail()} + className={classes.navIconHide} + > + <ArrowBack /> + </IconButton> + )} + <Avatar alt="avatar" src={dataContact.getIn([chatSelected, 'avatar'])} className={classes.avatar} /> + <Typography variant="h6" className={classes.flex} color="inherit" noWrap> + {dataContact.getIn([chatSelected, 'name'])} + <Typography variant="caption" component="p" className={classes.status} color="inherit" noWrap> + <span className={classes.online} /> + {' '} +Online + </Typography> + </Typography> + <IconButton + aria-label="More" + aria-owns={anchorElOpt ? 'long-menu' : null} + aria-haspopup="true" + className={classes.button} + onClick={this.handleClickOpt} + > + <MoreVertIcon color="inherit" /> + </IconButton> + </Toolbar> + <Menu + id="long-menu" + anchorEl={anchorElOpt} + open={Boolean(anchorElOpt)} + onClose={this.handleCloseOpt} + PaperProps={{ + style: { + maxHeight: ITEM_HEIGHT * 4.5, + width: 200, + }, + }} + > + {optionsOpt.map(option => { + if (option === 'Delete Conversation') { + return ( + <MenuItem key={option} onClick={this.handleRemove}> + {option} + </MenuItem> + ); + } + return ( + <MenuItem key={option} selected={option === 'Edit Profile'} onClick={this.handleCloseOpt}> + {option} + </MenuItem> + ); + })} + </Menu> + </AppBar> + ); + } +} + +ChatHeader.propTypes = { + classes: PropTypes.object.isRequired, + dataContact: PropTypes.object.isRequired, + showMobileDetail: PropTypes.bool.isRequired, + hideDetail: PropTypes.func.isRequired, + remove: PropTypes.func.isRequired, + chatSelected: PropTypes.number.isRequired, +}; + +export default withStyles(styles)(ChatHeader); diff --git a/front/odiparpack/app/components/Chat/ChatRoom.js b/front/odiparpack/app/components/Chat/ChatRoom.js new file mode 100644 index 0000000..9abef89 --- /dev/null +++ b/front/odiparpack/app/components/Chat/ChatRoom.js @@ -0,0 +1,106 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import Send from '@material-ui/icons/Send'; +import dummyContents from 'ba-api/dummyContents'; +import Type from 'ba-styles/Typography.scss'; +import { Avatar, Typography, Paper, Tooltip, IconButton } from '@material-ui/core'; +import MessageField from './MessageField'; +import styles from './chatStyle-jss'; + + +class ChatRoom extends React.Component { + constructor() { + super(); + this.state = { message: '' }; + this.handleWrite = this.handleWrite.bind(this); + } + + handleWrite = (e, value) => { + this.setState({ message: value }); + }; + + resetInput = () => { + const ctn = document.getElementById('roomContainer'); + this.setState({ message: '' }); + this._field.setState({ value: '' }); + setTimeout(() => { + ctn.scrollTo(0, ctn.scrollHeight); + }, 300); + } + + sendMessageByEnter = (event, message) => { + if (event.key === 'Enter' && event.target.value !== '') { + this.props.sendMessage(message.__html); + this.resetInput(); + } + } + + sendMessage = message => { + this.props.sendMessage(message.__html); + this.resetInput(); + } + + render() { + const html = { __html: this.state.message }; + const { + classes, + dataChat, + chatSelected, + dataContact, + showMobileDetail, + } = this.props; + const { message } = this.state; + const getChat = dataArray => dataArray.map(data => { + const renderHTML = { __html: data.get('message') }; + return ( + <li className={data.get('from') === 'contact' ? classes.from : classes.to} key={data.get('id')}> + <time dateTime={data.get('date') + ' ' + data.get('time')}>{data.get('date') + ' ' + data.get('time')}</time> + {data.get('from') === 'contact' + ? <Avatar alt="avatar" src={dataContact.getIn([chatSelected, 'avatar'])} className={classes.avatar} /> + : <Avatar alt="avatar" src={dummyContents.user.avatar} className={classes.avatar} /> + } + <div className={classes.talk}> + <p><span dangerouslySetInnerHTML={renderHTML} /></p> + </div> + </li> + ); + }); + return ( + <div className={classNames(classes.root, showMobileDetail ? classes.detailPopup : '')}> + <ul className={classes.chatList} id="roomContainer"> + {dataChat.size > 0 ? getChat(dataChat) : (<Typography variant="caption" component="p" className={Type.textCenter}>{'You haven\'t made any conversation yet'}</Typography>)} + </ul> + <Paper className={classes.writeMessage}> + <MessageField + onChange={this.handleWrite} + ref={(_field) => { this._field = _field; return this._field; }} + placeholder="Type a message" + fieldType="input" + value={message} + onKeyPress={(event) => this.sendMessageByEnter(event, html)} + /> + <Tooltip id="tooltip-send" title="Send"> + <div> + <IconButton size="small" color="secondary" disabled={message === ''} onClick={() => this.sendMessage(html)} aria-label="send" className={classes.sendBtn}> + <Send /> + </IconButton> + </div> + </Tooltip> + </Paper> + </div> + ); + } +} + +ChatRoom.propTypes = { + classes: PropTypes.object.isRequired, + dataChat: PropTypes.object.isRequired, + showMobileDetail: PropTypes.bool.isRequired, + chatSelected: PropTypes.number.isRequired, + dataContact: PropTypes.object.isRequired, + sendMessage: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(ChatRoom); diff --git a/front/odiparpack/app/components/Chat/MessageField.js b/front/odiparpack/app/components/Chat/MessageField.js new file mode 100644 index 0000000..947bed4 --- /dev/null +++ b/front/odiparpack/app/components/Chat/MessageField.js @@ -0,0 +1,69 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import 'ba-styles/vendors/emoji-picker-react/emoji-picker-react.css'; + +class MessageField extends Component { + constructor(props) { + super(props); + + this.state = { + value: props.value || '', + }; + + this.onChange = this.onChange.bind(this); + } + + onChange(e) { + const { val } = this.state; + const { onChange } = this.props; + const value = e ? e.target.value : val; + + this.setState({ value }, () => { + if (typeof onChange === 'function') { + onChange(e, value); + } + }); + } + + onPickerkeypress(e) { + if (e.keyCode === 27 || e.which === 27 || e.key === 'Escape' || e.code === 'Escape') { + this.closePicker(); + } + } + + render() { + const { + onChange, + fieldType, + ...rest + } = this.props; + + const className = `emoji-text-field emoji-${fieldType}`; + const { value } = this.state; + const isInput = fieldType === 'input'; + const ref = (_field) => { + this._field = _field; + return this._field; + }; + + return ( + <div className={className}> + { (isInput) && (<input {...rest} onChange={this.onChange} type="text" ref={ref} value={value} />) } + { (!isInput) && (<textarea {...rest} onChange={this.onChange} ref={ref} value={value} />) } + </div> + ); + } +} + +MessageField.propTypes = { + value: PropTypes.string, + onChange: PropTypes.func, + fieldType: PropTypes.string.isRequired +}; + +MessageField.defaultProps = { + value: '', + onChange: () => {}, +}; + +export default MessageField; diff --git a/front/odiparpack/app/components/Chat/chatStyle-jss.js b/front/odiparpack/app/components/Chat/chatStyle-jss.js new file mode 100644 index 0000000..360b170 --- /dev/null +++ b/front/odiparpack/app/components/Chat/chatStyle-jss.js @@ -0,0 +1,148 @@ +import { lighten } from '@material-ui/core/styles/colorManipulator'; +const styles = theme => ({ + root: { + flexGrow: 1, + display: 'flex', + flexDirection: 'column', + position: 'relative', + backgroundColor: lighten(theme.palette.secondary.light, 0.9), + }, + chatList: { + padding: 24, + paddingTop: 110, + overflow: 'auto', + height: 580, + '& li': { + marginBottom: theme.spacing(6), + display: 'flex', + position: 'relative', + '& time': { + position: 'absolute', + top: -20, + color: theme.palette.grey[500], + fontSize: 11 + } + }, + }, + detailPopup: { + [theme.breakpoints.down('xs')]: { + position: 'absolute', + top: 0, + left: 0, + zIndex: 1200, + width: '100%', + overflow: 'auto', + height: 575 + } + }, + talk: { + flex: 1, + '& p': { + marginBottom: 10, + position: 'relative', + '& span': { + padding: 10, + borderRadius: 10, + display: 'inline-block' + } + } + }, + avatar: {}, + from: { + '& time': { + left: 60, + }, + '& $avatar': { + marginRight: 20 + }, + '& $talk': { + '& > p': { + '& span': { + background: theme.palette.secondary.light, + border: `1px solid ${lighten(theme.palette.secondary.main, 0.5)}`, + }, + '&:first-child': { + '& span': { + borderTopLeftRadius: 0, + }, + '&:before': { + content: '""', + borderRight: `11px solid ${lighten(theme.palette.secondary.main, 0.5)}`, + borderBottom: '17px solid transparent', + position: 'absolute', + left: -11, + top: 0 + }, + '&:after': { + content: '""', + borderRight: `10px solid ${theme.palette.secondary.light}`, + borderBottom: '15px solid transparent', + position: 'absolute', + left: -9, + top: 1 + }, + } + } + } + }, + to: { + flexDirection: 'row-reverse', + '& time': { + right: 60, + }, + '& $avatar': { + marginLeft: 20 + }, + '& $talk': { + textAlign: 'right', + '& > p': { + '& span': { + textAlign: 'left', + background: theme.palette.primary.light, + border: `1px solid ${lighten(theme.palette.primary.main, 0.5)}`, + }, + '&:first-child': { + '& span': { + borderTopRightRadius: 0, + }, + '&:before': { + content: '""', + borderLeft: `11px solid ${lighten(theme.palette.primary.main, 0.5)}`, + borderBottom: '17px solid transparent', + position: 'absolute', + right: -11, + top: 0 + }, + '&:after': { + content: '""', + borderLeft: `10px solid ${theme.palette.primary.light}`, + borderBottom: '15px solid transparent', + position: 'absolute', + right: -9, + top: 1 + }, + } + } + } + }, + messageBox: { + border: 'none', + padding: 0, + outline: 'none', + width: '100%', + '&:after, &:before': { + display: 'none' + } + }, + writeMessage: { + position: 'relative', + bottom: 16, + display: 'flex', + minHeight: 55, + margin: '0 16px', + alignItems: 'center', + padding: '0 10px', + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/Chat/svg/trigger-opaque.svg b/front/odiparpack/app/components/Chat/svg/trigger-opaque.svg new file mode 100644 index 0000000..1540f5b --- /dev/null +++ b/front/odiparpack/app/components/Chat/svg/trigger-opaque.svg @@ -0,0 +1,66 @@ +<?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"> +<circle style="fill:#F7B239;" cx="256.004" cy="256.004" r="246.855"/> +<path style="fill:#E09B2D;" d="M126.308,385.694c-88.802-88.802-95.799-228.426-20.999-325.241 + c-8.286,6.401-16.258,13.399-23.858,20.999c-96.401,96.401-96.401,252.698,0,349.099s252.698,96.401,349.099,0 + c7.599-7.599,14.597-15.573,20.999-23.858C354.734,481.492,215.108,474.495,126.308,385.694z"/> +<path style="fill:#4D4D4D;" d="M256.001,184.864L256.001,184.864c14.551,0,29.02-1.978,43.1-5.65 + c53.478-13.946,112.377-5.29,149.086,3.183c19.574,4.519,30.868,25.04,24.163,43.978l-20.669,58.372 + c-4.75,13.414-17.654,22.198-31.877,21.698l-94.167-3.316c-15.87-0.559-29.283-11.933-32.426-27.5l-4.758-23.558 + c-3.119-15.446-16.695-26.551-32.452-26.548l0,0l0,0c-15.759,0-29.333,11.108-32.453,26.554l-4.757,23.552 + c-3.144,15.565-16.557,26.94-32.426,27.5l-94.167,3.316c-14.222,0.501-27.127-8.282-31.876-21.698l-20.669-58.372 + c-6.706-18.937,4.589-39.459,24.163-43.978c36.709-8.472,95.607-17.129,149.086-3.183 + C226.981,182.885,241.449,184.864,256.001,184.864L256.001,184.864z"/> +<path d="M255.999,512C114.841,512,0,397.16,0,256.001S114.841,0,255.999,0C397.159,0,512,114.841,512,256.001 + S397.159,512,255.999,512z M255.999,18.299c-131.068,0-237.7,106.632-237.7,237.702s106.632,237.702,237.7,237.702 + c131.069,0,237.702-106.632,237.702-237.702S387.068,18.299,255.999,18.299z"/> +<path d="M256.269,391.607c-2.716,0-5.443-0.051-8.186-0.155c-42.952-1.624-80.516-15.925-103.062-39.237 + c-3.513-3.633-3.416-9.425,0.216-12.937c3.633-3.515,9.425-3.416,12.937,0.216c40.228,41.595,138.107,45.631,187.018,7.708 + c2.506-1.942,7.259-6.399,8.879-7.952c3.646-3.496,9.437-3.376,12.936,0.272c3.498,3.648,3.376,9.439-0.272,12.936 + c-0.666,0.638-6.616,6.324-10.33,9.204C331.637,380.865,295.496,391.607,256.269,391.607z"/> +<path d="M420.986,315.612c-0.501,0-1-0.009-1.504-0.026l-94.167-3.316c-20.185-0.711-37.075-15.035-41.073-34.832l-4.758-23.558 + c-2.248-11.132-12.123-19.21-23.478-19.21c-0.001,0-0.002,0-0.004,0c-11.361,0-21.238,8.082-23.486,19.215l-4.757,23.553 + c-3.998,19.798-20.888,34.12-41.073,34.832l-94.167,3.316c-18.28,0.615-34.708-10.522-40.823-27.787l-20.669-58.372 + c-4.061-11.469-3.034-24.162,2.817-34.826c5.859-10.681,16.034-18.378,27.913-21.121c40.032-9.239,99.151-17.283,153.454-3.122 + c13.623,3.552,27.347,5.354,40.791,5.354l0,0c13.444,0,27.168-1.802,40.791-5.354c54.302-14.16,113.421-6.118,153.452,3.121 + c11.88,2.742,22.054,10.44,27.913,21.121c5.851,10.665,6.878,23.357,2.817,34.826l-20.669,58.372 + C454.359,304.588,438.678,315.611,420.986,315.612z M150.311,180.969c-23.43,0-51.525,2.745-84.439,10.341 + c-6.805,1.57-12.631,5.978-15.986,12.092c-3.395,6.188-3.966,13.261-1.61,19.916l20.669,58.372 + c3.434,9.697,12.647,15.962,22.929,15.607l94.167-3.316c11.687-0.411,21.465-8.704,23.78-20.166l4.757-23.553 + c3.966-19.639,21.387-33.892,41.422-33.892c0.002,0,0.005,0,0.007,0c20.03,0,37.447,14.25,41.413,33.886l4.758,23.558 + c2.314,11.463,12.093,19.756,23.78,20.167l94.167,3.316c10.294,0.367,19.496-5.909,22.93-15.607l20.669-58.372 + c2.357-6.656,1.785-13.728-1.61-19.916c-3.355-6.114-9.181-10.522-15.986-12.092c-38.033-8.777-94.012-16.469-144.719-3.245 + c-15.131,3.945-30.408,5.946-45.409,5.946c-15,0-30.279-2.001-45.41-5.946C195.972,184.255,175.932,180.969,150.311,180.969z"/> +<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> diff --git a/front/odiparpack/app/components/Chat/svg/trigger-transparent.svg b/front/odiparpack/app/components/Chat/svg/trigger-transparent.svg new file mode 100644 index 0000000..8555ab9 --- /dev/null +++ b/front/odiparpack/app/components/Chat/svg/trigger-transparent.svg @@ -0,0 +1,68 @@ +<?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"> +<g> + <g> + <path d="M255.999,0C114.841,0,0,114.841,0,256.001S114.841,512,255.999,512C397.159,512,512,397.16,512,256.001 + S397.159,0,255.999,0z M255.999,493.702c-131.068,0-237.7-106.632-237.7-237.702s106.632-237.702,237.7-237.702 + c131.069,0,237.702,106.632,237.702,237.702S387.068,493.702,255.999,493.702z"/> + </g> +</g> +<g> + <g> + <path d="M367.008,339.521c-3.499-3.648-9.29-3.768-12.936-0.272c-1.62,1.553-6.373,6.009-8.879,7.952 + c-48.911,37.923-146.79,33.888-187.018-7.708c-3.512-3.632-9.304-3.731-12.937-0.216c-3.632,3.512-3.729,9.304-0.216,12.937 + c22.546,23.312,60.11,37.613,103.062,39.237c2.742,0.104,5.47,0.155,8.186,0.155c39.227,0,75.368-10.742,100.136-29.945 + c3.715-2.88,9.664-8.566,10.33-9.204C370.383,348.96,370.505,343.169,367.008,339.521z"/> + </g> +</g> +<g> + <g> + <path d="M478.16,194.601c-5.859-10.681-16.034-18.378-27.913-21.121c-40.031-9.239-99.151-17.28-153.453-3.121 + c-13.623,3.552-27.347,5.354-40.791,5.354s-27.168-1.802-40.791-5.354c-54.303-14.161-113.421-6.117-153.454,3.122 + c-11.88,2.742-22.054,10.44-27.913,21.121c-5.851,10.664-6.878,23.357-2.817,34.826L51.697,287.8 + c6.114,17.265,22.543,28.401,40.823,27.787l94.167-3.316c20.185-0.711,37.075-15.033,41.073-34.832l4.757-23.553 + c2.248-11.133,12.125-19.215,23.486-19.215c0.001,0,0.002,0,0.004,0c11.355,0,21.229,8.078,23.478,19.21l4.758,23.558 + c3.998,19.797,20.888,34.12,41.073,34.832l94.167,3.316c0.504,0.017,1.003,0.026,1.504,0.026 + c17.692-0.001,33.373-11.023,39.321-27.813l20.669-58.372C485.038,217.958,484.011,205.266,478.16,194.601z M463.725,223.318 + l-20.669,58.372c-3.434,9.697-12.636,15.974-22.93,15.607l-94.167-3.316c-11.687-0.411-21.466-8.704-23.78-20.167l-4.758-23.558 + c-3.966-19.636-21.383-33.886-41.413-33.886c-0.002,0-0.005,0-0.007,0c-20.035,0-37.456,14.254-41.422,33.892l-4.757,23.553 + c-2.315,11.461-12.093,19.754-23.78,20.166l-94.167,3.316c-10.282,0.355-19.495-5.909-22.929-15.607l-20.669-58.372 + c-2.356-6.655-1.785-13.728,1.61-19.915c3.355-6.114,9.181-10.522,15.986-12.092c32.914-7.597,61.009-10.341,84.439-10.341 + c25.621,0,45.661,3.285,60.28,7.096c15.131,3.945,30.409,5.946,45.41,5.946c15,0,30.278-2.001,45.409-5.946 + c50.707-13.224,106.686-5.532,144.719,3.245c6.805,1.57,12.631,5.978,15.986,12.092 + C465.509,209.59,466.081,216.662,463.725,223.318z"/> + </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> +<g> +</g> +</svg> diff --git a/front/odiparpack/app/components/Contact/AddContact.js b/front/odiparpack/app/components/Contact/AddContact.js new file mode 100644 index 0000000..2690f1d --- /dev/null +++ b/front/odiparpack/app/components/Contact/AddContact.js @@ -0,0 +1,82 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Add from '@material-ui/icons/Add'; +import { Tooltip, Fab } from '@material-ui/core'; +import AddContactForm from './AddContactForm'; +import FloatingPanel from '../Panel/FloatingPanel'; +import styles from './contact-jss'; + + +class AddContact extends React.Component { + constructor(props) { + super(props); + this.state = { + img: '', + files: [] + }; + this.onDrop = this.onDrop.bind(this); + } + + onDrop(filesVal) { + const { files } = this.state; + const filesLimit = 1; + let oldFiles = files; + oldFiles = oldFiles.concat(filesVal); + if (oldFiles.length > filesLimit) { + console.log('Cannot upload more than ' + filesLimit + ' items.'); + } else { + this.setState({ img: filesVal[0] }); + } + } + + sendValues = (values) => { + const { submit } = this.props; + const { img } = this.state; + const { avatarInit } = this.props; + const avatar = img === null ? avatarInit : img; + setTimeout(() => { + submit(values, avatar); + this.setState({ img: null }); + }, 500); + } + + render() { + const { + classes, + openForm, + closeForm, + avatarInit, + addContact + } = this.props; + const { img } = this.state; + const branch = ''; + return ( + <div> + <Tooltip title="Add New Contact"> + <Fab color="secondary" onClick={() => addContact()} className={classes.addBtn}> + <Add /> + </Fab> + </Tooltip> + <FloatingPanel openForm={openForm} branch={branch} closeForm={closeForm}> + <AddContactForm + onSubmit={this.sendValues} + onDrop={this.onDrop} + imgAvatar={img === null ? avatarInit : img} + /> + </FloatingPanel> + </div> + ); + } +} + +AddContact.propTypes = { + classes: PropTypes.object.isRequired, + submit: PropTypes.func.isRequired, + addContact: PropTypes.func.isRequired, + openForm: PropTypes.bool.isRequired, + avatarInit: PropTypes.string.isRequired, + closeForm: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(AddContact); diff --git a/front/odiparpack/app/components/Contact/AddContactForm.js b/front/odiparpack/app/components/Contact/AddContactForm.js new file mode 100644 index 0000000..0b51684 --- /dev/null +++ b/front/odiparpack/app/components/Contact/AddContactForm.js @@ -0,0 +1,273 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Dropzone from 'react-dropzone'; +import { withStyles } from '@material-ui/core/styles'; +import Type from 'ba-styles/Typography.scss'; +import PhotoCamera from '@material-ui/icons/PhotoCamera'; +import { connect } from 'react-redux'; +import { reduxForm, Field } from 'redux-form/immutable'; +import PermContactCalendar from '@material-ui/icons/PermContactCalendar'; +import Bookmark from '@material-ui/icons/Bookmark'; +import LocalPhone from '@material-ui/icons/LocalPhone'; +import Email from '@material-ui/icons/Email'; +import Smartphone from '@material-ui/icons/Smartphone'; +import LocationOn from '@material-ui/icons/LocationOn'; +import Work from '@material-ui/icons/Work'; +import Language from '@material-ui/icons/Language'; +import css from 'ba-styles/Form.scss'; +import { Button, Avatar, IconButton, Typography, Tooltip, InputAdornment } from '@material-ui/core'; +import { TextFieldRedux } from '../Forms/ReduxFormMUI'; +import styles from './contact-jss'; + + +// validation functions +const required = value => (value == null ? 'Required' : undefined); +const email = value => ( + value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) + ? 'Invalid email' + : undefined +); + +class AddContactForm extends React.Component { + saveRef = ref => { + this.ref = ref; + return this.ref; + }; + + render() { + const { + classes, + reset, + pristine, + submitting, + handleSubmit, + onDrop, + imgAvatar + } = this.props; + let dropzoneRef; + const acceptedFiles = ['image/jpeg', 'image/png', 'image/bmp']; + const fileSizeLimit = 300000; + const imgPreview = img => { + if (typeof img !== 'string' && img !== '') { + return URL.createObjectURL(imgAvatar); + } + return img; + }; + return ( + <div> + <form onSubmit={handleSubmit}> + <section className={css.bodyForm}> + <div> + <Typography variant="button" component="p" className={Type.textCenter}>Upload Avatar</Typography> + <Dropzone + className={classes.hiddenDropzone} + accept={acceptedFiles.join(',')} + acceptClassName="stripes" + onDrop={onDrop} + maxSize={fileSizeLimit} + ref={(node) => { dropzoneRef = node; }} + > + {({ getRootProps, getInputProps }) => ( + <div {...getRootProps()}> + <input {...getInputProps()} /> + </div> + )} + </Dropzone> + <div className={classes.avatarWrap}> + <Avatar + alt="John Doe" + className={classes.uploadAvatar} + src={imgPreview(imgAvatar)} + /> + <Tooltip id="tooltip-upload" title="Upload Photo"> + <IconButton + className={classes.buttonUpload} + component="button" + onClick={() => { + dropzoneRef.open(); + }} + > + <PhotoCamera /> + </IconButton> + </Tooltip> + </div> + </div> + <div> + <Field + name="name" + component={TextFieldRedux} + placeholder="Name" + label="Name" + validate={required} + required + ref={this.saveRef} + className={classes.field} + InputProps={{ + startAdornment: ( + <InputAdornment position="start"> + <PermContactCalendar /> + </InputAdornment> + ) + }} + /> + </div> + <div> + <Field + name="title" + component={TextFieldRedux} + placeholder="Title" + label="Title" + className={classes.field} + InputProps={{ + startAdornment: ( + <InputAdornment position="start"> + <Bookmark /> + </InputAdornment> + ) + }} + /> + </div> + <div> + <Field + name="phone" + component={TextFieldRedux} + placeholder="Phone" + type="tel" + label="Phone" + className={classes.field} + InputProps={{ + startAdornment: ( + <InputAdornment position="start"> + <LocalPhone /> + </InputAdornment> + ) + }} + /> + </div> + <div> + <Field + name="secondaryPhone" + component={TextFieldRedux} + placeholder="Secondary Phone" + type="tel" + label="Secondary Phone" + className={classes.field} + InputProps={{ + startAdornment: ( + <InputAdornment position="start"> + <Smartphone /> + </InputAdornment> + ) + }} + /> + </div> + <div> + <Field + name="personalEmail" + component={TextFieldRedux} + placeholder="Personal Email" + type="email" + validate={email} + label="Personal Email" + className={classes.field} + InputProps={{ + startAdornment: ( + <InputAdornment position="start"> + <Email /> + </InputAdornment> + ) + }} + /> + </div> + <div> + <Field + name="companyEmail" + component={TextFieldRedux} + placeholder="Company Email" + type="email" + validate={email} + label="Company Email" + className={classes.field} + InputProps={{ + startAdornment: ( + <InputAdornment position="start"> + <Work /> + </InputAdornment> + ) + }} + /> + </div> + <div> + <Field + name="address" + component={TextFieldRedux} + placeholder="Address" + label="Address" + className={classes.field} + InputProps={{ + startAdornment: ( + <InputAdornment position="start"> + <LocationOn /> + </InputAdornment> + ) + }} + /> + </div> + <div> + <Field + name="website" + component={TextFieldRedux} + placeholder="Website" + type="url" + label="Website" + className={classes.field} + InputProps={{ + startAdornment: ( + <InputAdornment position="start"> + <Language /> + </InputAdornment> + ) + }} + /> + </div> + </section> + <div className={css.buttonArea}> + <Button variant="contained" color="secondary" type="submit" disabled={submitting}> + Submit + </Button> + <Button + type="button" + disabled={pristine || submitting} + onClick={reset} + > + Reset + </Button> + </div> + </form> + </div> + ); + } +} + +AddContactForm.propTypes = { + classes: PropTypes.object.isRequired, + handleSubmit: PropTypes.func.isRequired, + reset: PropTypes.func.isRequired, + onDrop: PropTypes.func.isRequired, + pristine: PropTypes.bool.isRequired, + submitting: PropTypes.bool.isRequired, + imgAvatar: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, +}; + +const AddContactFormRedux = reduxForm({ + form: 'immutableAddContact', + enableReinitialize: true, +})(AddContactForm); + +const AddContactInit = connect( + state => ({ + initialValues: state.getIn(['contact', 'formValues']) + }) +)(AddContactFormRedux); + +export default withStyles(styles)(AddContactInit); diff --git a/front/odiparpack/app/components/Contact/ContactDetail.js b/front/odiparpack/app/components/Contact/ContactDetail.js new file mode 100644 index 0000000..f6d2dfc --- /dev/null +++ b/front/odiparpack/app/components/Contact/ContactDetail.js @@ -0,0 +1,195 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import Edit from '@material-ui/icons/Edit'; +import Star from '@material-ui/icons/Star'; +import StarBorder from '@material-ui/icons/StarBorder'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import LocalPhone from '@material-ui/icons/LocalPhone'; +import Email from '@material-ui/icons/Email'; +import Smartphone from '@material-ui/icons/Smartphone'; +import LocationOn from '@material-ui/icons/LocationOn'; +import Work from '@material-ui/icons/Work'; +import Language from '@material-ui/icons/Language'; +import { + List, + ListItem, + ListItemText, + ListItemAvatar, + Avatar, + Menu, + MenuItem, + IconButton, + Typography, + Divider, +} from '@material-ui/core'; +import styles from './contact-jss'; + + +const optionsOpt = [ + 'Block Contact', + 'Delete Contact', + 'Option 1', + 'Option 2', + 'Option 3', +]; + +const ITEM_HEIGHT = 48; + +class ContactDetail extends React.Component { + state = { + anchorElOpt: null, + }; + + handleClickOpt = event => { + this.setState({ anchorElOpt: event.currentTarget }); + }; + + handleCloseOpt = () => { + this.setState({ anchorElOpt: null }); + }; + + deleteContact = (item) => { + this.props.remove(item); + this.setState({ anchorElOpt: null }); + } + + render() { + const { + classes, + dataContact, + itemSelected, + edit, + favorite, + showMobileDetail + } = this.props; + const { anchorElOpt } = this.state; + return ( + <main className={classNames(classes.content, showMobileDetail ? classes.detailPopup : '')}> + <section className={classes.cover}> + <div className={classes.opt}> + <IconButton className={classes.favorite} aria-label="Favorite" onClick={() => favorite(dataContact.get(itemSelected))}> + {dataContact.getIn([itemSelected, 'favorited']) ? (<Star />) : <StarBorder />} + </IconButton> + <IconButton aria-label="Edit" onClick={() => edit(dataContact.get(itemSelected))}> + <Edit /> + </IconButton> + <IconButton + aria-label="More" + aria-owns={anchorElOpt ? 'long-menu' : null} + aria-haspopup="true" + className={classes.button} + onClick={this.handleClickOpt} + > + <MoreVertIcon /> + </IconButton> + <Menu + id="long-menu" + anchorEl={anchorElOpt} + open={Boolean(anchorElOpt)} + onClose={this.handleCloseOpt} + PaperProps={{ + style: { + maxHeight: ITEM_HEIGHT * 4.5, + width: 200, + }, + }} + > + {optionsOpt.map(option => { + if (option === 'Delete Contact') { + return ( + <MenuItem key={option} selected={option === 'Edit Profile'} onClick={() => this.deleteContact(dataContact.get(itemSelected))}> + {option} + </MenuItem> + ); + } + return ( + <MenuItem key={option} selected={option === 'Edit Profile'} onClick={this.handleCloseOpt}> + {option} + </MenuItem> + ); + })} + </Menu> + </div> + <Avatar alt={dataContact.getIn([itemSelected, 'name'])} src={dataContact.getIn([itemSelected, 'avatar'])} className={classes.avatar} /> + <Typography className={classes.userName} variant="h4"> + {dataContact.getIn([itemSelected, 'name'])} + <Typography variant="caption" display="block"> + {dataContact.getIn([itemSelected, 'title'])} + </Typography> + </Typography> + </section> + <div> + <List> + <ListItem> + <ListItemAvatar> + <Avatar className={classes.blueIcon}> + <LocalPhone /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={dataContact.getIn([itemSelected, 'phone'])} secondary="Phone" /> + </ListItem> + <Divider variant="inset" /> + <ListItem> + <ListItemAvatar> + <Avatar className={classes.amberIcon}> + <Smartphone /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={dataContact.getIn([itemSelected, 'secondaryPhone'])} secondary="Secondary Phone" /> + </ListItem> + <Divider variant="inset" /> + <ListItem> + <ListItemAvatar> + <Avatar className={classes.tealIcon}> + <Email /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={dataContact.getIn([itemSelected, 'personalEmail'])} secondary="Personal Email" /> + </ListItem> + <Divider variant="inset" /> + <ListItem> + <ListItemAvatar> + <Avatar className={classes.brownIcon}> + <Work /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={dataContact.getIn([itemSelected, 'companyEmail'])} secondary="Company Email" /> + </ListItem> + <Divider variant="inset" /> + <ListItem> + <ListItemAvatar> + <Avatar className={classes.redIcon}> + <LocationOn /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={dataContact.getIn([itemSelected, 'address'])} secondary="Address" /> + </ListItem> + <Divider variant="inset" /> + <ListItem> + <ListItemAvatar> + <Avatar className={classes.purpleIcon}> + <Language /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={dataContact.getIn([itemSelected, 'website'])} secondary="Website" /> + </ListItem> + </List> + </div> + </main> + ); + } +} + +ContactDetail.propTypes = { + classes: PropTypes.object.isRequired, + showMobileDetail: PropTypes.bool.isRequired, + dataContact: PropTypes.object.isRequired, + itemSelected: PropTypes.number.isRequired, + edit: PropTypes.func.isRequired, + remove: PropTypes.func.isRequired, + favorite: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(ContactDetail); diff --git a/front/odiparpack/app/components/Contact/ContactHeader.js b/front/odiparpack/app/components/Contact/ContactHeader.js new file mode 100644 index 0000000..f17a1a4 --- /dev/null +++ b/front/odiparpack/app/components/Contact/ContactHeader.js @@ -0,0 +1,62 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import ArrowBack from '@material-ui/icons/ArrowBack'; +import PermContactCalendar from '@material-ui/icons/PermContactCalendar'; +import Add from '@material-ui/icons/Add'; +import { AppBar, Toolbar, Typography, Button, IconButton } from '@material-ui/core'; +import styles from './contact-jss'; + + +class ContactHeader extends React.Component { + render() { + const { + classes, + addContact, + total, + hideDetail, + showMobileDetail + } = this.props; + return ( + <AppBar + position="absolute" + className={classes.appBar} + > + <Toolbar> + {showMobileDetail && ( + <IconButton + color="inherit" + aria-label="open drawer" + onClick={() => hideDetail()} + className={classes.navIconHide} + > + <ArrowBack /> + </IconButton> + )} + <Typography variant="subtitle1" className={classes.title} color="inherit" noWrap> + <PermContactCalendar /> + {' '} +Contacts ( + {total} +) + </Typography> + <Button onClick={() => addContact()} variant="outlined" color="inherit" className={classes.button}> + <Add /> + {' '} +Add New + </Button> + </Toolbar> + </AppBar> + ); + } +} + +ContactHeader.propTypes = { + classes: PropTypes.object.isRequired, + showMobileDetail: PropTypes.bool.isRequired, + addContact: PropTypes.func.isRequired, + hideDetail: PropTypes.func.isRequired, + total: PropTypes.number.isRequired, +}; + +export default withStyles(styles)(ContactHeader); diff --git a/front/odiparpack/app/components/Contact/ContactList.js b/front/odiparpack/app/components/Contact/ContactList.js new file mode 100644 index 0000000..545687d --- /dev/null +++ b/front/odiparpack/app/components/Contact/ContactList.js @@ -0,0 +1,112 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import SearchIcon from '@material-ui/icons/Search'; +import PermContactCalendar from '@material-ui/icons/PermContactCalendar'; +import Star from '@material-ui/icons/Star'; +import { + Drawer, + Divider, + List, + ListItem, + ListItemText, + ListItemAvatar, + Avatar, + BottomNavigation, + BottomNavigationAction, +} from '@material-ui/core'; +import styles from './contact-jss'; + + +class ContactList extends React.Component { + state = { + filter: 'all', + }; + + handleChange = (event, value) => { + this.setState({ filter: value }); + }; + + render() { + const { + classes, + dataContact, + itemSelected, + showDetail, + search, + keyword, + clippedRight + } = this.props; + const { filter } = this.state; + const favoriteData = dataContact.filter(item => item.get('favorited') === true); + const getItem = dataArray => dataArray.map(data => { + const index = dataContact.indexOf(data); + if (data.get('name').toLowerCase().indexOf(keyword) === -1) { + return false; + } + return ( + <ListItem + button + key={data.get('id')} + className={index === itemSelected ? classes.selected : ''} + onClick={() => showDetail(data)} + > + <ListItemAvatar> + <Avatar alt={data.get('name')} src={data.get('avatar')} className={classes.avatar} /> + </ListItemAvatar> + <ListItemText primary={data.get('name')} secondary={data.get('title')} /> + </ListItem> + ); + }); + return ( + <Fragment> + <Drawer + variant="permanent" + anchor="left" + open + classes={{ + paper: classes.drawerPaper, + }} + > + <div> + <div className={classNames(classes.toolbar, clippedRight && classes.clippedRight)}> + <div className={classes.flex}> + <div className={classes.searchWrapper}> + <div className={classes.search}> + <SearchIcon /> + </div> + <input className={classes.input} onChange={(event) => search(event)} placeholder="Search Contact" /> + </div> + </div> + </div> + <Divider /> + <List> + {filter === 'all' ? getItem(dataContact) : getItem(favoriteData)} + </List> + </div> + </Drawer> + <BottomNavigation value={filter} onChange={this.handleChange} className={classes.bottomFilter}> + <BottomNavigationAction label="All" value="all" icon={<PermContactCalendar />} /> + <BottomNavigationAction label="Favorites" value="favorites" icon={<Star />} /> + </BottomNavigation> + </Fragment> + ); + } +} + +ContactList.propTypes = { + classes: PropTypes.object.isRequired, + dataContact: PropTypes.object.isRequired, + keyword: PropTypes.string.isRequired, + itemSelected: PropTypes.number.isRequired, + showDetail: PropTypes.func.isRequired, + search: PropTypes.func.isRequired, + clippedRight: PropTypes.bool, +}; + +ContactList.defaultProps = { + clippedRight: false +}; + +export default withStyles(styles)(ContactList); diff --git a/front/odiparpack/app/components/Contact/contact-jss.js b/front/odiparpack/app/components/Contact/contact-jss.js new file mode 100644 index 0000000..6745527 --- /dev/null +++ b/front/odiparpack/app/components/Contact/contact-jss.js @@ -0,0 +1,282 @@ +import { amber, blue, deepPurple as purple, teal, brown, red } from '@material-ui/core/colors'; + +const drawerWidth = 240; +const drawerHeight = 630; + +const styles = theme => ({ + root: { + flexGrow: 1, + height: drawerHeight, + zIndex: 1, + overflow: 'hidden', + position: 'relative', + [theme.breakpoints.up('sm')]: { + display: 'flex', + }, + borderRadius: 2, + boxShadow: theme.shadows[2] + }, + addBtn: { + position: 'fixed', + bottom: 30, + right: 30, + zIndex: 100 + }, + appBar: { + zIndex: theme.zIndex.drawer + 1, + background: theme.palette.secondary.main, + height: 64, + display: 'flex', + justifyContent: 'center', + '& $avatar': { + marginRight: 10 + }, + '& h2': { + flex: 1 + }, + '& $button': { + color: theme.palette.common.white + } + }, + button: { + [theme.breakpoints.down('sm')]: { + display: 'none' + }, + }, + online: { + background: '#CDDC39' + }, + bussy: { + background: '#EF5350' + }, + idle: { + background: '#FFC107' + }, + offline: { + background: '#9E9E9E' + }, + status: { + padding: '2px 6px', + '& span': { + borderRadius: '50%', + display: 'inline-block', + marginRight: 2, + width: 10, + height: 10, + border: `1px solid ${theme.palette.common.white}` + } + }, + appBarShift: { + marginLeft: 0, + width: '100%', + transition: theme.transitions.create(['width', 'margin'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + [theme.breakpoints.up('md')]: { + marginLeft: drawerWidth, + width: `calc(100% - ${drawerWidth}px)`, + }, + }, + drawerPaper: { + [theme.breakpoints.up('sm')]: { + width: drawerWidth, + }, + position: 'relative', + paddingBottom: 65, + height: drawerHeight, + }, + clippedRight: {}, + toolbar: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + padding: 8, + position: 'relative', + '&$clippedRight': { + marginTop: 66 + } + }, + content: { + flexGrow: 1, + paddingTop: 64, + backgroundColor: theme.palette.background.paper, + }, + detailPopup: { + [theme.breakpoints.down('xs')]: { + position: 'absolute', + top: 0, + left: 0, + zIndex: 1200, + width: '100%', + overflow: 'auto', + height: 'calc(100% - 50px)' + } + }, + title: { + display: 'flex', + flex: 1, + '& svg': { + marginRight: 5 + } + }, + flex: { + flex: 1, + }, + searchWrapper: { + fontFamily: theme.typography.fontFamily, + position: 'relative', + borderRadius: 2, + display: 'block', + background: theme.palette.grey[100] + }, + search: { + width: 'auto', + height: '100%', + top: 0, + left: 20, + position: 'absolute', + pointerEvents: 'none', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + input: { + font: 'inherit', + padding: `${theme.spacing(1)}px ${theme.spacing(1)}px ${theme.spacing(1)}px ${theme.spacing(6)}px`, + border: 0, + display: 'block', + verticalAlign: 'middle', + whiteSpace: 'normal', + background: 'none', + margin: 0, // Reset for Safari + color: 'inherit', + width: '100%', + '&:focus': { + outline: 0, + }, + }, + bottomFilter: { + position: 'absolute', + width: '100%', + [theme.breakpoints.up('sm')]: { + width: 240, + }, + zIndex: 2000, + bottom: 0, + left: 0, + background: theme.palette.grey[100], + borderTop: `1px solid ${theme.palette.grey[300]}`, + borderRight: `1px solid ${theme.palette.grey[300]}`, + }, + avatar: {}, + userName: { + textAlign: 'left' + }, + cover: { + padding: 20, + height: 130, + position: 'relative', + background: theme.palette.primary.light, + width: '100%', + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + '& $avatar': { + boxShadow: theme.shadows[4], + width: 100, + height: 100, + marginRight: 20 + }, + }, + opt: { + position: 'absolute', + top: 10, + right: 10, + }, + favorite: { + color: amber[500] + }, + redIcon: { + background: red[50], + '& svg': { + color: red[500] + } + }, + brownIcon: { + background: brown[50], + '& svg': { + color: brown[500] + } + }, + tealIcon: { + background: teal[50], + '& svg': { + color: teal[500] + } + }, + blueIcon: { + background: blue[50], + '& svg': { + color: blue[500] + } + }, + amberIcon: { + background: amber[50], + '& svg': { + color: amber[500] + } + }, + purpleIcon: { + background: purple[50], + '& svg': { + color: purple[500] + } + }, + field: { + width: '100%', + marginBottom: 20, + '& svg': { + color: theme.palette.grey[400], + fontSize: 18, + } + }, + uploadAvatar: { + width: '100%', + height: '100%', + background: theme.palette.grey[200], + boxShadow: theme.shadows[4], + }, + selected: { + background: theme.palette.secondary.light, + borderLeft: `2px solid ${theme.palette.secondary.main}`, + paddingLeft: 22, + '& h3': { + color: theme.palette.secondary.dark + } + }, + hiddenDropzone: { + display: 'none' + }, + avatarWrap: { + width: 100, + height: 100, + margin: '10px auto 30px', + position: 'relative' + }, + buttonUpload: { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)' + }, + navIconHide: { + marginRight: theme.spacing(1), + [theme.breakpoints.up('sm')]: { + display: 'none' + } + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/Counter/CounterWidget.js b/front/odiparpack/app/components/Counter/CounterWidget.js new file mode 100644 index 0000000..f22bfb8 --- /dev/null +++ b/front/odiparpack/app/components/Counter/CounterWidget.js @@ -0,0 +1,80 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import CountUp from 'react-countup'; +import { withStyles } from '@material-ui/core/styles'; +import { Typography, Paper } from '@material-ui/core'; + +const styles = theme => ({ + root: { + flexGrow: 1, + justifyContent: 'space-between', + alignItems: 'flex-start', + padding: 10, + height: 190, + marginBottom: 6, + display: 'flex', + [theme.breakpoints.up('sm')]: { + height: 120, + marginBottom: -1, + alignItems: 'flex-end', + }, + [theme.breakpoints.down('xs')]: { + flexDirection: 'column', + }, + '& > *': { + padding: '0 5px' + } + }, + title: { + color: theme.palette.common.white, + fontSize: 16, + fontWeight: 400 + }, + counter: { + color: theme.palette.common.white, + fontSize: 28, + fontWeight: 500 + }, + customContent: { + textAlign: 'right' + } +}); + +class CounterWidget extends PureComponent { + render() { + const { + classes, + color, + start, + end, + duration, + title, + children + } = this.props; + return ( + <Paper className={classes.root} style={{ backgroundColor: color }}> + <div> + <Typography className={classes.counter}> + <CountUp start={start} end={end} duration={duration} useEasing /> + </Typography> + <Typography className={classes.title} variant="subtitle1">{title}</Typography> + </div> + <div className={classes.customContent}> + {children} + </div> + </Paper> + ); + } +} + +CounterWidget.propTypes = { + classes: PropTypes.object.isRequired, + color: PropTypes.string.isRequired, + start: PropTypes.number.isRequired, + end: PropTypes.number.isRequired, + duration: PropTypes.number.isRequired, + title: PropTypes.string.isRequired, + children: PropTypes.node.isRequired, +}; + +export default withStyles(styles)(CounterWidget); diff --git a/front/odiparpack/app/components/Divider/divider-jss.js b/front/odiparpack/app/components/Divider/divider-jss.js new file mode 100644 index 0000000..e813b8d --- /dev/null +++ b/front/odiparpack/app/components/Divider/divider-jss.js @@ -0,0 +1,70 @@ +const space = { + margin: '40px 0' +}; +const styles = theme => ({ + gradient: { + extend: space, + border: 0, + height: 1, + background: '#333', + backgroundImage: 'linear-gradient(to right, #fff, #8c8c8c, #fff)' + }, + colorDash: { + border: 0, + extend: space, + borderBottom: `1px dashed ${theme.palette.grey[100]}`, + background: '#999' + }, + shadow: { + height: 12, + extend: space, + border: 0, + boxShadow: 'inset 0 12px 12px -12px rgba(0, 0, 0, 0.5)' + }, + inset: { + border: 0, + extend: space, + height: 0, + borderTop: '1px solid rgba(0, 0, 0, 0.1)', + borderBottom: '1px solid rgba(255, 255, 255, 0.3)' + }, + flairedEdges: { + overflow: 'visible', /* For IE */ + extend: space, + height: 30, + borderStyle: 'solid', + borderColor: theme.palette.grey[400], + borderWidth: '1px 0 0 0', + borderRadius: 20, + '&:before': { + display: 'block', + content: '""', + height: 30, + marginTop: -31, + borderStyle: 'solid', + borderColor: theme.palette.grey[400], + borderWidth: '0 0 1px 0', + borderRadius: 20 + } + }, + content: { + overflow: 'visible', /* For IE */ + extend: space, + padding: 0, + border: 'none', + borderTop: `1px solid ${theme.palette.grey[400]}`, + color: '#333', + textAlign: 'center', + '&:after': { + content: 'attr(data-content)', + display: 'inline-block', + position: 'relative', + top: -15, + fontSize: 14, + padding: '0 0.25em', + background: '#FFF' + } + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/Divider/index.js b/front/odiparpack/app/components/Divider/index.js new file mode 100644 index 0000000..8e5c010 --- /dev/null +++ b/front/odiparpack/app/components/Divider/index.js @@ -0,0 +1,148 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import styles from './divider-jss'; + +/* Gradient Divider */ +const Gradient = props => { + const { + thin, + classes, + ...rest + } = props; + return ( + <hr className={classes.gradient} style={{ height: `${thin}` }} {...rest} /> + ); +}; + +Gradient.propTypes = { + thin: PropTypes.number, + classes: PropTypes.object.isRequired, +}; + +Gradient.defaultProps = { + thin: 1 +}; + +export const GradientDivider = withStyles(styles)(Gradient); + +/* Dash Divider */ + +const Dash = props => { + const { + thin, + classes, + ...rest + } = props; + return ( + <hr className={classes.colorDash} style={{ height: `${thin}` }} {...rest} /> + ); +}; + +Dash.propTypes = { + classes: PropTypes.object.isRequired, + thin: PropTypes.number, +}; + +Dash.defaultProps = { + thin: 1 +}; + +export const DashDivider = withStyles(styles)(Dash); + +/* Shadow Divider */ + +const Shadow = props => { + const { + classes, + thin, + ...rest + } = props; + return ( + <hr className={classes.shadow} style={{ height: `${thin}` }} {...rest} /> + ); +}; + +Shadow.propTypes = { + classes: PropTypes.object.isRequired, + thin: PropTypes.number, +}; + +Shadow.defaultProps = { + thin: 1 +}; + +export const ShadowDivider = withStyles(styles)(Shadow); + +/* Shadow Inset */ + +const Inset = props => { + const { + classes, + thin, + ...rest + } = props; + return ( + <hr className={classes.inset} style={{ height: `${thin}` }} {...rest} /> + ); +}; + +Inset.propTypes = { + classes: PropTypes.object.isRequired, + thin: PropTypes.number, +}; + +Inset.defaultProps = { + thin: 1 +}; + +export const InsetDivider = withStyles(styles)(Inset); + +/* Shadow FlairedEdges */ + +export const FlairedEdges = props => { + const { + classes, + thin, + ...rest + } = props; + return ( + <hr className={classes.flairedEdges} style={{ height: `${thin}` }} {...rest} /> + ); +}; + +FlairedEdges.propTypes = { + classes: PropTypes.object.isRequired, + thin: PropTypes.number, +}; + +FlairedEdges.defaultProps = { + thin: 1 +}; + +export const FlairedEdgesDivider = withStyles(styles)(FlairedEdges); + + +export const Content = props => { + const { + classes, + thin, + content, + ...rest + } = props; + return ( + <hr className={classes.content} style={{ height: `${thin}` }} data-content={content} {...rest} /> + ); +}; + +Content.propTypes = { + classes: PropTypes.object.isRequired, + thin: PropTypes.number, + content: PropTypes.string.isRequired, +}; + +Content.defaultProps = { + thin: 1 +}; + +export const ContentDivider = withStyles(styles)(Content); diff --git a/front/odiparpack/app/components/Email/ComposeEmail.js b/front/odiparpack/app/components/Email/ComposeEmail.js new file mode 100644 index 0000000..8d06ebd --- /dev/null +++ b/front/odiparpack/app/components/Email/ComposeEmail.js @@ -0,0 +1,65 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Add from '@material-ui/icons/Add'; +import { Fab, Tooltip } from '@material-ui/core'; +import ComposeEmailForm from './ComposeEmailForm'; +import FloatingPanel from '../Panel/FloatingPanel'; +import styles from './email-jss'; + + +class ComposeEmail extends React.Component { + render() { + const { + classes, + open, + closeForm, + sendEmail, + to, + subject, + validMail, + inputChange, + compose + } = this.props; + const branch = ''; + return ( + <div> + <Tooltip title="Compose Email"> + <Fab color="secondary" onClick={() => compose()} className={classes.addBtn}> + <Add /> + </Fab> + </Tooltip> + <FloatingPanel + openForm={open} + branch={branch} + closeForm={closeForm} + title="Compose Email" + extraSize + > + <ComposeEmailForm + to={to} + subject={subject} + validMail={validMail} + sendEmail={sendEmail} + closeForm={closeForm} + inputChange={inputChange} + /> + </FloatingPanel> + </div> + ); + } +} + +ComposeEmail.propTypes = { + classes: PropTypes.object.isRequired, + open: PropTypes.bool.isRequired, + to: PropTypes.string.isRequired, + subject: PropTypes.string.isRequired, + validMail: PropTypes.string.isRequired, + compose: PropTypes.func.isRequired, + closeForm: PropTypes.func.isRequired, + sendEmail: PropTypes.func.isRequired, + inputChange: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(ComposeEmail); diff --git a/front/odiparpack/app/components/Email/ComposeEmailForm.js b/front/odiparpack/app/components/Email/ComposeEmailForm.js new file mode 100644 index 0000000..3620d4d --- /dev/null +++ b/front/odiparpack/app/components/Email/ComposeEmailForm.js @@ -0,0 +1,262 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Editor } from 'react-draft-wysiwyg'; +import { convertFromRaw, EditorState, convertToRaw } from 'draft-js'; +import draftToHtml from 'draftjs-to-html'; +import Dropzone from 'react-dropzone'; +import { withStyles } from '@material-ui/core/styles'; +import TextField from '@material-ui/core/TextField'; +import Attachment from '@material-ui/icons/Attachment'; +import FileIcon from '@material-ui/icons/Description'; +import ActionDelete from '@material-ui/icons/Delete'; +import Send from '@material-ui/icons/Send'; +import EditorStyle from 'ba-styles/TextEditor.scss'; +import css from 'ba-styles/Form.scss'; +import 'ba-styles/vendors/react-draft-wysiwyg/react-draft-wysiwyg.css'; +import { Button, Grid, Typography, Snackbar, IconButton } from '@material-ui/core'; +import isImage from '../Forms/helpers/helpers.js'; +import styles from './email-jss'; + + +const content = { + blocks: [{ + key: '637gr', + text: 'Lorem ipsum dolor sit amet 😀', + type: 'unstyled', + depth: 0, + inlineStyleRanges: [], + entityRanges: [], + data: {} + }], + entityMap: {} +}; + +class ComposeEmailForm extends React.Component { + constructor(props) { + super(props); + const contentBlock = convertFromRaw(content); + if (contentBlock) { + const editorState = EditorState.createWithContent(contentBlock); + this.state = { + openSnackBar: false, + errorMessage: '', + files: [], + editorState, + emailContent: draftToHtml(convertToRaw(editorState.getCurrentContent())), + }; + } + this.onDrop = this.onDrop.bind(this); + } + + onDrop(filesVal) { + const { files } = this.state; + let oldFiles = files; + const filesLimit = 3; + oldFiles = oldFiles.concat(filesVal); + if (oldFiles.length > filesLimit) { + console.log('Cannot upload more than ' + filesLimit + ' items.'); + } else { + this.setState({ files: oldFiles }); + } + } + + onEditorStateChange = editorState => { + this.setState({ + editorState, + emailContent: draftToHtml(convertToRaw(editorState.getCurrentContent())) + }); + }; + + onDropRejected() { + this.setState({ + openSnackBar: true, + errorMessage: 'File too big, max size is 3MB', + }); + } + + handleRequestCloseSnackBar = () => { + this.setState({ + openSnackBar: false, + }); + }; + + handleRemove(file, fileIndex) { + const thisFiles = this.state.files; + // This is to prevent memory leaks. + window.URL.revokeObjectURL(file.preview); + + thisFiles.splice(fileIndex, 1); + this.setState({ files: thisFiles }); + } + + handleSend = (to, subject, emailContent, files) => { + this.props.sendEmail(to, subject, emailContent, files); + this.setState({ emailContent: '', files: [] }); + }; + + render() { + const { + classes, + closeForm, + to, + subject, + validMail, + inputChange + } = this.props; + const { + editorState, + emailContent, + files, + openSnackBar, + errorMessage, + } = this.state; + let dropzoneRef; + const deleteBtn = (file, index) => ( + <div className="middle"> + <IconButton onClick={() => this.handleRemove(file, index)}> + <ActionDelete className="removeBtn" /> + </IconButton> + </div> + ); + const previews = filesArray => filesArray.map((file, index) => { + if (isImage(file)) { + const base64Img = URL.createObjectURL(file); + return ( + <div key={index.toString()} className={classes.item}> + <div className="imageContainer col fileIconImg"> + <figure className="imgWrap"><img className="smallPreviewImg" src={base64Img} alt="preview" /></figure> + {deleteBtn(file, index)} + </div> + <Typography noWrap variant="caption">{file.name}</Typography> + </div> + ); + } + return ( + <div key={index.toString()} className={classes.item}> + <div className="imageContainer col fileIconImg"> + <div className="fileWrap"> + <FileIcon className="smallPreviewImg" alt="preview" /> + {deleteBtn(file, index)} + </div> + </div> + <Typography noWrap variant="caption">{file.name}</Typography> + </div> + ); + }); + const fileSizeLimit = 3000000; + return ( + <div> + <form> + <section className={css.bodyForm}> + <div> + <TextField + error={validMail === 'Invalid email'} + id="to" + label="To" + helperText={validMail} + className={classes.field} + type="email" + placeholder="To" + value={to} + onChange={(event) => inputChange(event, 'to')} + margin="normal" + /> + </div> + <div> + <TextField + id="subject" + label="Subject" + className={classes.field} + placeholder="Subject" + value={subject} + onChange={(event) => inputChange(event, 'subject')} + margin="normal" + /> + </div> + <Grid container alignItems="center"> + <Dropzone + className={classes.hiddenDropzone} + acceptClassName="stripes" + onDrop={this.onDrop} + maxSize={fileSizeLimit} + ref={(node) => { dropzoneRef = node; }} + > + {({ getRootProps, getInputProps }) => ( + <div {...getRootProps()}> + <input {...getInputProps()} /> + </div> + )} + </Dropzone> + <Button + className={classes.buttonUpload} + color="secondary" + component="button" + onClick={() => { + dropzoneRef.open(); + }} + > + <Attachment /> + Attach Files + </Button> + + <Typography variant="caption">(Max 3MB)</Typography> + </Grid> + <div className={classes.preview}> + {previews(files)} + </div> + <div> + <Editor + editorState={editorState} + editorClassName={EditorStyle.TextEditor} + toolbarClassName={EditorStyle.ToolbarEditor} + onEditorStateChange={this.onEditorStateChange} + toolbar={{ + options: ['inline', 'fontSize', 'fontFamily', 'colorPicker', 'image', 'emoji', 'list', 'textAlign', 'link'], + inline: { inDropdown: true }, + color: true, + list: { inDropdown: true }, + textAlign: { inDropdown: true }, + link: { inDropdown: true }, + }} + /> + </div> + </section> + <div className={css.buttonArea}> + <Button type="button" onClick={() => closeForm()}> + Discard + </Button> + <Button + variant="contained" + color="secondary" + type="button" + disabled={!to || !subject} + onClick={() => this.handleSend(to, subject, emailContent, files)} + > + Send + {' '} + <Send className={classes.sendIcon} /> + </Button> + </div> + </form> + <Snackbar + open={openSnackBar} + message={errorMessage} + autoHideDuration={4000} + onClose={this.handleRequestCloseSnackBar} + /> + </div> + ); + } +} + +ComposeEmailForm.propTypes = { + classes: PropTypes.object.isRequired, + to: PropTypes.string.isRequired, + subject: PropTypes.string.isRequired, + validMail: PropTypes.string.isRequired, + sendEmail: PropTypes.func.isRequired, + closeForm: PropTypes.func.isRequired, + inputChange: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(ComposeEmailForm); diff --git a/front/odiparpack/app/components/Email/EmailHeader.js b/front/odiparpack/app/components/Email/EmailHeader.js new file mode 100644 index 0000000..eceb757 --- /dev/null +++ b/front/odiparpack/app/components/Email/EmailHeader.js @@ -0,0 +1,49 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import SearchIcon from '@material-ui/icons/Search'; +import MenuIcon from '@material-ui/icons/Menu'; +import { AppBar, Hidden, Toolbar, Typography, IconButton } from '@material-ui/core'; +import styles from './email-jss'; + + +class EmailHeader extends React.Component { + render() { + const { classes, search, handleDrawerToggle } = this.props; + return ( + <AppBar position="absolute" className={classes.appBar}> + <Toolbar> + <Hidden smDown> + <Typography variant="h6" color="inherit" className={classes.title} noWrap> + Email + </Typography> + </Hidden> + <IconButton + color="inherit" + aria-label="open drawer" + onClick={() => handleDrawerToggle()} + className={classes.navIconHide} + > + <MenuIcon /> + </IconButton> + <div className={classes.flex}> + <div className={classes.wrapper}> + <div className={classes.search}> + <SearchIcon /> + </div> + <input className={classes.input} onChange={(event) => search(event)} placeholder="Search Email" /> + </div> + </div> + </Toolbar> + </AppBar> + ); + } +} + +EmailHeader.propTypes = { + classes: PropTypes.object.isRequired, + search: PropTypes.func.isRequired, + handleDrawerToggle: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(EmailHeader); diff --git a/front/odiparpack/app/components/Email/EmailList.js b/front/odiparpack/app/components/Email/EmailList.js new file mode 100644 index 0000000..1fc3507 --- /dev/null +++ b/front/odiparpack/app/components/Email/EmailList.js @@ -0,0 +1,314 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Bookmark from '@material-ui/icons/Bookmark'; +import Delete from '@material-ui/icons/Delete'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import classNames from 'classnames'; +import Flag from '@material-ui/icons/Flag'; +import People from '@material-ui/icons/People'; +import QuestionAnswer from '@material-ui/icons/QuestionAnswer'; +import ReportIcon from '@material-ui/icons/Report'; +import LabelIcon from '@material-ui/icons/Label'; +import FileIcon from '@material-ui/icons/Description'; +import Download from '@material-ui/icons/CloudDownload'; +import StarBorder from '@material-ui/icons/StarBorder'; +import Star from '@material-ui/icons/Star'; +import { + List, + Typography, + ExpansionPanel, + ExpansionPanelDetails, + ExpansionPanelSummary, + ExpansionPanelActions, + Tooltip, + IconButton, + Avatar, + Button, + ListSubheader, + Menu, + MenuItem, + Divider, +} from '@material-ui/core'; +import isImage from '../Forms/helpers/helpers.js'; +import styles from './email-jss'; + + +const ITEM_HEIGHT = 80; +class EmailList extends React.Component { + state = { + anchorElOpt: null, + itemToMove: null + }; + + handleClickOpt = (event, item) => { + this.setState({ + anchorElOpt: event.currentTarget, + itemToMove: item + }); + }; + + handleCloseOpt = () => { + this.setState({ anchorElOpt: null }); + }; + + handleMoveTo = (item, category) => { + this.props.moveTo(item, category); + this.setState({ anchorElOpt: null }); + } + + render() { + const { + classes, + emailData, + openMail, + filterPage, + keyword, + remove, + toggleStar, + reply + } = this.props; + const { anchorElOpt, itemToMove } = this.state; + /* Basic Filter */ + const inbox = emailData.filter(item => item.get('category') !== 'sent' && item.get('category') !== 'spam'); + const stared = emailData.filter(item => item.get('stared')); + const sent = emailData.filter(item => item.get('category') === 'sent'); + const spam = emailData.filter(item => item.get('category') === 'spam'); + /* Category Filter */ + const updates = emailData.filter(item => item.get('category') === 'updates'); + const social = emailData.filter(item => item.get('category') === 'social'); + const forums = emailData.filter(item => item.get('category') === 'forums'); + const promos = emailData.filter(item => item.get('category') === 'promos'); + const getCategory = cat => { + switch (cat) { + case 'updates': + return ( + <span className={classNames(classes.iconOrange, classes.category)}> + <Flag /> + {' '} +Updates + </span> + ); + case 'social': + return ( + <span className={classNames(classes.iconRed, classes.category)}> + <People /> + {' '} +Social + </span> + ); + case 'promos': + return ( + <span className={classNames(classes.iconBlue, classes.category)}> + <LabelIcon /> + {' '} +Promos + </span> + ); + case 'forums': + return ( + <span className={classNames(classes.iconCyan, classes.category)}> + <QuestionAnswer /> + {' '} +Forums + </span> + ); + default: + return false; + } + }; + const attachmentPreview = filesArray => filesArray.map((file, index) => { + const base64File = URL.createObjectURL(file); + if (isImage(file)) { + return ( + <div key={index.toString()} className={classes.item}> + <div className="imageContainer col fileIconImg"> + <div className="downloadBtn"> + <IconButton color="secondary" component="a" href={base64File} target="_blank"> + <Download /> + </IconButton> + </div> + <figure className="imgWrap"><img className="smallPreviewImg" src={base64File} alt="preview" /></figure> + </div> + <Typography noWrap>{file.name}</Typography> + </div> + ); + } + return ( + <div key={index.toString()} className={classes.item}> + <div className="imageContainer col fileIconImg"> + <div className="fileWrap"> + <div className="downloadBtn"> + <IconButton color="secondary" href={base64File} target="_blank"> + <Download /> + </IconButton> + </div> + <FileIcon className="smallPreviewImg" alt="preview" /> + </div> + </div> + <Typography noWrap>{file.name}</Typography> + </div> + ); + }); + const getEmail = dataArray => dataArray.map(mail => { + const renderHTML = { __html: mail.get('content') }; + if (mail.get('subject').toLowerCase().indexOf(keyword) === -1) { + return false; + } + return ( + <ExpansionPanel className={classes.emailList} key={mail.get('id')} onChange={() => openMail(mail)}> + <ExpansionPanelSummary className={classes.emailSummary} expandIcon={<ExpandMoreIcon />}> + <div className={classes.fromHeading}> + <Tooltip id="tooltip-mark" title="Stared"> + <IconButton onClick={() => toggleStar(mail)} className={classes.starBtn}>{mail.get('stared') ? (<Star className={classes.iconOrange} />) : (<StarBorder />) }</IconButton> + </Tooltip> + {mail.get('category') !== 'spam' + ? (<Avatar alt="avatar" src={mail.get('avatar')} className={classes.avatar} />) + : (<Avatar alt="avatar" className={classes.avatar}><ReportIcon /></Avatar>) + } + <Typography className={classes.heading}> + {mail.get('category') === 'sent' && ('To ')} + {mail.get('name')} + <Typography variant="caption" display="block">{mail.get('date')}</Typography> + </Typography> + </div> + <div className={classes.column}> + <Typography className={classes.secondaryHeading} noWrap>{mail.get('subject')}</Typography> + {getCategory(mail.get('category'))} + </div> + </ExpansionPanelSummary> + <ExpansionPanelDetails className={classes.details}> + <section> + <div className={classes.topAction}> + <Typography className={classes.headMail}> + {mail.get('category') !== 'sent' && ( + <Fragment> +From + {mail.get('name')} + {' '} +to me + </Fragment> + )} + </Typography> + <div className={classes.opt}> + <Tooltip id="tooltip-mark" title="Stared"> + <IconButton onClick={() => toggleStar(mail)}>{mail.get('stared') ? (<Star className={classes.iconOrange} />) : (<StarBorder />) }</IconButton> + </Tooltip> + <Tooltip id="tooltip-mark" title="Mark message to"> + <IconButton + className={classes.button} + aria-label="mark" + aria-owns={anchorElOpt ? 'long-menu' : null} + aria-haspopup="true" + onClick={(event) => this.handleClickOpt(event, mail)} + > + <Bookmark /> + </IconButton> + </Tooltip> + <Tooltip id="tooltip-mark" title="Remove mail"> + <IconButton className={classes.button} aria-label="Delete" onClick={() => remove(mail)}><Delete /></IconButton> + </Tooltip> + </div> + </div> + <div className={classes.emailContent}> + <Typography variant="h6" gutterBottom>{mail.get('subject')}</Typography> + <article dangerouslySetInnerHTML={renderHTML} /> + </div> + <div className={classes.preview}> + {attachmentPreview(mail.get('attachment'))} + </div> + </section> + </ExpansionPanelDetails> + <Divider /> + <ExpansionPanelActions> + <div className={classes.action}> + <Button size="small">Forwad</Button> + <Button size="small" color="secondary" onClick={() => reply(mail)}>Reply</Button> + </div> + </ExpansionPanelActions> + </ExpansionPanel> + ); + }); + const showEmail = category => { + switch (category) { + case 'inbox': + return getEmail(inbox); + case 'stared': + return getEmail(stared); + case 'sent': + return getEmail(sent); + case 'spam': + return getEmail(spam); + case 'updates': + return getEmail(updates); + case 'social': + return getEmail(social); + case 'promos': + return getEmail(promos); + case 'forums': + return getEmail(forums); + default: + return getEmail(inbox); + } + }; + return ( + <main className={classes.content}> + <div className={classes.toolbar} /> + <Menu + id="long-menu" + anchorEl={anchorElOpt} + open={Boolean(anchorElOpt)} + onClose={this.handleCloseOpt} + className={classes.markMenu} + PaperProps={{ style: { maxHeight: ITEM_HEIGHT * 4.5, width: 200 } }} + > + <List + component="nav" + subheader={<ListSubheader component="div">Mark to... </ListSubheader>} + /> + <MenuItem selected onClick={() => this.handleMoveTo(itemToMove, 'updates')}> + <Flag className={classes.iconOrange} /> + {' '} +Updates + </MenuItem> + <MenuItem onClick={() => this.handleMoveTo(itemToMove, 'social')}> + <People className={classes.iconRed} /> + {' '} +Social + </MenuItem> + <MenuItem onClick={() => this.handleMoveTo(itemToMove, 'promos')}> + <LabelIcon className={classes.iconBlue} /> + {' '} +Promos + </MenuItem> + <MenuItem onClick={() => this.handleMoveTo(itemToMove, 'forums')}> + <QuestionAnswer className={classes.iconCyan} /> + {' '} +Forums + </MenuItem> + <Divider /> + <MenuItem onClick={() => this.handleMoveTo(itemToMove, 'spam')}> + <ReportIcon /> + {' '} +Spam + </MenuItem> + </Menu> + {showEmail(filterPage)} + </main> + ); + } +} + +EmailList.propTypes = { + classes: PropTypes.object.isRequired, + emailData: PropTypes.object.isRequired, + openMail: PropTypes.func.isRequired, + moveTo: PropTypes.func.isRequired, + remove: PropTypes.func.isRequired, + toggleStar: PropTypes.func.isRequired, + reply: PropTypes.func.isRequired, + filterPage: PropTypes.string.isRequired, + keyword: PropTypes.string.isRequired, +}; + +export default withStyles(styles)(EmailList); diff --git a/front/odiparpack/app/components/Email/EmailSidebar.js b/front/odiparpack/app/components/Email/EmailSidebar.js new file mode 100644 index 0000000..1951de2 --- /dev/null +++ b/front/odiparpack/app/components/Email/EmailSidebar.js @@ -0,0 +1,162 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import InboxIcon from '@material-ui/icons/MoveToInbox'; +import SendIcon from '@material-ui/icons/Send'; +import ReportIcon from '@material-ui/icons/Report'; +import StarIcon from '@material-ui/icons/Star'; +import Flag from '@material-ui/icons/Flag'; +import People from '@material-ui/icons/People'; +import QuestionAnswer from '@material-ui/icons/QuestionAnswer'; +import LabelIcon from '@material-ui/icons/Label'; +import Add from '@material-ui/icons/Add'; +import { + Drawer, + List, + ListItem, + ListItemIcon, + ListItemText, + Hidden, + Button, + Divider, +} from '@material-ui/core'; +import styles from './email-jss'; + + +const MenuList = props => { + const { + classes, + compose, + goto, + selected, + } = props; + return ( + <Fragment> + <List> + <ListItem> + <Button variant="contained" onClick={compose} fullWidth color="primary"> + <Add /> + {' '} +Compose + </Button> + </ListItem> + <ListItem button className={selected === 'inbox' ? classes.selected : ''} onClick={() => goto('inbox')}> + <ListItemIcon> + <InboxIcon /> + </ListItemIcon> + <ListItemText primary="Inbox" /> + </ListItem> + <ListItem button className={selected === 'stared' ? classes.selected : ''} onClick={() => goto('stared')}> + <ListItemIcon> + <StarIcon /> + </ListItemIcon> + <ListItemText primary="Stared" /> + </ListItem> + <ListItem button className={selected === 'sent' ? classes.selected : ''} onClick={() => goto('sent')}> + <ListItemIcon> + <SendIcon /> + </ListItemIcon> + <ListItemText primary="Sent" /> + </ListItem> + <ListItem button className={selected === 'spam' ? classes.selected : ''} onClick={() => goto('spam')}> + <ListItemIcon> + <ReportIcon /> + </ListItemIcon> + <ListItemText primary="Spam" /> + </ListItem> + </List> + <Divider className={classes.divider} /> + <List> + <ListItem button className={selected === 'updates' ? classes.selected : ''} onClick={() => goto('updates')}> + <ListItemIcon> + <Flag className={classes.iconOrange} /> + </ListItemIcon> + <ListItemText primary="Updates" /> + </ListItem> + <ListItem button className={selected === 'social' ? classes.selected : ''} onClick={() => goto('social')}> + <ListItemIcon> + <People className={classes.iconRed} /> + </ListItemIcon> + <ListItemText primary="Social" /> + </ListItem> + <ListItem button className={selected === 'promos' ? classes.selected : ''} onClick={() => goto('promos')}> + <ListItemIcon> + <LabelIcon className={classes.iconBlue} /> + </ListItemIcon> + <ListItemText primary="Promos" /> + </ListItem> + <ListItem button className={selected === 'forums' ? classes.selected : ''} onClick={() => goto('forums')}> + <ListItemIcon> + <QuestionAnswer className={classes.iconCyan} /> + </ListItemIcon> + <ListItemText primary="Forums" /> + </ListItem> + </List> + </Fragment> + ); +}; + +MenuList.propTypes = { + classes: PropTypes.object.isRequired, + compose: PropTypes.func.isRequired, + goto: PropTypes.func.isRequired, + selected: PropTypes.string.isRequired, +}; + +const MenuEmail = withStyles(styles)(MenuList); + +class EmailSidebar extends React.Component { + render() { + const { + classes, + compose, + goto, + selected, + handleDrawerToggle, + mobileOpen + } = this.props; + return ( + <Fragment> + <Hidden mdUp> + <Drawer + variant="temporary" + open={mobileOpen} + onClose={handleDrawerToggle} + classes={{ + paper: classes.drawerPaper, + }} + ModalProps={{ + keepMounted: true, // Better open performance on mobile. + }} + > + <div className={classes.toolbar} /> + <MenuEmail compose={compose} goto={goto} selected={selected} /> + </Drawer> + </Hidden> + <Hidden smDown> + <Drawer + variant="permanent" + className={classes.sidebar} + classes={{ + paper: classes.drawerPaper, + }} + > + <div className={classes.toolbar} /> + <MenuEmail compose={compose} goto={goto} selected={selected} /> + </Drawer> + </Hidden> + </Fragment> + ); + } +} + +EmailSidebar.propTypes = { + classes: PropTypes.object.isRequired, + compose: PropTypes.func.isRequired, + goto: PropTypes.func.isRequired, + handleDrawerToggle: PropTypes.func.isRequired, + selected: PropTypes.string.isRequired, + mobileOpen: PropTypes.bool.isRequired, +}; + +export default withStyles(styles)(EmailSidebar); diff --git a/front/odiparpack/app/components/Email/email-jss.js b/front/odiparpack/app/components/Email/email-jss.js new file mode 100644 index 0000000..6775966 --- /dev/null +++ b/front/odiparpack/app/components/Email/email-jss.js @@ -0,0 +1,238 @@ +import { fade } from '@material-ui/core/styles/colorManipulator'; +import { red, orange, indigo as blue, cyan } from '@material-ui/core/colors'; +const drawerWidth = 240; +const styles = theme => ({ + iconRed: { + color: red[500] + }, + iconOrange: { + color: orange[500] + }, + iconBlue: { + color: blue[500] + }, + iconCyan: { + color: cyan[500] + }, + appBar: { + zIndex: 130, + background: theme.palette.secondary.main, + '& ::-webkit-input-placeholder': { + color: theme.palette.common.white + }, + '& ::-moz-placeholder': { + color: theme.palette.common.white + }, + '& :-ms-input-placeholder': { + color: theme.palette.common.white + }, + '& :-moz-placeholder': { + color: theme.palette.common.white + } + }, + flex: { + flex: 1, + }, + wrapper: { + fontFamily: theme.typography.fontFamily, + position: 'relative', + marginLeft: theme.spacing(1), + borderRadius: 2, + background: fade(theme.palette.common.white, 0.15), + '&:hover': { + background: fade(theme.palette.common.white, 0.25), + }, + '& $input': { + transition: theme.transitions.create('width'), + }, + }, + addBtn: { + position: 'fixed', + bottom: 30, + right: 30, + zIndex: 1000 + }, + sidebar: { + zIndex: 120 + }, + search: { + width: theme.spacing(9), + height: '100%', + position: 'absolute', + pointerEvents: 'none', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + input: { + font: 'inherit', + padding: `${theme.spacing(1)}px ${theme.spacing(1)}px ${theme.spacing(1)}px ${theme.spacing(9)}px`, + border: 0, + display: 'block', + verticalAlign: 'middle', + whiteSpace: 'normal', + background: 'none', + margin: 0, // Reset for Safari + color: 'inherit', + width: '100%', + '&:focus': { + outline: 0, + }, + }, + drawerPaper: { + [theme.breakpoints.up('md')]: { + position: 'relative', + }, + width: drawerWidth, + background: theme.palette.grey[50], + border: 'none', + padding: 10 + }, + selected: { + background: theme.palette.secondary.light, + borderLeft: `2px solid ${theme.palette.secondary.main}`, + paddingLeft: 22, + '& h3': { + color: theme.palette.secondary.dark + } + }, + content: { + flexGrow: 1, + backgroundColor: theme.palette.background.default, + zIndex: 120, + marginBottom: theme.spacing(8), + [theme.breakpoints.up('md')]: { + padding: theme.spacing(3), + marginBottom: theme.spacing(4), + paddingLeft: 0, + }, + position: 'relative', + minWidth: 0, // So the Typography noWrap works + }, + toolbar: { + minHeight: 32 + }, + title: { + width: 205 + }, + divider: { + margin: '0 20px 0 10px' + }, + /* Email List */ + column: { + flexBasis: '33.33%', + overflow: 'hidden', + paddingRight: '0 !important', + paddingTop: 5, + marginLeft: 20 + }, + secondaryHeading: { + fontSize: 14, + color: theme.palette.text.secondary, + }, + icon: { + verticalAlign: 'bottom', + height: 20, + width: 20, + }, + details: { + alignItems: 'center', + [theme.breakpoints.down('sm')]: { + padding: `${theme.spacing(1)}px ${theme.spacing(1)}px ${theme.spacing(3)}px` + }, + '& section': { + width: '100%' + } + }, + link: { + color: theme.palette.secondary.main, + textDecoration: 'none', + '&:hover': { + textDecoration: 'underline', + }, + }, + avatar: {}, + fromHeading: { + overflow: 'hidden', + display: 'flex', + alignItems: 'center', + '& $avatar': { + width: 30, + height: 30, + marginRight: 20 + } + }, + topAction: { + display: 'flex', + background: theme.palette.grey[100], + marginBottom: 20, + alignItems: 'center', + padding: '0 20px', + borderRadius: 2, + }, + category: { + fontSize: 12, + textTransform: 'uppercase', + display: 'flex', + '& svg': { + fontSize: 16, + marginRight: 5 + } + }, + markMenu: { + '& svg': { + marginRight: 10 + } + }, + headMail: { + flex: 1 + }, + field: { + width: '100%', + marginBottom: 20, + '& svg': { + color: theme.palette.grey[400], + fontSize: 18, + } + }, + hiddenDropzone: { + display: 'none' + }, + sendIcon: { + marginLeft: 10 + }, + item: {}, + preview: { + display: 'flex', + marginBottom: 20, + '& $item': { + maxWidth: 160, + marginBottom: 5, + marginRight: 5 + } + }, + emailSummary: { + paddingLeft: 0, + '& > div': { + [theme.breakpoints.down('sm')]: { + flexDirection: 'column' + }, + } + }, + emailContent: { + padding: theme.spacing(2), + [theme.breakpoints.down('sm')]: { + padding: `${theme.spacing(2)}px ${theme.spacing(2)}px`, + }, + }, + starBtn: { + marginRight: 10 + }, + navIconHide: { + [theme.breakpoints.up('md')]: { + display: 'none', + }, + }, +}); + +export default styles; diff --git a/front/odiparpack/app/components/Error/ErrorWrap.js b/front/odiparpack/app/components/Error/ErrorWrap.js new file mode 100644 index 0000000..5b57ab3 --- /dev/null +++ b/front/odiparpack/app/components/Error/ErrorWrap.js @@ -0,0 +1,75 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Route, Link } from 'react-router-dom'; + +import { Typography, Button } from '@material-ui/core'; + +const styles = theme => ({ + errorWrap: { + background: theme.palette.common.white, + boxShadow: theme.shadows[2], + borderRadius: '50%', + width: 500, + height: 500, + [theme.breakpoints.down('sm')]: { + width: 300, + height: 300, + }, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'column', + position: 'relative', + margin: `${theme.spacing(3)}px auto`, + }, + title: { + color: theme.palette.primary.main, + textShadow: `10px 6px 50px ${theme.palette.primary.main}`, + [theme.breakpoints.down('sm')]: { + fontSize: '4rem' + }, + }, + deco: { + boxShadow: theme.shadows[2], + position: 'absolute', + borderRadius: 2, + }, + button: { + marginTop: 50 + } +}); + +const ErrorWrap = (props) => ( + <Route + render={({ staticContext }) => { + if (staticContext) { + staticContext.status = 404; // eslint-disable-line + } + const { classes, title, desc } = props; + return ( + <div className={classes.errorWrap}> + <Typography className={classes.title} variant="h1">{title}</Typography> + <Typography variant="h5">{desc}</Typography> + <Button + variant="contained" + color="primary" + className={classes.button} + component={Link} + to="/app/" + > + Go To Dashboard + </Button> + </div> + ); + }} + /> +); + +ErrorWrap.propTypes = { + classes: PropTypes.object.isRequired, + desc: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, +}; + +export default withStyles(styles)(ErrorWrap); diff --git a/front/odiparpack/app/components/Forms/LockForm.js b/front/odiparpack/app/components/Forms/LockForm.js new file mode 100644 index 0000000..c667809 --- /dev/null +++ b/front/odiparpack/app/components/Forms/LockForm.js @@ -0,0 +1,123 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import { Field, reduxForm } from 'redux-form/immutable'; +import ArrowForward from '@material-ui/icons/ArrowForward'; +import Help from '@material-ui/icons/Help'; +import dummy from 'ba-api/dummyContents'; +import avatarApi from 'ba-api/avatars'; +import { + Button, + Popover, + FormControl, + IconButton, + Typography, + InputAdornment, + Paper, + Avatar, +} from '@material-ui/core'; +import { TextFieldRedux } from './ReduxFormMUI'; +import styles from './user-jss'; + + +// validation functions +const required = value => (value == null ? 'Required' : undefined); + +class LockForm extends React.Component { + state = { + anchorEl: null, + }; + + handleShowHint = event => { + this.setState({ + anchorEl: event.currentTarget, + }); + }; + + handleClose = () => { + this.setState({ + anchorEl: null, + }); + }; + + render() { + const { + classes, + handleSubmit, + pristine, + submitting + } = this.props; + const { anchorEl } = this.state; + return ( + <div className={classes.formWrap}> + <Paper className={classes.lockWrap}> + <form onSubmit={handleSubmit}> + <Avatar alt="John Doe" src={avatarApi[6]} className={classes.avatar} /> + <Typography className={classes.userName} variant="h4">{dummy.user.name}</Typography> + <div> + <FormControl className={classes.formControl}> + <Field + name="password" + component={TextFieldRedux} + type="password" + label="Your Password" + required + validate={required} + className={classes.field} + InputProps={{ + endAdornment: ( + <InputAdornment position="end"> + <IconButton + aria-label="Helper Hint" + onClick={this.handleShowHint} + > + <Help /> + </IconButton> + </InputAdornment> + ) + }} + /> + </FormControl> + <Popover + open={Boolean(anchorEl)} + anchorEl={anchorEl} + onClose={this.handleClose} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'center', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'center', + }} + > + <Typography className={classes.hint}>Hint: Type anything to unlock!</Typography> + </Popover> + </div> + <div className={classes.btnArea}> + <Button fullWidth variant="contained" color="primary" type="submit"> + Continue + <ArrowForward className={classNames(classes.rightIcon, classes.iconSmall)} disabled={submitting || pristine} /> + </Button> + </div> + </form> + </Paper> + </div> + ); + } +} + +LockForm.propTypes = { + classes: PropTypes.object.isRequired, + handleSubmit: PropTypes.func.isRequired, + pristine: PropTypes.bool.isRequired, + submitting: PropTypes.bool.isRequired, +}; + +const LockFormReduxed = reduxForm({ + form: 'immutableELockFrm', + enableReinitialize: true, +})(LockForm); + +export default withStyles(styles)(LockFormReduxed); diff --git a/front/odiparpack/app/components/Forms/LoginForm.js b/front/odiparpack/app/components/Forms/LoginForm.js new file mode 100644 index 0000000..d652480 --- /dev/null +++ b/front/odiparpack/app/components/Forms/LoginForm.js @@ -0,0 +1,147 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import { Field, reduxForm } from 'redux-form/immutable'; +import { connect } from 'react-redux'; +import Visibility from '@material-ui/icons/Visibility'; +import VisibilityOff from '@material-ui/icons/VisibilityOff'; +import AllInclusive from '@material-ui/icons/AllInclusive'; +import Brightness5 from '@material-ui/icons/Brightness5'; +import People from '@material-ui/icons/People'; +import ArrowForward from '@material-ui/icons/ArrowForward'; +import { Button, IconButton, InputAdornment, FormControl, FormControlLabel } from '@material-ui/core'; +import styles from './user-jss'; +import { TextFieldRedux, CheckboxRedux } from './ReduxFormMUI'; +import { ContentDivider } from '../Divider'; +import PapperBlock from '../PapperBlock/PapperBlock'; + + +// validation functions +const required = value => (value == null ? 'Required' : undefined); +const email = value => ( + value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) + ? 'Invalid email' + : undefined +); + +class LoginForm extends React.Component { + state = { + showPassword: false + } + + handleClickShowPassword = () => { + this.setState({ showPassword: !this.state.showPassword }); + }; + + handleMouseDownPassword = event => { + event.preventDefault(); + }; + + render() { + const { + classes, + handleSubmit, + pristine, + submitting + } = this.props; + return ( + <div className={classes.formWrap}> + <PapperBlock whiteBg title="Login" desc=""> + <form onSubmit={handleSubmit}> + <div> + <FormControl className={classes.formControl}> + <Field + name="email" + component={TextFieldRedux} + placeholder="Your Email" + label="Your Email" + required + validate={[required, email]} + className={classes.field} + /> + </FormControl> + </div> + <div> + <FormControl className={classes.formControl}> + <Field + name="password" + component={TextFieldRedux} + type={this.state.showPassword ? 'text' : 'password'} + label="Your Password" + InputProps={{ + endAdornment: ( + <InputAdornment position="end"> + <IconButton + aria-label="Toggle password visibility" + onClick={this.handleClickShowPassword} + onMouseDown={this.handleMouseDownPassword} + > + {this.state.showPassword ? <VisibilityOff /> : <Visibility />} + </IconButton> + </InputAdornment> + ) + }} + required + validate={required} + className={classes.field} + /> + </FormControl> + </div> + <div className={classes.btnArea}> + <FormControlLabel control={<Field name="checkbox" component={CheckboxRedux} />} label="Remember" /> + <Button variant="contained" color="primary" type="submit"> + Continue + <ArrowForward className={classNames(classes.rightIcon, classes.iconSmall)} disabled={submitting || pristine} /> + </Button> + </div> + <ContentDivider content="OR" /> + <div className={classes.btnArea}> + <Button variant="contained" size="small" className={classes.redBtn} type="button"> + <AllInclusive className={classNames(classes.leftIcon, classes.iconSmall)} /> + Socmed 1 + </Button> + <Button variant="contained" size="small" className={classes.blueBtn} type="button"> + <Brightness5 className={classNames(classes.leftIcon, classes.iconSmall)} /> + Socmed 2 + </Button> + <Button variant="contained" size="small" className={classes.cyanBtn} type="button"> + <People className={classNames(classes.leftIcon, classes.iconSmall)} /> + Socmed 3 + </Button> + </div> + <div className={classes.footer}> + Cannot Login? + <Button size="small" color="secondary" className={classes.button}>Forgot Password</Button> + | + {' '} + <Button size="small" color="secondary" className={classes.button}>Register</Button> + </div> + </form> + </PapperBlock> + </div> + ); + } +} + +LoginForm.propTypes = { + classes: PropTypes.object.isRequired, + handleSubmit: PropTypes.func.isRequired, + pristine: PropTypes.bool.isRequired, + submitting: PropTypes.bool.isRequired, +}; + +const LoginFormReduxed = reduxForm({ + form: 'immutableExample', + enableReinitialize: true, +})(LoginForm); + +const reducer = 'login'; +const FormInit = connect( + state => ({ + force: state, + initialValues: state.getIn([reducer, 'usersLogin']) + }), +)(LoginFormReduxed); + +export default withStyles(styles)(FormInit); diff --git a/front/odiparpack/app/components/Forms/MaterialDropZone.js b/front/odiparpack/app/components/Forms/MaterialDropZone.js new file mode 100644 index 0000000..c62b3fd --- /dev/null +++ b/front/odiparpack/app/components/Forms/MaterialDropZone.js @@ -0,0 +1,202 @@ +import React from 'react'; +import Dropzone from 'react-dropzone'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { withStyles } from '@material-ui/core/styles'; +import Button from '@material-ui/core/Button'; +import FileIcon from '@material-ui/icons/Description'; +import ActionDelete from '@material-ui/icons/Delete'; +import IconButton from '@material-ui/core/IconButton'; +import Snackbar from '@material-ui/core/Snackbar'; +import CloudUpload from '@material-ui/icons/CloudUpload'; +import { lighten } from '@material-ui/core/styles/colorManipulator'; +import 'ba-styles/vendors/react-dropzone/react-dropzone.css'; +import isImage from './helpers/helpers.js'; + +const styles = theme => ({ + dropItem: { + borderColor: theme.palette.secondary.main, + background: lighten(theme.palette.secondary.light, 0.9), + borderRadius: 2 + }, + uploadIconSize: { + width: 51, + height: 51, + color: theme.palette.secondary.main, + margin: '0 auto' + }, + rightIcon: { + marginLeft: theme.spacing(1), + }, + button: { + marginTop: 20 + } +}); + +class MaterialDropZone extends React.Component { + constructor(props) { + super(props); + + this.state = { + openSnackBar: false, + errorMessage: '', + files: this.props.files, // eslint-disable-line + acceptedFiles: this.props.acceptedFiles // eslint-disable-line + }; + this.onDrop = this.onDrop.bind(this); + } + + onDrop(filesVal) { + const { files } = this.state; + const { filesLimit } = this.props; + let oldFiles = files; + const filesLimitVal = filesLimit || '3'; + oldFiles = oldFiles.concat(filesVal); + if (oldFiles.length > filesLimit) { + this.setState({ + openSnackBar: true, + errorMessage: 'Cannot upload more than ' + filesLimitVal + ' items.', + }); + } else { + this.setState({ files: oldFiles }); + } + } + + onDropRejected() { + this.setState({ + openSnackBar: true, + errorMessage: 'File too big, max size is 3MB', + }); + } + + handleRequestCloseSnackBar = () => { + this.setState({ + openSnackBar: false, + }); + }; + + handleRemove(file, fileIndex) { + const thisFiles = this.state.files; // eslint-disable-line + // This is to prevent memory leaks. + window.URL.revokeObjectURL(file.preview); + + thisFiles.splice(fileIndex, 1); + this.setState({ files: thisFiles }); + } + + render() { + const { + classes, + showPreviews, + maxSize, + text, + showButton, + filesLimit, + ...rest + } = this.props; + + const { + acceptedFiles, + files, + openSnackBar, + errorMessage + } = this.state; + const fileSizeLimit = maxSize || 3000000; + const deleteBtn = (file, index) => ( + <div className="middle"> + <IconButton onClick={() => this.handleRemove(file, index)}> + <ActionDelete className="removeBtn" /> + </IconButton> + </div> + ); + const previews = filesArray => filesArray.map((file, index) => { + const base64Img = URL.createObjectURL(file); + if (isImage(file)) { + return ( + <div key={index.toString()}> + <div className="imageContainer col fileIconImg"> + <figure className="imgWrap"><img className="smallPreviewImg" src={base64Img} alt="preview" /></figure> + {deleteBtn(file, index)} + </div> + </div> + ); + } + return ( + <div key={index.toString()}> + <div className="imageContainer col fileIconImg"> + <FileIcon className="smallPreviewImg" alt="preview" /> + {deleteBtn(file, index)} + </div> + </div> + ); + }); + let dropzoneRef; + return ( + <div> + <Dropzone + accept={acceptedFiles.join(',')} + onDrop={this.onDrop} + onDropRejected={this.onDropRejected} + acceptClassName="stripes" + rejectClassName="rejectStripes" + maxSize={fileSizeLimit} + ref={(node) => { dropzoneRef = node; }} + {...rest} + > + {({ getRootProps, getInputProps }) => ( + <div {...getRootProps()} className={classNames(classes.dropItem, 'dropZone')}> + <div className="dropzoneTextStyle"> + <input {...getInputProps()} /> + <p className="dropzoneParagraph">{text}</p> + <div className={classes.uploadIconSize}> + <CloudUpload className={classes.uploadIconSize} /> + </div> + </div> + </div> + )} + {/* end */} + </Dropzone> + {showButton && ( + <Button + className={classes.button} + fullWidth + variant="contained" + onClick={() => { + dropzoneRef.open(); + }} + color="secondary" + > + Click to upload file(s) + </Button> + )} + <div className="row preview"> + {showPreviews && previews(files)} + </div> + <Snackbar + open={openSnackBar} + message={errorMessage} + autoHideDuration={4000} + onClose={this.handleRequestCloseSnackBar} + /> + </div> + ); + } +} + +MaterialDropZone.propTypes = { + files: PropTypes.array.isRequired, + text: PropTypes.string.isRequired, + acceptedFiles: PropTypes.array, + showPreviews: PropTypes.bool.isRequired, + showButton: PropTypes.bool, + maxSize: PropTypes.number.isRequired, + filesLimit: PropTypes.number.isRequired, + classes: PropTypes.object.isRequired, +}; + +MaterialDropZone.defaultProps = { + acceptedFiles: [], + showButton: false, +}; + +export default withStyles(styles)(MaterialDropZone); diff --git a/front/odiparpack/app/components/Forms/ReduxFormMUI.js b/front/odiparpack/app/components/Forms/ReduxFormMUI.js new file mode 100644 index 0000000..383a717 --- /dev/null +++ b/front/odiparpack/app/components/Forms/ReduxFormMUI.js @@ -0,0 +1,69 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import TextField from '@material-ui/core/TextField'; +import Select from '@material-ui/core/Select'; +import Checkbox from '@material-ui/core/Checkbox'; +import Switch from '@material-ui/core/Switch'; + +/* Textfield */ +export const TextFieldRedux = ({ meta: { touched, error }, input, ...rest }) => ( + <TextField + {...rest} + {...input} + error={touched && Boolean(error)} + /> +); + +TextFieldRedux.propTypes = { + input: PropTypes.object.isRequired, + meta: PropTypes.object, +}; + +TextFieldRedux.defaultProps = { + meta: null, +}; +/* End */ + +/* Select */ +export const SelectRedux = ({ input, children, ...rest }) => ( + <Select + {...input} + {...rest} + > + {children} + </Select> +); + +SelectRedux.propTypes = { + input: PropTypes.object.isRequired, + children: PropTypes.node.isRequired, +}; +/* End */ + +/* Checkbox */ +export const CheckboxRedux = ({ input, ...rest }) => ( + <Checkbox + checked={input.value === '' ? false : input.value} + {...input} + {...rest} + /> +); + +CheckboxRedux.propTypes = { + input: PropTypes.object.isRequired, +}; +/* End */ + +/* Switch */ +export const SwitchRedux = ({ input, ...rest }) => ( + <Switch + checked={input.value === '' ? false : input.value} + {...input} + {...rest} + /> +); + +SwitchRedux.propTypes = { + input: PropTypes.object.isRequired, +}; +/* End */ diff --git a/front/odiparpack/app/components/Forms/RegisterForm.js b/front/odiparpack/app/components/Forms/RegisterForm.js new file mode 100644 index 0000000..2ac4c65 --- /dev/null +++ b/front/odiparpack/app/components/Forms/RegisterForm.js @@ -0,0 +1,174 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import { Field, reduxForm } from 'redux-form/immutable'; +import ArrowForward from '@material-ui/icons/ArrowForward'; +import AllInclusive from '@material-ui/icons/AllInclusive'; +import Brightness5 from '@material-ui/icons/Brightness5'; +import People from '@material-ui/icons/People'; +import { Button, FormControl, FormControlLabel, Tabs, Tab } from '@material-ui/core'; +import styles from './user-jss'; +import { TextFieldRedux, CheckboxRedux } from './ReduxFormMUI'; +import PapperBlock from '../PapperBlock/PapperBlock'; + + +// validation functions +const required = value => (value == null ? 'Required' : undefined); +const email = value => ( + value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) + ? 'Invalid email' + : undefined +); + +const passwordsMatch = (value, allValues) => { + console.log(value, allValues.get('password')); + if (value !== allValues.get('password')) { + return 'Passwords dont match'; + } + return undefined; +}; + +class RegisterForm extends React.Component { + state = { + tab: 0, + }; + + handleClickShowPassword = () => { + this.setState({ showPassword: !this.state.showPassword }); + }; + + handleMouseDownPassword = event => { + event.preventDefault(); + }; + + handleChangeTab = (event, value) => { + this.setState({ tab: value }); + }; + + render() { + const { + classes, + handleSubmit, + pristine, + submitting + } = this.props; + const { tab } = this.state; + return ( + <div className={classes.formWrap}> + <PapperBlock whiteBg title="Create New Account" desc=""> + <Tabs + value={this.state.tab} + onChange={this.handleChangeTab} + indicatorColor="primary" + textColor="primary" + centered + className={classes.tab} + > + <Tab label="With Email" /> + <Tab label="With Social Network" /> + </Tabs> + {tab === 0 + && ( + <form onSubmit={handleSubmit}> + <div> + <FormControl className={classes.formControl}> + <Field + name="name" + component={TextFieldRedux} + placeholder="Username" + label="Username" + required + className={classes.field} + /> + </FormControl> + </div> + <div> + <FormControl className={classes.formControl}> + <Field + name="email" + component={TextFieldRedux} + placeholder="Your Email" + label="Your Email" + required + validate={[required, email]} + className={classes.field} + /> + </FormControl> + </div> + <div> + <FormControl className={classes.formControl}> + <Field + name="password" + component={TextFieldRedux} + type="password" + label="Your Password" + required + validate={[required, passwordsMatch]} + className={classes.field} + /> + </FormControl> + </div> + <div> + <FormControl className={classes.formControl}> + <Field + name="passwordConfirm" + component={TextFieldRedux} + type="password" + label="Re-type Password" + required + validate={[required, passwordsMatch]} + className={classes.field} + /> + </FormControl> + </div> + <div className={classNames(classes.btnArea, classes.noMargin)}> + <div className={classes.optArea}> + <FormControlLabel control={<Field name="checkbox" component={CheckboxRedux} className={classes.agree} />} label="Agree with" /> + <a href="#" className={classes.link}>Terms & Condition</a> + </div> + <Button variant="contained" color="primary" type="submit"> + Continue + <ArrowForward className={classNames(classes.rightIcon, classes.iconSmall)} disabled={submitting || pristine} /> + </Button> + </div> + </form> + ) + } + {tab === 1 + && ( + <div> + <Button fullWidth variant="contained" size="large" className={classNames(classes.redBtn, classes.socMedFull)} type="button"> + <AllInclusive className={classNames(classes.leftIcon, classes.iconSmall)} /> + Socmed 1 + </Button> + <Button fullWidth variant="contained" size="large" className={classNames(classes.blueBtn, classes.socMedFull)} type="button"> + <Brightness5 className={classNames(classes.leftIcon, classes.iconSmall)} /> + Socmed 2 + </Button> + <Button fullWidth variant="contained" size="large" className={classes.cyanBtn} type="button"> + <People className={classNames(classes.leftIcon, classes.iconSmall)} /> + Socmed 3 + </Button> + </div> + ) + } + </PapperBlock> + </div> + ); + } +} + +RegisterForm.propTypes = { + classes: PropTypes.object.isRequired, + handleSubmit: PropTypes.func.isRequired, + pristine: PropTypes.bool.isRequired, + submitting: PropTypes.bool.isRequired, +}; + +const RegisterFormReduxed = reduxForm({ + form: 'immutableExample', + enableReinitialize: true, +})(RegisterForm); + +export default withStyles(styles)(RegisterFormReduxed); diff --git a/front/odiparpack/app/components/Forms/ResetForm.js b/front/odiparpack/app/components/Forms/ResetForm.js new file mode 100644 index 0000000..95bf93b --- /dev/null +++ b/front/odiparpack/app/components/Forms/ResetForm.js @@ -0,0 +1,71 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import { Field, reduxForm } from 'redux-form/immutable'; +import ArrowForward from '@material-ui/icons/ArrowForward'; +import { Button, FormControl } from '@material-ui/core'; +import styles from './user-jss'; +import PapperBlock from '../PapperBlock/PapperBlock'; +import { TextFieldRedux } from './ReduxFormMUI'; + + +// validation functions +const required = value => (value == null ? 'Required' : undefined); +const email = value => ( + value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) + ? 'Invalid email' + : undefined +); + +class ResetForm extends React.Component { + render() { + const { + classes, + handleSubmit, + pristine, + submitting + } = this.props; + return ( + <div className={classes.formWrap}> + <PapperBlock whiteBg title="Reset Password" desc="We will send reset password link to Your Email"> + <form onSubmit={handleSubmit}> + <div> + <FormControl className={classes.formControl}> + <Field + name="email" + component={TextFieldRedux} + placeholder="Your Email" + label="Your Email" + required + validate={[required, email]} + className={classes.field} + /> + </FormControl> + </div> + <div className={classes.btnArea}> + <Button variant="contained" color="primary" type="submit"> + Send ResetLink + <ArrowForward className={classNames(classes.rightIcon, classes.iconSmall)} disabled={submitting || pristine} /> + </Button> + </div> + </form> + </PapperBlock> + </div> + ); + } +} + +ResetForm.propTypes = { + classes: PropTypes.object.isRequired, + handleSubmit: PropTypes.func.isRequired, + pristine: PropTypes.bool.isRequired, + submitting: PropTypes.bool.isRequired, +}; + +const ResetFormReduxed = reduxForm({ + form: 'immutableEResetFrm', + enableReinitialize: true, +})(ResetForm); + +export default withStyles(styles)(ResetFormReduxed); diff --git a/front/odiparpack/app/components/Forms/helpers/helpers.js b/front/odiparpack/app/components/Forms/helpers/helpers.js new file mode 100644 index 0000000..99c953a --- /dev/null +++ b/front/odiparpack/app/components/Forms/helpers/helpers.js @@ -0,0 +1,8 @@ +export default function isImage(file) { + const fileName = file.name || file.path; + const suffix = fileName.substr(fileName.indexOf('.') + 1).toLowerCase(); + if (suffix === 'jpg' || suffix === 'jpeg' || suffix === 'bmp' || suffix === 'png') { + return true; + } + return false; +} diff --git a/front/odiparpack/app/components/Forms/user-jss.js b/front/odiparpack/app/components/Forms/user-jss.js new file mode 100644 index 0000000..5b9ae4a --- /dev/null +++ b/front/odiparpack/app/components/Forms/user-jss.js @@ -0,0 +1,179 @@ +import { cyan, indigo, red } from '@material-ui/core/colors'; +const styles = theme => ({ + root: { + display: 'flex', + width: '100%', + zIndex: 1, + position: 'relative' + }, + container: { + overflow: 'hidden', + display: 'flex', + alignItems: 'center', + width: '100%', + [theme.breakpoints.down('md')]: { + overflow: 'hidden' + }, + }, + formControl: { + width: '100%', + marginBottom: theme.spacing(3) + }, + loginWrap: { + [theme.breakpoints.up('md')]: { + width: 860 + }, + }, + formWrap: { + [theme.breakpoints.up('md')]: { + marginTop: -24 + }, + }, + btnArea: { + justifyContent: 'space-between', + display: 'flex', + alignItems: 'center', + marginBottom: theme.spacing(3), + [theme.breakpoints.down('sm')]: { + flexDirection: 'column', + '& button': { + width: '100%', + margin: 5 + } + }, + }, + noMargin: { + margin: 0 + }, + optArea: { + justifyContent: 'space-between', + display: 'flex', + alignItems: 'center', + width: '100%', + [theme.breakpoints.up('sm')]: { + width: '60%' + }, + }, + redBtn: { + color: theme.palette.getContrastText(red[500]), + backgroundColor: red[500], + '&:hover': { + backgroundColor: red[700], + }, + }, + blueBtn: { + color: theme.palette.getContrastText(indigo[500]), + backgroundColor: indigo[500], + '&:hover': { + backgroundColor: indigo[700], + }, + }, + cyanBtn: { + color: theme.palette.getContrastText(cyan[700]), + backgroundColor: cyan[500], + '&:hover': { + backgroundColor: cyan[700], + }, + }, + leftIcon: { + marginRight: theme.spacing(1), + }, + rightIcon: { + marginLeft: theme.spacing(1), + }, + iconSmall: { + fontSize: 20, + }, + footer: { + textAlign: 'center', + padding: 5, + background: theme.palette.grey[100], + fontSize: 14, + position: 'relative' + }, + welcomeWrap: { + position: 'relative' + }, + welcome: { + background: theme.palette.secondary.light, + position: 'absolute', + width: '100%', + height: 'calc(100% + 30px)', + padding: '20px 50px', + top: -15, + left: 2, + boxShadow: theme.shadows[5], + borderRadius: 2, + display: 'flex', + alignItems: 'center', + overflow: 'hidden' + }, + brand: { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-start', + position: 'relative', + marginBottom: 20, + '& img': { + width: 32 + }, + '& h3': { + fontSize: 18, + margin: 0, + paddingLeft: 10, + fontWeight: 500, + color: theme.palette.grey[700] + } + }, + brandText: { + marginTop: 10, + color: 'rgba(0, 0, 0, 0.54)', + }, + decoBottom: { + fontSize: 480, + position: 'absolute', + left: 10, + bottom: -190, + opacity: 0.1, + color: theme.palette.secondary.dark + }, + tab: { + marginBottom: 20, + [theme.breakpoints.up('md')]: { + marginTop: theme.spacing(1) * -3, + }, + }, + link: { + fontSize: 12, + marginLeft: -30, + color: theme.palette.secondary.main, + textDecoration: 'none', + '&:hover': { + textDecoration: 'underline' + } + }, + socMedFull: { + marginBottom: theme.spacing(2) + }, + lockWrap: { + textAlign: 'center', + padding: theme.spacing(3) + }, + avatar: { + width: 150, + height: 150, + margin: '5px auto 30px', + [theme.breakpoints.up('md')]: { + margin: '-75px auto 30px', + }, + boxShadow: theme.shadows[8] + }, + userName: { + marginBottom: theme.spacing(3) + }, + hint: { + padding: theme.spacing(1) + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/Gallery/PhotoGallery.js b/front/odiparpack/app/components/Gallery/PhotoGallery.js new file mode 100644 index 0000000..3877bba --- /dev/null +++ b/front/odiparpack/app/components/Gallery/PhotoGallery.js @@ -0,0 +1,83 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import 'ba-styles/vendors/image-lightbox/image-lightbox.css'; +import { Typography, ButtonBase } from '@material-ui/core'; +import ImageLightbox from '../ImageLightbox/ImageLightbox'; +import styles from './photo-jss'; + + +class PhotoGallery extends React.Component { + constructor(props) { + super(props); + + this.state = { + photoIndex: 0, + isOpen: false, + }; + } + + openPopup = (photoIndex) => { + this.setState({ isOpen: true, photoIndex }); + } + + render() { + const { photoIndex, isOpen } = this.state; + const { classes, imgData } = this.props; + return ( + <div> + {isOpen && ( + <ImageLightbox + mainSrc={imgData[photoIndex].img} + nextSrc={imgData[(photoIndex + 1) % imgData.length].img} + prevSrc={imgData[(photoIndex + (imgData.length - 1)) % imgData.length].img} + onCloseRequest={() => this.setState({ isOpen: false })} + onMovePrevRequest={() => this.setState({ + photoIndex: (photoIndex + (imgData.length - 1)) % imgData.length, + }) + } + onMoveNextRequest={() => this.setState({ + photoIndex: (photoIndex + 1) % imgData.length, + }) + } + /> + )} + <div className={classes.masonry}> + { + imgData.map((thumb, index) => ( + <figure className={classes.item} key={index.toString()}> + <ButtonBase + focusRipple + className={classes.image} + focusVisibleClassName={classes.focusVisible} + onClick={() => this.openPopup(index)} + > + <img src={thumb.img} alt={thumb.title} /> + <span className={classes.imageBackdrop} /> + <span className={classes.imageButton}> + <Typography + component="span" + variant="subtitle1" + color="inherit" + className={classes.imageTitle} + > + {thumb.title} + <span className={classes.imageMarked} /> + </Typography> + </span> + </ButtonBase> + </figure> + )) + } + </div> + </div> + ); + } +} + +PhotoGallery.propTypes = { + classes: PropTypes.object.isRequired, + imgData: PropTypes.array.isRequired +}; + +export default withStyles(styles)(PhotoGallery); diff --git a/front/odiparpack/app/components/Gallery/ProductDetail.js b/front/odiparpack/app/components/Gallery/ProductDetail.js new file mode 100644 index 0000000..f05852f --- /dev/null +++ b/front/odiparpack/app/components/Gallery/ProductDetail.js @@ -0,0 +1,195 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Slider from 'react-slick'; +import CloseIcon from '@material-ui/icons/Close'; +import AddShoppingCart from '@material-ui/icons/AddShoppingCart'; +import imgData from 'ba-api/imgData'; +import Type from 'ba-styles/Typography.scss'; +import 'ba-styles/vendors/slick-carousel/slick-carousel.css'; +import 'ba-styles/vendors/slick-carousel/slick.css'; +import 'ba-styles/vendors/slick-carousel/slick-theme.css'; +import { + Typography, + Grid, + Dialog, + AppBar, + Toolbar, + IconButton, + Slide, + Button, + Chip, + TextField, +} from '@material-ui/core'; +import Rating from '../Rating/Rating'; +import styles from './product-jss'; + +const getThumb = imgData.map(a => a.thumb); + +const Transition = React.forwardRef(function Transition(props, ref) { // eslint-disable-line + return <Slide direction="up" ref={ref} {...props} />; +}); + +class ProductDetail extends React.Component { + state = { + qty: 1, + } + + handleQtyChange = event => { + this.setState({ qty: event.target.value }); + } + + submitToCart = itemAttr => { + this.props.handleAddToCart(itemAttr); + this.props.close(); + } + + render() { + const { + classes, + open, + close, + detailContent, + productIndex + } = this.props; + + const { qty } = this.state; + + const itemAttr = (item) => { + if (item !== undefined) { + return { + id: detailContent.getIn([productIndex, 'id']), + name: detailContent.getIn([productIndex, 'name']), + thumbnail: detailContent.getIn([productIndex, 'thumbnail']), + price: detailContent.getIn([productIndex, 'price']), + quantity: qty + }; + } + return false; + }; + + const settings = { + customPaging: (i) => ( + <a> + <img src={getThumb[i]} alt="thumb" /> + </a> + ), + infinite: true, + dots: true, + slidesToShow: 1, + slidesToScroll: 1, + }; + + return ( + <Dialog + fullScreen + open={open} + onClose={close} + TransitionComponent={Transition} + > + <AppBar className={classes.appBar}> + <Toolbar> + <Typography variant="h6" color="inherit" className={classes.flex}> + {detailContent.getIn([productIndex, 'name'])} + </Typography> + <IconButton color="inherit" onClick={() => close()} aria-label="Close"> + <CloseIcon /> + </IconButton> + </Toolbar> + </AppBar> + <div className={classes.detailContainer}> + <Grid container className={classes.root} spacing={3}> + <Grid item md={5} sm={12} xs={12}> + <div className="container thumb-nav"> + <Slider {...settings}> + {imgData.map((item, index) => { + if (index >= 5) { + return false; + } + return ( + <div key={index.toString()} className={classes.item}> + <img src={item.img} alt={item.title} /> + </div> + ); + })} + </Slider> + </div> + </Grid> + <Grid item md={7} sm={12} xs={12}> + <section className={classes.detailWrap}> + <Typography noWrap gutterBottom variant="h5" className={classes.title} component="h2"> + {detailContent.getIn([productIndex, 'name'])} + </Typography> + <div className={classes.price}> + <Typography variant="h5"> + <span> +$ + {detailContent.getIn([productIndex, 'price'])} + </span> + </Typography> + {detailContent.getIn([productIndex, 'discount']) !== '' && ( + <Fragment> + <Typography variant="caption" component="h5"> + <span className={Type.lineThrought}> +$ + {detailContent.getIn([productIndex, 'prevPrice'])} + </span> + </Typography> + <Chip label={'Discount ' + detailContent.getIn([productIndex, 'discount'])} className={classes.chipDiscount} /> + </Fragment> + )} + {detailContent.getIn([productIndex, 'soldout']) && ( + <Chip label="Sold Out" className={classes.chipSold} /> + )} + </div> + <div className={classes.ratting}> + <Rating value={detailContent.getIn([productIndex, 'ratting'])} max={5} readOnly /> + </div> + <Typography component="p" className={classes.desc}> + {detailContent.getIn([productIndex, 'desc'])} + </Typography> + {!detailContent.getIn([productIndex, 'soldout']) && ( + <div className={classes.btnArea}> + <Typography variant="subtitle1"> + Quantity : + </Typography> + <TextField + type="number" + InputLabelProps={{ + shrink: true, + }} + margin="none" + value={qty} + className={classes.quantity} + onChange={this.handleQtyChange} + /> + <Button variant="contained" onClick={() => this.submitToCart(itemAttr(detailContent))} color="secondary"> + <AddShoppingCart /> + {' '} +Add to cart + </Button> + </div> + )} + </section> + </Grid> + </Grid> + </div> + </Dialog> + ); + } +} + +ProductDetail.propTypes = { + classes: PropTypes.object.isRequired, + open: PropTypes.bool.isRequired, + close: PropTypes.func.isRequired, + handleAddToCart: PropTypes.func.isRequired, + detailContent: PropTypes.object.isRequired, + productIndex: PropTypes.number, +}; + +ProductDetail.defaultProps = { + productIndex: undefined +}; + +export default withStyles(styles)(ProductDetail); diff --git a/front/odiparpack/app/components/Gallery/ProductGallery.js b/front/odiparpack/app/components/Gallery/ProductGallery.js new file mode 100644 index 0000000..94f6c1e --- /dev/null +++ b/front/odiparpack/app/components/Gallery/ProductGallery.js @@ -0,0 +1,152 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import ViewList from '@material-ui/icons/ViewList'; +import GridOn from '@material-ui/icons/GridOn'; +import { Grid, Typography, Button } from '@material-ui/core'; +import ProductCard from '../CardPaper/ProductCard'; +import ProductDetail from './ProductDetail'; + + +const styles = theme => ({ + result: { + margin: theme.spacing(1) + }, + option: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 10 + }, + button: { + fontSize: 12, + '& svg': { + marginRight: 10 + } + }, + appBar: { + position: 'relative', + }, + flex: { + flex: 1, + }, +}); + +class ProductGallery extends React.Component { + state = { + listView: false, + open: false, + } + + handleDetailOpen = (product) => { + this.setState({ open: true }); + this.props.showDetail(product); + }; + + handleClose = () => { + this.setState({ open: false }); + }; + + handleSwitchView = () => { + this.setState({ + listView: !this.state.listView + }); + } + + render() { + const { classes } = this.props; + const { listView, open } = this.state; + const { + dataProduct, + handleAddToCart, + productIndex, + keyword, + } = this.props; + + const getTotalResult = dataArray => { + let totalResult = 0; + for (let i = 0; i < dataArray.size; i += 1) { + if (dataArray.getIn([i, 'name']) === undefined) { + return false; + } + if (dataArray.getIn([i, 'name']).toLowerCase().indexOf(keyword) !== -1) { + totalResult += 1; + } + } + return totalResult; + }; + + return ( + <div> + <ProductDetail + open={open} + close={this.handleClose} + detailContent={dataProduct} + productIndex={productIndex} + handleAddToCart={handleAddToCart} + /> + <section className={classes.option}> + <Typography variant="caption" className={classes.result}> + {getTotalResult(dataProduct)} + {' '} +Results + </Typography> + <Button onClick={this.handleSwitchView} className={classes.button} size="small"> + {listView ? <GridOn /> : <ViewList />} + {listView ? 'Grid View' : 'List View'} + </Button> + </section> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={3} + > + { + dataProduct.map((product, index) => { + if (product.get('name').toLowerCase().indexOf(keyword) === -1) { + return false; + } + const itemAttr = { + id: product.get('id'), + name: product.get('name'), + thumbnail: product.get('thumbnail'), + price: product.get('price'), + quantity: 1 + }; + return ( + <Grid item md={listView ? 12 : 4} sm={listView ? 12 : 6} xs={12} key={index.toString()}> + <ProductCard + list={listView} + name={product.get('name')} + thumbnail={product.get('thumbnail')} + desc={product.get('desc')} + ratting={product.get('ratting')} + price={product.get('price')} + prevPrice={product.get('prevPrice')} + discount={product.get('discount')} + soldout={product.get('soldout')} + detailOpen={() => this.handleDetailOpen(product)} + addToCart={() => handleAddToCart(itemAttr)} + /> + </Grid> + ); + }) + } + </Grid> + </div> + ); + } +} + +ProductGallery.propTypes = { + classes: PropTypes.object.isRequired, + dataProduct: PropTypes.object.isRequired, + handleAddToCart: PropTypes.func.isRequired, + showDetail: PropTypes.func.isRequired, + productIndex: PropTypes.number.isRequired, + keyword: PropTypes.string.isRequired, +}; + +export default withStyles(styles)(ProductGallery); diff --git a/front/odiparpack/app/components/Gallery/photo-jss.js b/front/odiparpack/app/components/Gallery/photo-jss.js new file mode 100644 index 0000000..61a6961 --- /dev/null +++ b/front/odiparpack/app/components/Gallery/photo-jss.js @@ -0,0 +1,79 @@ +const styles = theme => ({ + masonry: { /* Masonry container */ + [theme.breakpoints.up('sm')]: { + columnCount: 2, + }, + [theme.breakpoints.up('md')]: { + columnCount: 3, + }, + columnGap: '1em', + columnFill: 'initial', + marginTop: 20 + }, + item: { + display: 'inline-table', + margin: `0 0 ${theme.spacing(2)}px`, + width: '100%', + boxShadow: theme.shadows[4], + overflow: 'hidden', + borderRadius: 2, + transition: 'box-shadow .3s', + '&:hover': { + cursor: 'pointer', + boxShadow: theme.shadows[7], + }, + '& img': { + marginBottom: -7 + } + }, + image: { + position: 'relative', + [theme.breakpoints.down('xs')]: { + width: '100% !important', // Overrides inline-style + }, + '&:hover, &$focusVisible': { + zIndex: 1, + '& $imageBackdrop': { + opacity: 0.15, + }, + }, + }, + focusVisible: {}, + imageButton: { + position: 'absolute', + left: 0, + right: 0, + top: 0, + bottom: 0, + display: 'flex', + alignItems: 'flex-end', + justifyContent: 'center', + color: theme.palette.common.white, + paddingBottom: 10 + }, + imageBackdrop: { + position: 'absolute', + left: 0, + right: 0, + top: 0, + bottom: 0, + backgroundColor: theme.palette.common.black, + opacity: 0, + transition: theme.transitions.create('opacity'), + }, + imageTitle: { + position: 'relative', + padding: `${theme.spacing(2)}px ${theme.spacing(4)}px ${theme.spacing(1) + 6}px`, + }, + imageMarked: { + height: 3, + width: 18, + backgroundColor: theme.palette.common.white, + position: 'absolute', + bottom: -2, + left: 'calc(50% - 9px)', + transition: theme.transitions.create('opacity'), + }, +}); + +export default styles; diff --git a/front/odiparpack/app/components/Gallery/product-jss.js b/front/odiparpack/app/components/Gallery/product-jss.js new file mode 100644 index 0000000..230ebf0 --- /dev/null +++ b/front/odiparpack/app/components/Gallery/product-jss.js @@ -0,0 +1,87 @@ +import { blueGrey as dark } from '@material-ui/core/colors'; +const styles = theme => ({ + root: { + flexGrow: 1, + }, + rootSlider: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center' + }, + item: { + textAlign: 'center', + '& img': { + margin: '10px auto' + } + }, + appBar: { + position: 'relative', + }, + flex: { + flex: 1, + }, + detailContainer: { + margin: '-16px auto 0', + maxWidth: '100%', + [theme.breakpoints.up('lg')]: { + maxWidth: 1080, + }, + [theme.breakpoints.up('md')]: { + maxWidth: 960, + paddingTop: 40, + marginTop: 0 + }, + [theme.breakpoints.down('sm')]: { + overflowX: 'hidden', + } + }, + chipDiscount: { + background: theme.palette.primary.light, + color: theme.palette.primary.dark, + marginBottom: 10, + }, + chipSold: { + background: dark[500], + color: theme.palette.getContrastText(dark[500]), + marginBottom: 10, + }, + detailWrap: { + padding: 30 + }, + title: { + marginBottom: 30 + }, + price: { + display: 'flex', + alignItems: 'center', + marginTop: 30, + padding: '8px 12px', + '& > *': { + marginRight: 10 + } + }, + ratting: { + borderBottom: `1px solid ${theme.palette.grey[400]}`, + marginBottom: 20, + }, + btnArea: { + display: 'flex', + alignItems: 'center', + marginTop: 20, + background: theme.palette.grey[100], + padding: '10px 20px' + }, + quantity: { + width: 40, + marginRight: 40, + marginLeft: 10, + '& input': { + textAlign: 'right' + } + }, + desc: { + padding: '10px 0' + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/Header/Header.js b/front/odiparpack/app/components/Header/Header.js new file mode 100644 index 0000000..e1d0bf5 --- /dev/null +++ b/front/odiparpack/app/components/Header/Header.js @@ -0,0 +1,63 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import SearchIcon from '@material-ui/icons/Search'; +import MenuIcon from '@material-ui/icons/Menu'; +import { AppBar, Toolbar, IconButton, Hidden } from '@material-ui/core'; +import UserMenu from './UserMenu'; +import styles from './header-jss'; + +function Header(props) { + const { + classes, + toggleDrawerOpen, + margin, + turnDarker, + } = props; + + return ( + <AppBar + className={ + classNames( + classes.appBar, + margin && classes.appBarShift, + classes.appbar, + turnDarker && classes.darker + ) + } + > + <Toolbar disableGutters> + <IconButton + className={classes.menuButton} + color="inherit" + aria-label="Menu" + onClick={toggleDrawerOpen} + > + <MenuIcon /> + </IconButton> + <div className={classes.flex}> + <div className={classes.wrapper}> + <div className={classes.search}> + <SearchIcon /> + </div> + <input className={classes.input} placeholder="Search" /> + </div> + </div> + <Hidden xsDown> + <span className={classes.separatorV} /> + </Hidden> + <UserMenu /> + </Toolbar> + </AppBar> + ); +} + +Header.propTypes = { + classes: PropTypes.object.isRequired, + toggleDrawerOpen: PropTypes.func.isRequired, + margin: PropTypes.bool.isRequired, + turnDarker: PropTypes.bool.isRequired, +}; + +export default withStyles(styles)(Header); diff --git a/front/odiparpack/app/components/Header/UserMenu.js b/front/odiparpack/app/components/Header/UserMenu.js new file mode 100644 index 0000000..3ec891e --- /dev/null +++ b/front/odiparpack/app/components/Header/UserMenu.js @@ -0,0 +1,177 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import { Link } from 'react-router-dom'; +import Avatar from '@material-ui/core/Avatar'; +import IconButton from '@material-ui/core/IconButton'; +import Button from '@material-ui/core/Button'; +import Info from '@material-ui/icons/Info'; +import Warning from '@material-ui/icons/Warning'; +import Check from '@material-ui/icons/CheckCircle'; +import Error from '@material-ui/icons/RemoveCircle'; +import ExitToApp from '@material-ui/icons/ExitToApp'; +import Badge from '@material-ui/core/Badge'; +import Divider from '@material-ui/core/Divider'; +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import ListItemAvatar from '@material-ui/core/ListItemAvatar'; +import Notification from '@material-ui/icons/Notifications'; +import dummy from 'ba-api/dummyContents'; +import messageStyles from 'ba-styles/Messages.scss'; +import avatarApi from 'ba-api/avatars'; +import link from 'ba-api/link'; +import styles from './header-jss'; + +function UserMenu(props) { + const { classes, dark } = props; + const [anchorEl, setAnchorEl] = useState(null); + const [openMenu, setOpenMenu] = useState(null); + + const handleMenu = menu => (event) => { + setOpenMenu(openMenu === menu ? null : menu); + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setOpenMenu(null); + setAnchorEl(null); + }; + + return ( + <div> + <IconButton + aria-haspopup="true" + onClick={handleMenu('notification')} + color="inherit" + className={classNames(classes.notifIcon, dark ? classes.dark : classes.light)} + > + <Badge className={classes.badge} badgeContent={4} color="secondary"> + <Notification /> + </Badge> + </IconButton> + <Menu + id="menu-notification" + anchorEl={anchorEl} + anchorOrigin={{ + vertical: 'top', + horizontal: 'right', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'right', + }} + className={classes.notifMenu} + PaperProps={{ + style: { + width: 350, + }, + }} + open={openMenu === 'notification'} + onClose={handleClose} + > + <MenuItem onClick={handleClose}> + <div className={messageStyles.messageInfo}> + <ListItemAvatar> + <Avatar alt="User Name" src={avatarApi[0]} /> + </ListItemAvatar> + <ListItemText primary={dummy.text.subtitle} className={classes.textNotif} secondary={dummy.text.date} /> + </div> + </MenuItem> + <Divider variant="inset" /> + <MenuItem onClick={handleClose}> + <div className={messageStyles.messageInfo}> + <ListItemAvatar> + <Avatar className={messageStyles.icon}> + <Info /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={dummy.text.sentences} className={classes.textNotif} secondary={dummy.text.date} /> + </div> + </MenuItem> + <Divider variant="inset" /> + <MenuItem onClick={handleClose}> + <div className={messageStyles.messageSuccess}> + <ListItemAvatar> + <Avatar className={messageStyles.icon}> + <Check /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={dummy.text.subtitle} className={classes.textNotif} secondary={dummy.text.date} /> + </div> + </MenuItem> + <Divider variant="inset" /> + <MenuItem onClick={handleClose}> + <div className={messageStyles.messageWarning}> + <ListItemAvatar> + <Avatar className={messageStyles.icon}> + <Warning /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={dummy.text.subtitle} className={classes.textNotif} secondary={dummy.text.date} /> + </div> + </MenuItem> + <Divider variant="inset" /> + <MenuItem onClick={handleClose}> + <div className={messageStyles.messageError}> + <ListItemAvatar> + <Avatar className={messageStyles.icon}> + <Error /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Suspendisse pharetra pulvinar sollicitudin. Aenean ut orci eu odio cursus lobortis eget tempus velit. " className={classes.textNotif} secondary="Jan 9, 2016" /> + </div> + </MenuItem> + </Menu> + <Button onClick={handleMenu('user-setting')}> + <Avatar + alt={dummy.user.name} + src={dummy.user.avatar} + /> + </Button> + <Menu + id="menu-appbar" + anchorEl={anchorEl} + anchorOrigin={{ + vertical: 'top', + horizontal: 'right', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'right', + }} + open={openMenu === 'user-setting'} + onClose={handleClose} + > + <MenuItem onClick={handleClose} component={Link} to={link.profile}>My Profile</MenuItem> + <MenuItem onClick={handleClose} component={Link} to={link.calendar}>My Calendar</MenuItem> + <MenuItem onClick={handleClose} component={Link} to={link.email}> + My Inbox + <ListItemIcon> + <Badge className={classNames(classes.badge, classes.badgeMenu)} badgeContent={2} color="secondary" /> + </ListItemIcon> + </MenuItem> + <Divider /> + <MenuItem onClick={handleClose} component={Link} to="/"> + <ListItemIcon> + <ExitToApp /> + </ListItemIcon> + Log Out + </MenuItem> + </Menu> + </div> + ); +} + +UserMenu.propTypes = { + classes: PropTypes.object.isRequired, + dark: PropTypes.bool, +}; + +UserMenu.defaultProps = { + dark: false +}; + +export default withStyles(styles)(UserMenu); diff --git a/front/odiparpack/app/components/Header/header-jss.js b/front/odiparpack/app/components/Header/header-jss.js new file mode 100644 index 0000000..e0a1d6b --- /dev/null +++ b/front/odiparpack/app/components/Header/header-jss.js @@ -0,0 +1,166 @@ +import { fade } from '@material-ui/core/styles/colorManipulator'; +const drawerWidth = 240; + +const styles = theme => ({ + appBar: { + position: 'fixed', + zIndex: theme.zIndex.drawer + 1, + transition: theme.transitions.create(['width', 'margin', 'background'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + boxShadow: 'none !important', + '& ::-webkit-input-placeholder': { /* Chrome/Opera/Safari */ + color: 'rgba(255,255,255,.3)' + }, + '& ::-moz-placeholder': { /* Firefox 19+ */ + color: 'rgba(255,255,255,.3)' + }, + '& :-ms-input-placeholder': { /* IE 10+ */ + color: 'rgba(255,255,255,.3)' + }, + '& :-moz-placeholder': { /* Firefox 18- */ + color: 'rgba(255,255,255,.3)' + }, + '& $menuButton': { + marginLeft: theme.spacing(2) + } + }, + flex: { + flex: 1, + textAlign: 'right' + }, + appBarShift: { + transition: theme.transitions.create(['width', 'margin', 'background'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + [theme.breakpoints.up('lg')]: { + marginLeft: drawerWidth, + width: `calc(100% - ${drawerWidth}px)`, + }, + '& $menuButton': { + marginLeft: 0 + } + }, + menuButton: { + [theme.breakpoints.up('lg')]: { + marginLeft: 0, + } + }, + hide: { + display: 'none', + }, + textField: { + marginLeft: theme.spacing(1), + marginRight: theme.spacing(1), + width: 200, + }, + container: { + display: 'flex', + flexWrap: 'wrap', + }, + wrapper: { + fontFamily: theme.typography.fontFamily, + position: 'relative', + marginRight: theme.spacing(2), + marginLeft: theme.spacing(1), + borderRadius: 2, + background: fade(theme.palette.common.white, 0.15), + display: 'inline-block', + '&:hover': { + background: fade(theme.palette.common.white, 0.25), + }, + '& $input': { + transition: theme.transitions.create('width'), + width: 180, + '&:focus': { + width: 350, + }, + [theme.breakpoints.down('xs')]: { + display: 'none' + }, + }, + }, + search: { + width: theme.spacing(9), + height: '100%', + position: 'absolute', + pointerEvents: 'none', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + [theme.breakpoints.down('xs')]: { + display: 'none' + }, + }, + input: { + font: 'inherit', + padding: `${theme.spacing(1)}px ${theme.spacing(1)}px ${theme.spacing(1)}px ${theme.spacing(9)}px`, + border: 0, + display: 'block', + verticalAlign: 'middle', + whiteSpace: 'normal', + background: 'none', + margin: 0, // Reset for Safari + color: 'inherit', + width: '100%', + '&:focus': { + outline: 0, + }, + }, + userMenu: { + display: 'flex', + alignItems: 'center' + }, + popperClose: { + pointerEvents: 'none', + zIndex: 2 + }, + darker: { + background: theme.palette.primary.dark, + '&:after': { + content: '""', + left: -240, + width: 'calc(100% + 240px)', + position: 'absolute', + bottom: -2, + height: 1, + background: '#000', + filter: 'blur(3px)' + } + }, + separatorV: { + borderLeft: `1px solid ${theme.palette.grey[300]}`, + height: 20, + margin: '0 10px', + opacity: 0.4 + }, + notifMenu: { + width: 350, + '& li': { + height: 'auto', + '& h3': { + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis' + } + } + }, + badgeMenu: { + '& span': { + top: 0, + right: -30 + } + }, + textNotif: { + '& span': { + display: 'block', + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis' + } + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/ImageLightbox/ImageLightbox.js b/front/odiparpack/app/components/ImageLightbox/ImageLightbox.js new file mode 100644 index 0000000..86e18a6 --- /dev/null +++ b/front/odiparpack/app/components/ImageLightbox/ImageLightbox.js @@ -0,0 +1,1794 @@ +/* eslint-disable */ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import Modal from 'react-modal'; +import { + translate, + getWindowWidth, + getWindowHeight, + getHighestSafeWindowContext, +} from './util'; +import { + KEYS, + MIN_ZOOM_LEVEL, + MAX_ZOOM_LEVEL, + ZOOM_RATIO, + WHEEL_MOVE_X_THRESHOLD, + WHEEL_MOVE_Y_THRESHOLD, + ZOOM_BUTTON_INCREMENT_SIZE, + ACTION_NONE, + ACTION_MOVE, + ACTION_SWIPE, + ACTION_PINCH, + SOURCE_ANY, + SOURCE_MOUSE, + SOURCE_TOUCH, + SOURCE_POINTER, + MIN_SWIPE_DISTANCE, +} from './constant'; + +class ReactImageLightbox extends Component { + static isTargetMatchImage(target) { + return target && /ril-image-current/.test(target.className); + } + + static parseMouseEvent(mouseEvent) { + return { + id: 'mouse', + source: SOURCE_MOUSE, + x: parseInt(mouseEvent.clientX, 10), + y: parseInt(mouseEvent.clientY, 10), + }; + } + + static parseTouchPointer(touchPointer) { + return { + id: touchPointer.identifier, + source: SOURCE_TOUCH, + x: parseInt(touchPointer.clientX, 10), + y: parseInt(touchPointer.clientY, 10), + }; + } + + static parsePointerEvent(pointerEvent) { + return { + id: pointerEvent.pointerId, + source: SOURCE_POINTER, + x: parseInt(pointerEvent.clientX, 10), + y: parseInt(pointerEvent.clientY, 10), + }; + } + + // Request to transition to the previous image + static getTransform({ + x = 0, + y = 0, + zoom = 1, + width, + targetWidth + }) { + let nextX = x; + const windowWidth = getWindowWidth(); + if (width > windowWidth) { + nextX += (windowWidth - width) / 2; + } + const scaleFactor = zoom * (targetWidth / width); + + return { + transform: `translate3d(${nextX}px,${y}px,0) scale3d(${scaleFactor},${scaleFactor},1)`, + }; + } + + constructor(props) { + super(props); + + this.state = { + //----------------------------- + // Animation + //----------------------------- + + // Lightbox is closing + // When Lightbox is mounted, if animation is enabled it will open with the reverse of the closing animation + isClosing: !props.animationDisabled, + + // Component parts should animate (e.g., when images are moving, or image is being zoomed) + shouldAnimate: false, + + //----------------------------- + // Zoom settings + //----------------------------- + // Zoom level of image + zoomLevel: MIN_ZOOM_LEVEL, + + //----------------------------- + // Image position settings + //----------------------------- + // Horizontal offset from center + offsetX: 0, + + // Vertical offset from center + offsetY: 0, + + // image load error for srcType + loadErrorStatus: {}, + }; + + this.closeIfClickInner = this.closeIfClickInner.bind(this); + this.handleImageDoubleClick = this.handleImageDoubleClick.bind(this); + this.handleImageMouseWheel = this.handleImageMouseWheel.bind(this); + this.handleKeyInput = this.handleKeyInput.bind(this); + this.handleMouseUp = this.handleMouseUp.bind(this); + this.handleMouseDown = this.handleMouseDown.bind(this); + this.handleMouseMove = this.handleMouseMove.bind(this); + this.handleOuterMousewheel = this.handleOuterMousewheel.bind(this); + this.handleTouchStart = this.handleTouchStart.bind(this); + this.handleTouchMove = this.handleTouchMove.bind(this); + this.handleTouchEnd = this.handleTouchEnd.bind(this); + this.handlePointerEvent = this.handlePointerEvent.bind(this); + this.handleCaptionMousewheel = this.handleCaptionMousewheel.bind(this); + this.handleWindowResize = this.handleWindowResize.bind(this); + this.handleZoomInButtonClick = this.handleZoomInButtonClick.bind(this); + this.handleZoomOutButtonClick = this.handleZoomOutButtonClick.bind(this); + this.requestClose = this.requestClose.bind(this); + this.requestMoveNext = this.requestMoveNext.bind(this); + this.requestMovePrev = this.requestMovePrev.bind(this); + } + + componentWillMount() { + // Timeouts - always clear it before umount + this.timeouts = []; + + // Current action + this.currentAction = ACTION_NONE; + + // Events source + this.eventsSource = SOURCE_ANY; + + // Empty pointers list + this.pointerList = []; + + // Prevent inner close + this.preventInnerClose = false; + this.preventInnerCloseTimeout = null; + + // Used to disable animation when changing props.mainSrc|nextSrc|prevSrc + this.keyPressed = false; + + // Used to store load state / dimensions of images + this.imageCache = {}; + + // Time the last keydown event was called (used in keyboard action rate limiting) + this.lastKeyDownTime = 0; + + // Used for debouncing window resize event + this.resizeTimeout = null; + + // Used to determine when actions are triggered by the scroll wheel + this.wheelActionTimeout = null; + this.resetScrollTimeout = null; + this.scrollX = 0; + this.scrollY = 0; + + // Used in panning zoomed images + this.moveStartX = 0; + this.moveStartY = 0; + this.moveStartOffsetX = 0; + this.moveStartOffsetY = 0; + + // Used to swipe + this.swipeStartX = 0; + this.swipeStartY = 0; + this.swipeEndX = 0; + this.swipeEndY = 0; + + // Used to pinch + this.pinchTouchList = null; + this.pinchDistance = 0; + + // Used to differentiate between images with identical src + this.keyCounter = 0; + + // Used to detect a move when all src's remain unchanged (four or more of the same image in a row) + this.moveRequested = false; + + if (!this.props.animationDisabled) { + // Make opening animation play + this.setState({ isClosing: false }); + } + } + + componentDidMount() { + // Prevents cross-origin errors when using a cross-origin iframe + this.windowContext = getHighestSafeWindowContext(); + + this.listeners = { + resize: this.handleWindowResize, + mouseup: this.handleMouseUp, + touchend: this.handleTouchEnd, + touchcancel: this.handleTouchEnd, + pointerdown: this.handlePointerEvent, + pointermove: this.handlePointerEvent, + pointerup: this.handlePointerEvent, + pointercancel: this.handlePointerEvent, + }; + Object.keys(this.listeners).forEach(type => { + this.windowContext.addEventListener(type, this.listeners[type]); + }); + + this.loadAllImages(); + } + + componentWillReceiveProps(nextProps) { + // Iterate through the source types for prevProps and nextProps to + // determine if any of the sources changed + let sourcesChanged = false; + const prevSrcDict = {}; + const nextSrcDict = {}; + this.getSrcTypes().forEach(srcType => { + if (this.props[srcType.name] !== nextProps[srcType.name]) { + sourcesChanged = true; + + prevSrcDict[this.props[srcType.name]] = true; + nextSrcDict[nextProps[srcType.name]] = true; + } + }); + + if (sourcesChanged || this.moveRequested) { + // Reset the loaded state for images not rendered next + Object.keys(prevSrcDict).forEach(prevSrc => { + if (!(prevSrc in nextSrcDict) && prevSrc in this.imageCache) { + this.imageCache[prevSrc].loaded = false; + } + }); + + this.moveRequested = false; + + // Load any new images + this.loadAllImages(nextProps); + } + } + + shouldComponentUpdate() { + // Wait for move... + return !this.moveRequested; + } + + componentWillUnmount() { + this.didUnmount = true; + Object.keys(this.listeners).forEach(type => { + this.windowContext.removeEventListener(type, this.listeners[type]); + }); + this.timeouts.forEach(tid => clearTimeout(tid)); + } + + setTimeout(func, time) { + const id = setTimeout(() => { + this.timeouts = this.timeouts.filter(tid => tid !== id); + func(); + }, time); + this.timeouts.push(id); + return id; + } + + setPreventInnerClose() { + if (this.preventInnerCloseTimeout) { + this.clearTimeout(this.preventInnerCloseTimeout); + } + this.preventInnerClose = true; + this.preventInnerCloseTimeout = this.setTimeout(() => { + this.preventInnerClose = false; + this.preventInnerCloseTimeout = null; + }, 100); + } + + // Get info for the best suited image to display with the given srcType + getBestImageForType(srcType) { + let imageSrc = this.props[srcType]; + let fitSizes = {}; + + if (this.isImageLoaded(imageSrc)) { + // Use full-size image if available + fitSizes = this.getFitSizes( + this.imageCache[imageSrc].width, + this.imageCache[imageSrc].height + ); + } else if (this.isImageLoaded(this.props[`${srcType}Thumbnail`])) { + // Fall back to using thumbnail if the image has not been loaded + imageSrc = this.props[`${srcType}Thumbnail`]; + fitSizes = this.getFitSizes( + this.imageCache[imageSrc].width, + this.imageCache[imageSrc].height, + true + ); + } else { + return null; + } + + return { + src: imageSrc, + height: this.imageCache[imageSrc].height, + width: this.imageCache[imageSrc].width, + targetHeight: fitSizes.height, + targetWidth: fitSizes.width, + }; + } + + // Get sizing for when an image is larger than the window + getFitSizes(width, height, stretch) { + const boxSize = this.getLightboxRect(); + let maxHeight = boxSize.height - (this.props.imagePadding * 2); + let maxWidth = boxSize.width - (this.props.imagePadding * 2); + + if (!stretch) { + maxHeight = Math.min(maxHeight, height); + maxWidth = Math.min(maxWidth, width); + } + + const maxRatio = maxWidth / maxHeight; + const srcRatio = width / height; + + if (maxRatio > srcRatio) { + // height is the constraining dimension of the photo + return { + width: (width * maxHeight) / height, + height: maxHeight, + }; + } + + return { + width: maxWidth, + height: (height * maxWidth) / width, + }; + } + + getMaxOffsets(zoomLevel = this.state.zoomLevel) { + const currentImageInfo = this.getBestImageForType('mainSrc'); + if (currentImageInfo === null) { + return { + maxX: 0, + minX: 0, + maxY: 0, + minY: 0 + }; + } + + const boxSize = this.getLightboxRect(); + const zoomMultiplier = this.getZoomMultiplier(zoomLevel); + + let maxX = 0; + if ((zoomMultiplier * currentImageInfo.width) - boxSize.width < 0) { + // if there is still blank space in the X dimension, don't limit except to the opposite edge + maxX = (boxSize.width - (zoomMultiplier * currentImageInfo.width)) / 2; + } else { + maxX = ((zoomMultiplier * currentImageInfo.width) - boxSize.width) / 2; + } + + let maxY = 0; + if ((zoomMultiplier * currentImageInfo.height) - boxSize.height < 0) { + // if there is still blank space in the Y dimension, don't limit except to the opposite edge + maxY = (boxSize.height - (zoomMultiplier * currentImageInfo.height)) / 2; + } else { + maxY = ((zoomMultiplier * currentImageInfo.height) - boxSize.height) / 2; + } + + return { + maxX, + maxY, + minX: -1 * maxX, + minY: -1 * maxY, + }; + } + + // Get image src types + getSrcTypes() { + return [ + { + name: 'mainSrc', + keyEnding: `i${this.keyCounter}`, + }, + { + name: 'mainSrcThumbnail', + keyEnding: `t${this.keyCounter}`, + }, + { + name: 'nextSrc', + keyEnding: `i${this.keyCounter + 1}`, + }, + { + name: 'nextSrcThumbnail', + keyEnding: `t${this.keyCounter + 1}`, + }, + { + name: 'prevSrc', + keyEnding: `i${this.keyCounter - 1}`, + }, + { + name: 'prevSrcThumbnail', + keyEnding: `t${this.keyCounter - 1}`, + }, + ]; + } + + /** + * Get sizing when the image is scaled + */ + getZoomMultiplier(zoomLevel = this.state.zoomLevel) { + return ZOOM_RATIO ** zoomLevel; + } + + /** + * Get the size of the lightbox in pixels + */ + getLightboxRect() { + if (this.outerEl) { + return this.outerEl.getBoundingClientRect(); + } + + return { + width: getWindowWidth(), + height: getWindowHeight(), + top: 0, + right: 0, + bottom: 0, + left: 0, + }; + } + + clearTimeout(id) { + this.timeouts = this.timeouts.filter(tid => tid !== id); + clearTimeout(id); + } + + // Change zoom level + changeZoom(zoomLevel, clientX, clientY) { + // Ignore if zoom disabled + if (!this.props.enableZoom) { + return; + } + + // Constrain zoom level to the set bounds + const nextZoomLevel = Math.max( + MIN_ZOOM_LEVEL, + Math.min(MAX_ZOOM_LEVEL, zoomLevel) + ); + + // Ignore requests that don't change the zoom level + if (nextZoomLevel === this.state.zoomLevel) { + return; + } else if (nextZoomLevel === MIN_ZOOM_LEVEL) { + // Snap back to center if zoomed all the way out + this.setState({ + zoomLevel: nextZoomLevel, + offsetX: 0, + offsetY: 0, + }); + + return; + } + + const imageBaseSize = this.getBestImageForType('mainSrc'); + if (imageBaseSize === null) { + return; + } + + const currentZoomMultiplier = this.getZoomMultiplier(); + const nextZoomMultiplier = this.getZoomMultiplier(nextZoomLevel); + + // Default to the center of the image to zoom when no mouse position specified + const boxRect = this.getLightboxRect(); + const pointerX = + typeof clientX !== 'undefined' + ? clientX - boxRect.left + : boxRect.width / 2; + const pointerY = + typeof clientY !== 'undefined' + ? clientY - boxRect.top + : boxRect.height / 2; + + const currentImageOffsetX = + (boxRect.width - (imageBaseSize.width * currentZoomMultiplier)) / 2; + const currentImageOffsetY = + (boxRect.height - (imageBaseSize.height * currentZoomMultiplier)) / 2; + + const currentImageRealOffsetX = currentImageOffsetX - this.state.offsetX; + const currentImageRealOffsetY = currentImageOffsetY - this.state.offsetY; + + const currentPointerXRelativeToImage = + (pointerX - currentImageRealOffsetX) / currentZoomMultiplier; + const currentPointerYRelativeToImage = + (pointerY - currentImageRealOffsetY) / currentZoomMultiplier; + + const nextImageRealOffsetX = + pointerX - (currentPointerXRelativeToImage * nextZoomMultiplier); + const nextImageRealOffsetY = + pointerY - (currentPointerYRelativeToImage * nextZoomMultiplier); + + const nextImageOffsetX = + (boxRect.width - (imageBaseSize.width * nextZoomMultiplier)) / 2; + const nextImageOffsetY = + (boxRect.height - (imageBaseSize.height * nextZoomMultiplier)) / 2; + + let nextOffsetX = nextImageOffsetX - nextImageRealOffsetX; + let nextOffsetY = nextImageOffsetY - nextImageRealOffsetY; + + // When zooming out, limit the offset so things don't get left askew + if (this.currentAction !== ACTION_PINCH) { + const maxOffsets = this.getMaxOffsets(); + if (this.state.zoomLevel > nextZoomLevel) { + nextOffsetX = Math.max( + maxOffsets.minX, + Math.min(maxOffsets.maxX, nextOffsetX) + ); + nextOffsetY = Math.max( + maxOffsets.minY, + Math.min(maxOffsets.maxY, nextOffsetY) + ); + } + } + + this.setState({ + zoomLevel: nextZoomLevel, + offsetX: nextOffsetX, + offsetY: nextOffsetY, + }); + } + + closeIfClickInner(event) { + if ( + !this.preventInnerClose && + event.target.className.search(/\bril-inner\b/) > -1 + ) { + this.requestClose(event); + } + } + + /** + * Handle user keyboard actions + */ + handleKeyInput(event) { + event.stopPropagation(); + + // Ignore key input during animations + if (this.isAnimating()) { + return; + } + + // Allow slightly faster navigation through the images when user presses keys repeatedly + if (event.type === 'keyup') { + this.lastKeyDownTime -= this.props.keyRepeatKeyupBonus; + return; + } + + const keyCode = event.which || event.keyCode; + + // Ignore key presses that happen too close to each other (when rapid fire key pressing or holding down the key) + // But allow it if it's a lightbox closing action + const currentTime = new Date(); + if ( + currentTime.getTime() - this.lastKeyDownTime < + this.props.keyRepeatLimit && + keyCode !== KEYS.ESC + ) { + return; + } + this.lastKeyDownTime = currentTime.getTime(); + + switch (keyCode) { + // ESC key closes the lightbox + case KEYS.ESC: + event.preventDefault(); + this.requestClose(event); + break; + + // Left arrow key moves to previous image + case KEYS.LEFT_ARROW: + if (!this.props.prevSrc) { + return; + } + + event.preventDefault(); + this.keyPressed = true; + this.requestMovePrev(event); + break; + + // Right arrow key moves to next image + case KEYS.RIGHT_ARROW: + if (!this.props.nextSrc) { + return; + } + + event.preventDefault(); + this.keyPressed = true; + this.requestMoveNext(event); + break; + + default: + } + } + + /** + * Handle a mouse wheel event over the lightbox container + */ + handleOuterMousewheel(event) { + // Prevent scrolling of the background + event.preventDefault(); + event.stopPropagation(); + + const xThreshold = WHEEL_MOVE_X_THRESHOLD; + let actionDelay = 0; + const imageMoveDelay = 500; + + this.clearTimeout(this.resetScrollTimeout); + this.resetScrollTimeout = this.setTimeout(() => { + this.scrollX = 0; + this.scrollY = 0; + }, 300); + + // Prevent rapid-fire zoom behavior + if (this.wheelActionTimeout !== null || this.isAnimating()) { + return; + } + + if (Math.abs(event.deltaY) < Math.abs(event.deltaX)) { + // handle horizontal scrolls with image moves + this.scrollY = 0; + this.scrollX += event.deltaX; + + const bigLeapX = xThreshold / 2; + // If the scroll amount has accumulated sufficiently, or a large leap was taken + if (this.scrollX >= xThreshold || event.deltaX >= bigLeapX) { + // Scroll right moves to next + this.requestMoveNext(event); + actionDelay = imageMoveDelay; + this.scrollX = 0; + } else if ( + this.scrollX <= -1 * xThreshold || + event.deltaX <= -1 * bigLeapX + ) { + // Scroll left moves to previous + this.requestMovePrev(event); + actionDelay = imageMoveDelay; + this.scrollX = 0; + } + } + + // Allow successive actions after the set delay + if (actionDelay !== 0) { + this.wheelActionTimeout = this.setTimeout(() => { + this.wheelActionTimeout = null; + }, actionDelay); + } + } + + handleImageMouseWheel(event) { + event.preventDefault(); + const yThreshold = WHEEL_MOVE_Y_THRESHOLD; + + if (Math.abs(event.deltaY) >= Math.abs(event.deltaX)) { + event.stopPropagation(); + // If the vertical scroll amount was large enough, perform a zoom + if (Math.abs(event.deltaY) < yThreshold) { + return; + } + + this.scrollX = 0; + this.scrollY += event.deltaY; + + this.changeZoom( + this.state.zoomLevel - event.deltaY, + event.clientX, + event.clientY + ); + } + } + + /** + * Handle a double click on the current image + */ + handleImageDoubleClick(event) { + if (this.state.zoomLevel > MIN_ZOOM_LEVEL) { + // A double click when zoomed in zooms all the way out + this.changeZoom(MIN_ZOOM_LEVEL, event.clientX, event.clientY); + } else { + // A double click when zoomed all the way out zooms in + this.changeZoom( + this.state.zoomLevel + ZOOM_BUTTON_INCREMENT_SIZE, + event.clientX, + event.clientY + ); + } + } + + shouldHandleEvent(source) { + if (this.eventsSource === source) { + return true; + } + if (this.eventsSource === SOURCE_ANY) { + this.eventsSource = source; + return true; + } + switch (source) { + case SOURCE_MOUSE: + return false; + case SOURCE_TOUCH: + this.eventsSource = SOURCE_TOUCH; + this.filterPointersBySource(); + return true; + case SOURCE_POINTER: + if (this.eventsSource === SOURCE_MOUSE) { + this.eventsSource = SOURCE_POINTER; + this.filterPointersBySource(); + return true; + } + return false; + default: + return false; + } + } + + addPointer(pointer) { + this.pointerList.push(pointer); + } + + removePointer(pointer) { + this.pointerList = this.pointerList.filter(({ id }) => id !== pointer.id); + } + + filterPointersBySource() { + this.pointerList = this.pointerList.filter( + ({ source }) => source === this.eventsSource + ); + } + + handleMouseDown(event) { + if ( + this.shouldHandleEvent(SOURCE_MOUSE) && + ReactImageLightbox.isTargetMatchImage(event.target) + ) { + this.addPointer(ReactImageLightbox.parseMouseEvent(event)); + this.multiPointerStart(event); + } + } + + handleMouseMove(event) { + if (this.shouldHandleEvent(SOURCE_MOUSE)) { + this.multiPointerMove(event, [ReactImageLightbox.parseMouseEvent(event)]); + } + } + + handleMouseUp(event) { + if (this.shouldHandleEvent(SOURCE_MOUSE)) { + this.removePointer(ReactImageLightbox.parseMouseEvent(event)); + this.multiPointerEnd(event); + } + } + + handlePointerEvent(event) { + if (this.shouldHandleEvent(SOURCE_POINTER)) { + switch (event.type) { + case 'pointerdown': + if (ReactImageLightbox.isTargetMatchImage(event.target)) { + this.addPointer(ReactImageLightbox.parsePointerEvent(event)); + this.multiPointerStart(event); + } + break; + case 'pointermove': + this.multiPointerMove(event, [ + ReactImageLightbox.parsePointerEvent(event), + ]); + break; + case 'pointerup': + case 'pointercancel': + this.removePointer(ReactImageLightbox.parsePointerEvent(event)); + this.multiPointerEnd(event); + break; + default: + break; + } + } + } + + handleTouchStart(event) { + if ( + this.shouldHandleEvent(SOURCE_TOUCH) && + ReactImageLightbox.isTargetMatchImage(event.target) + ) { + [].forEach.call(event.changedTouches, eventTouch => + this.addPointer(ReactImageLightbox.parseTouchPointer(eventTouch)) + ); + this.multiPointerStart(event); + } + } + + handleTouchMove(event) { + if (this.shouldHandleEvent(SOURCE_TOUCH)) { + this.multiPointerMove( + event, + [].map.call(event.changedTouches, eventTouch => + ReactImageLightbox.parseTouchPointer(eventTouch) + ) + ); + } + } + + handleTouchEnd(event) { + if (this.shouldHandleEvent(SOURCE_TOUCH)) { + [].map.call(event.changedTouches, touch => + this.removePointer(ReactImageLightbox.parseTouchPointer(touch)) + ); + this.multiPointerEnd(event); + } + } + + decideMoveOrSwipe(pointer) { + if (this.state.zoomLevel <= MIN_ZOOM_LEVEL) { + this.handleSwipeStart(pointer); + } else { + this.handleMoveStart(pointer); + } + } + + multiPointerStart(event) { + this.handleEnd(null); + switch (this.pointerList.length) { + case 1: { + event.preventDefault(); + this.decideMoveOrSwipe(this.pointerList[0]); + break; + } + case 2: { + event.preventDefault(); + this.handlePinchStart(this.pointerList); + break; + } + default: + break; + } + } + + multiPointerMove(event, pointerList) { + switch (this.currentAction) { + case ACTION_MOVE: { + event.preventDefault(); + this.handleMove(pointerList[0]); + break; + } + case ACTION_SWIPE: { + event.preventDefault(); + this.handleSwipe(pointerList[0]); + break; + } + case ACTION_PINCH: { + event.preventDefault(); + this.handlePinch(pointerList); + break; + } + default: + break; + } + } + + multiPointerEnd(event) { + if (this.currentAction !== ACTION_NONE) { + this.setPreventInnerClose(); + this.handleEnd(event); + } + switch (this.pointerList.length) { + case 0: { + this.eventsSource = SOURCE_ANY; + break; + } + case 1: { + event.preventDefault(); + this.decideMoveOrSwipe(this.pointerList[0]); + break; + } + case 2: { + event.preventDefault(); + this.handlePinchStart(this.pointerList); + break; + } + default: + break; + } + } + + handleEnd(event) { + switch (this.currentAction) { + case ACTION_MOVE: + this.handleMoveEnd(event); + break; + case ACTION_SWIPE: + this.handleSwipeEnd(event); + break; + case ACTION_PINCH: + this.handlePinchEnd(event); + break; + default: + break; + } + } + + // Handle move start over the lightbox container + // This happens: + // - On a mouseDown event + // - On a touchstart event + handleMoveStart({ x: clientX, y: clientY }) { + if (!this.props.enableZoom) { + return; + } + this.currentAction = ACTION_MOVE; + this.moveStartX = clientX; + this.moveStartY = clientY; + this.moveStartOffsetX = this.state.offsetX; + this.moveStartOffsetY = this.state.offsetY; + } + + // Handle dragging over the lightbox container + // This happens: + // - After a mouseDown and before a mouseUp event + // - After a touchstart and before a touchend event + handleMove({ x: clientX, y: clientY }) { + const newOffsetX = (this.moveStartX - clientX) + this.moveStartOffsetX; + const newOffsetY = (this.moveStartY - clientY) + this.moveStartOffsetY; + if ( + this.state.offsetX !== newOffsetX || + this.state.offsetY !== newOffsetY + ) { + this.setState({ + offsetX: newOffsetX, + offsetY: newOffsetY, + }); + } + } + + handleMoveEnd() { + this.currentAction = ACTION_NONE; + this.moveStartX = 0; + this.moveStartY = 0; + this.moveStartOffsetX = 0; + this.moveStartOffsetY = 0; + // Snap image back into frame if outside max offset range + const maxOffsets = this.getMaxOffsets(); + const nextOffsetX = Math.max( + maxOffsets.minX, + Math.min(maxOffsets.maxX, this.state.offsetX) + ); + const nextOffsetY = Math.max( + maxOffsets.minY, + Math.min(maxOffsets.maxY, this.state.offsetY) + ); + if ( + nextOffsetX !== this.state.offsetX || + nextOffsetY !== this.state.offsetY + ) { + this.setState({ + offsetX: nextOffsetX, + offsetY: nextOffsetY, + shouldAnimate: true, + }); + this.setTimeout(() => { + this.setState({ shouldAnimate: false }); + }, this.props.animationDuration); + } + } + + handleSwipeStart({ x: clientX, y: clientY }) { + this.currentAction = ACTION_SWIPE; + this.swipeStartX = clientX; + this.swipeStartY = clientY; + this.swipeEndX = clientX; + this.swipeEndY = clientY; + } + + handleSwipe({ x: clientX, y: clientY }) { + this.swipeEndX = clientX; + this.swipeEndY = clientY; + } + + handleSwipeEnd(event) { + const xDiff = this.swipeEndX - this.swipeStartX; + const xDiffAbs = Math.abs(xDiff); + const yDiffAbs = Math.abs(this.swipeEndY - this.swipeStartY); + + this.currentAction = ACTION_NONE; + this.swipeStartX = 0; + this.swipeStartY = 0; + this.swipeEndX = 0; + this.swipeEndY = 0; + + if (!event || this.isAnimating() || xDiffAbs < yDiffAbs * 1.5) { + return; + } + + if (xDiffAbs < MIN_SWIPE_DISTANCE) { + const boxRect = this.getLightboxRect(); + if (xDiffAbs < boxRect.width / 4) { + return; + } + } + + if (xDiff > 0 && this.props.prevSrc) { + event.preventDefault(); + this.requestMovePrev(); + } else if (xDiff < 0 && this.props.nextSrc) { + event.preventDefault(); + this.requestMoveNext(); + } + } + + calculatePinchDistance([a, b] = this.pinchTouchList) { + return Math.sqrt(((a.x - b.x) ** 2) + ((a.y - b.y) ** 2)); + } + + calculatePinchCenter([a, b] = this.pinchTouchList) { + return { + x: a.x - ((a.x - b.x) / 2), + y: a.y - ((a.y - b.y) / 2), + }; + } + + handlePinchStart(pointerList) { + if (!this.props.enableZoom) { + return; + } + this.currentAction = ACTION_PINCH; + this.pinchTouchList = pointerList.map(({ id, x, y }) => ({ id, x, y })); + this.pinchDistance = this.calculatePinchDistance(); + } + + handlePinch(pointerList) { + this.pinchTouchList = this.pinchTouchList.map(oldPointer => { + for (let i = 0; i < pointerList.length; i += 1) { + if (pointerList[i].id === oldPointer.id) { + return pointerList[i]; + } + } + + return oldPointer; + }); + + const newDistance = this.calculatePinchDistance(); + + const zoomLevel = (this.state.zoomLevel + newDistance) - this.pinchDistance; + + this.pinchDistance = newDistance; + const { x: clientX, y: clientY } = this.calculatePinchCenter( + this.pinchTouchList + ); + this.changeZoom(zoomLevel, clientX, clientY); + } + + handlePinchEnd() { + this.currentAction = ACTION_NONE; + this.pinchTouchList = null; + this.pinchDistance = 0; + } + + // Handle the window resize event + handleWindowResize() { + this.clearTimeout(this.resizeTimeout); + this.resizeTimeout = this.setTimeout(this.forceUpdate.bind(this), 100); + } + + handleZoomInButtonClick() { + this.changeZoom(this.state.zoomLevel + ZOOM_BUTTON_INCREMENT_SIZE); + } + + handleZoomOutButtonClick() { + this.changeZoom(this.state.zoomLevel - ZOOM_BUTTON_INCREMENT_SIZE); + } + + handleCaptionMousewheel(event) { + event.stopPropagation(); + + if (!this.caption) { + return; + } + + const { height } = this.caption.getBoundingClientRect(); + const { scrollHeight, scrollTop } = this.caption; + if ( + (event.deltaY > 0 && height + scrollTop >= scrollHeight) || + (event.deltaY < 0 && scrollTop <= 0) + ) { + event.preventDefault(); + } + } + + // Detach key and mouse input events + isAnimating() { + return this.state.shouldAnimate || this.state.isClosing; + } + + // Check if image is loaded + isImageLoaded(imageSrc) { + return ( + imageSrc && + imageSrc in this.imageCache && + this.imageCache[imageSrc].loaded + ); + } + + // Load image from src and call callback with image width and height on load + loadImage(srcType, imageSrc, done) { + // Return the image info if it is already cached + if (this.isImageLoaded(imageSrc)) { + this.setTimeout(() => { + done(); + }, 1); + return; + } + + const inMemoryImage = new global.Image(); + + if (this.props.imageCrossOrigin) { + inMemoryImage.crossOrigin = this.props.imageCrossOrigin; + } + + inMemoryImage.onerror = errorEvent => { + this.props.onImageLoadError(imageSrc, srcType, errorEvent); + + // failed to load so set the state loadErrorStatus + this.setState(prevState => ({ + loadErrorStatus: { ...prevState.loadErrorStatus, [srcType]: true }, + })); + + done(errorEvent); + }; + + inMemoryImage.onload = () => { + this.props.onImageLoad(imageSrc, srcType, inMemoryImage); + + this.imageCache[imageSrc] = { + loaded: true, + width: inMemoryImage.width, + height: inMemoryImage.height, + }; + + done(); + }; + + inMemoryImage.src = imageSrc; + } + + // Load all images and their thumbnails + loadAllImages(props = this.props) { + const generateLoadDoneCallback = (srcType, imageSrc) => err => { + // Give up showing image on error + if (err) { + return; + } + + // Don't rerender if the src is not the same as when the load started + // or if the component has unmounted + if (this.props[srcType] !== imageSrc || this.didUnmount) { + return; + } + + // Force rerender with the new image + this.forceUpdate(); + }; + + // Load the images + this.getSrcTypes().forEach(srcType => { + const type = srcType.name; + + // there is no error when we try to load it initially + if (props[type] && this.state.loadErrorStatus[type]) { + this.setState(prevState => ({ + loadErrorStatus: { ...prevState.loadErrorStatus, [type]: false }, + })); + } + + // Load unloaded images + if (props[type] && !this.isImageLoaded(props[type])) { + this.loadImage( + type, + props[type], + generateLoadDoneCallback(type, props[type]) + ); + } + }); + } + + // Request that the lightbox be closed + requestClose(event) { + // Call the parent close request + const closeLightbox = () => this.props.onCloseRequest(event); + + if ( + this.props.animationDisabled || + (event.type === 'keydown' && !this.props.animationOnKeyInput) + ) { + // No animation + closeLightbox(); + return; + } + + // With animation + // Start closing animation + this.setState({ isClosing: true }); + + // Perform the actual closing at the end of the animation + this.setTimeout(closeLightbox, this.props.animationDuration); + } + + requestMove(direction, event) { + // Reset the zoom level on image move + const nextState = { + zoomLevel: MIN_ZOOM_LEVEL, + offsetX: 0, + offsetY: 0, + }; + + // Enable animated states + if ( + !this.props.animationDisabled && + (!this.keyPressed || this.props.animationOnKeyInput) + ) { + nextState.shouldAnimate = true; + this.setTimeout( + () => this.setState({ shouldAnimate: false }), + this.props.animationDuration + ); + } + this.keyPressed = false; + + this.moveRequested = true; + + if (direction === 'prev') { + this.keyCounter -= 1; + this.setState(nextState); + this.props.onMovePrevRequest(event); + } else { + this.keyCounter += 1; + this.setState(nextState); + this.props.onMoveNextRequest(event); + } + } + + // Request to transition to the next image + requestMoveNext(event) { + this.requestMove('next', event); + } + + // Request to transition to the previous image + requestMovePrev(event) { + this.requestMove('prev', event); + } + + render() { + const { + animationDisabled, + animationDuration, + clickOutsideToClose, + discourageDownloads, + enableZoom, + imageTitle, + nextSrc, + prevSrc, + toolbarButtons, + reactModalStyle, + onAfterOpen, + imageCrossOrigin, + reactModalProps, + } = this.props; + const { + zoomLevel, + offsetX, + offsetY, + isClosing, + loadErrorStatus, + } = this.state; + + const boxSize = this.getLightboxRect(); + let transitionStyle = {}; + + // Transition settings for sliding animations + if (!animationDisabled && this.isAnimating()) { + transitionStyle = { + ...transitionStyle, + transition: `transform ${animationDuration}ms`, + }; + } + + // Key endings to differentiate between images with the same src + const keyEndings = {}; + this.getSrcTypes().forEach(({ name, keyEnding }) => { + keyEndings[name] = keyEnding; + }); + + // Images to be displayed + const images = []; + const addImage = (srcType, imageClass, transforms) => { + // Ignore types that have no source defined for their full size image + if (!this.props[srcType]) { + return; + } + const bestImageInfo = this.getBestImageForType(srcType); + + const imageStyle = { + ...transitionStyle, + ...ReactImageLightbox.getTransform({ + ...transforms, + ...bestImageInfo, + }), + }; + + if (zoomLevel > MIN_ZOOM_LEVEL) { + imageStyle.cursor = 'move'; + } + + // support IE 9 and 11 + const hasTrueValue = object => + Object.keys(object).some(key => object[key]); + + // when error on one of the loads then push custom error stuff + if (bestImageInfo === null && hasTrueValue(loadErrorStatus)) { + images.push( + <div + className={`${imageClass} ril__image ril-errored`} + style={imageStyle} + key={this.props[srcType] + keyEndings[srcType]} + > + <div className="ril__errorContainer"> + {this.props.imageLoadErrorMessage} + </div> + </div> + ); + + return; + } else if (bestImageInfo === null) { + const loadingIcon = ( + <div className="ril-loading-circle ril__loadingCircle ril__loadingContainer__icon"> + {[...new Array(12)].map((_, index) => ( + <div + // eslint-disable-next-line react/no-array-index-key + key={index} + className="ril-loading-circle-point ril__loadingCirclePoint" + /> + ))} + </div> + ); + + // Fall back to loading icon if the thumbnail has not been loaded + images.push( + <div + className={`${imageClass} ril__image ril-not-loaded`} + style={imageStyle} + key={this.props[srcType] + keyEndings[srcType]} + > + <div className="ril__loadingContainer">{loadingIcon}</div> + </div> + ); + + return; + } + + const imageSrc = bestImageInfo.src; + if (discourageDownloads) { + imageStyle.backgroundImage = `url('${imageSrc}')`; + images.push( + <div + className={`${imageClass} ril__image ril__imageDiscourager`} + onDoubleClick={this.handleImageDoubleClick} + onWheel={this.handleImageMouseWheel} + style={imageStyle} + key={imageSrc + keyEndings[srcType]} + > + <div className="ril-download-blocker ril__downloadBlocker" /> + </div> + ); + } else { + images.push( + <img + {...(imageCrossOrigin ? { crossOrigin: imageCrossOrigin } : {})} + className={`${imageClass} ril__image`} + onDoubleClick={this.handleImageDoubleClick} + onWheel={this.handleImageMouseWheel} + onDragStart={e => e.preventDefault()} + style={imageStyle} + src={imageSrc} + key={imageSrc + keyEndings[srcType]} + alt={ + typeof imageTitle === 'string' ? imageTitle : translate('Image') + } + draggable={false} + /> + ); + } + }; + + const zoomMultiplier = this.getZoomMultiplier(); + // Next Image (displayed on the right) + addImage('nextSrc', 'ril-image-next ril__imageNext', { + x: boxSize.width, + }); + // Main Image + addImage('mainSrc', 'ril-image-current', { + x: -1 * offsetX, + y: -1 * offsetY, + zoom: zoomMultiplier, + }); + // Previous Image (displayed on the left) + addImage('prevSrc', 'ril-image-prev ril__imagePrev', { + x: -1 * boxSize.width, + }); + + const modalStyle = { + overlay: { + zIndex: 1000, + backgroundColor: 'transparent', + ...reactModalStyle.overlay, // Allow style overrides via props + }, + content: { + backgroundColor: 'transparent', + overflow: 'hidden', // Needed, otherwise keyboard shortcuts scroll the page + border: 'none', + borderRadius: 0, + padding: 0, + top: 0, + left: 0, + right: 0, + bottom: 0, + ...reactModalStyle.content, // Allow style overrides via props + }, + }; + + return ( + <Modal + isOpen + onRequestClose={clickOutsideToClose ? this.requestClose : undefined} + onAfterOpen={() => { + // Focus on the div with key handlers + if (this.outerEl) { + this.outerEl.focus(); + } + + onAfterOpen(); + }} + style={modalStyle} + contentLabel={translate('Lightbox')} + appElement={ + typeof global.window !== 'undefined' + ? global.window.document.body + : undefined + } + {...reactModalProps} + > + <div // eslint-disable-line jsx-a11y/no-static-element-interactions + // Floating modal with closing animations + className={`ril-outer ril__outer ril__outerAnimating ${ + this.props.wrapperClassName + } ${isClosing ? 'ril-closing ril__outerClosing' : ''}`} + style={{ + transition: `opacity ${animationDuration}ms`, + animationDuration: `${animationDuration}ms`, + animationDirection: isClosing ? 'normal' : 'reverse', + }} + ref={el => { + this.outerEl = el; + }} + onWheel={this.handleOuterMousewheel} + onMouseMove={this.handleMouseMove} + onMouseDown={this.handleMouseDown} + onTouchStart={this.handleTouchStart} + onTouchMove={this.handleTouchMove} + tabIndex="-1" // Enables key handlers on div + onKeyDown={this.handleKeyInput} + onKeyUp={this.handleKeyInput} + > + <div // eslint-disable-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events + // Image holder + className="ril-inner ril__inner" + onClick={clickOutsideToClose ? this.closeIfClickInner : undefined} + > + {images} + </div> + + {prevSrc && ( + <button // Move to previous image button + type="button" + className="ril-prev-button ril__navButtons ril__navButtonPrev" + key="prev" + aria-label={this.props.prevLabel} + onClick={!this.isAnimating() ? this.requestMovePrev : undefined} // Ignore clicks during animation + /> + )} + + {nextSrc && ( + <button // Move to next image button + type="button" + className="ril-next-button ril__navButtons ril__navButtonNext" + key="next" + aria-label={this.props.nextLabel} + onClick={!this.isAnimating() ? this.requestMoveNext : undefined} // Ignore clicks during animation + /> + )} + + <div // Lightbox toolbar + className="ril-toolbar ril__toolbar" + > + <ul className="ril-toolbar-left ril__toolbarSide ril__toolbarLeftSide"> + <li className="ril-toolbar__item ril__toolbarItem"> + <span className="ril-toolbar__item__child ril__toolbarItemChild"> + {imageTitle} + </span> + </li> + </ul> + + <ul className="ril-toolbar-right ril__toolbarSide ril__toolbarRightSide"> + {toolbarButtons && + toolbarButtons.map((button, i) => ( + <li + key={`button_${i + 1}`} + className="ril-toolbar__item ril__toolbarItem" + > + {button} + </li> + ))} + + {enableZoom && ( + <li className="ril-toolbar__item ril__toolbarItem"> + <button // Lightbox zoom in button + type="button" + key="zoom-in" + aria-label={this.props.zoomInLabel} + className={[ + 'ril-zoom-in', + 'ril__toolbarItemChild', + 'ril__builtinButton', + 'ril__zoomInButton', + ...(zoomLevel === MAX_ZOOM_LEVEL + ? ['ril__builtinButtonDisabled'] + : []), + ].join(' ')} + disabled={ + this.isAnimating() || zoomLevel === MAX_ZOOM_LEVEL + } + onClick={ + !this.isAnimating() && zoomLevel !== MAX_ZOOM_LEVEL + ? this.handleZoomInButtonClick + : undefined + } + /> + </li> + )} + + {enableZoom && ( + <li className="ril-toolbar__item ril__toolbarItem"> + <button // Lightbox zoom out button + type="button" + key="zoom-out" + aria-label={this.props.zoomOutLabel} + className={[ + 'ril-zoom-out', + 'ril__toolbarItemChild', + 'ril__builtinButton', + 'ril__zoomOutButton', + ...(zoomLevel === MIN_ZOOM_LEVEL + ? ['ril__builtinButtonDisabled'] + : []), + ].join(' ')} + disabled={ + this.isAnimating() || zoomLevel === MIN_ZOOM_LEVEL + } + onClick={ + !this.isAnimating() && zoomLevel !== MIN_ZOOM_LEVEL + ? this.handleZoomOutButtonClick + : undefined + } + /> + </li> + )} + + <li className="ril-toolbar__item ril__toolbarItem"> + <button // Lightbox close button + type="button" + key="close" + aria-label={this.props.closeLabel} + className="ril-close ril-toolbar__item__child ril__toolbarItemChild ril__builtinButton ril__closeButton" + onClick={!this.isAnimating() ? this.requestClose : undefined} // Ignore clicks during animation + /> + </li> + </ul> + </div> + + {this.props.imageCaption && ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions + <div // Image caption + onWheel={this.handleCaptionMousewheel} + onMouseDown={event => event.stopPropagation()} + className="ril-caption ril__caption" + ref={el => { + this.caption = el; + }} + > + <div className="ril-caption-content ril__captionContent"> + {this.props.imageCaption} + </div> + </div> + )} + </div> + </Modal> + ); + } +} + +ReactImageLightbox.propTypes = { + //----------------------------- + // Image sources + //----------------------------- + + // Main display image url + mainSrc: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types + + // Previous display image url (displayed to the left) + // If left undefined, movePrev actions will not be performed, and the button not displayed + prevSrc: PropTypes.string, + + // Next display image url (displayed to the right) + // If left undefined, moveNext actions will not be performed, and the button not displayed + nextSrc: PropTypes.string, + + //----------------------------- + // Image thumbnail sources + //----------------------------- + + // Thumbnail image url corresponding to props.mainSrc + mainSrcThumbnail: PropTypes.string, // eslint-disable-line react/no-unused-prop-types + + // Thumbnail image url corresponding to props.prevSrc + prevSrcThumbnail: PropTypes.string, // eslint-disable-line react/no-unused-prop-types + + // Thumbnail image url corresponding to props.nextSrc + nextSrcThumbnail: PropTypes.string, // eslint-disable-line react/no-unused-prop-types + + //----------------------------- + // Event Handlers + //----------------------------- + + // Close window event + // Should change the parent state such that the lightbox is not rendered + onCloseRequest: PropTypes.func.isRequired, + + // Move to previous image event + // Should change the parent state such that props.prevSrc becomes props.mainSrc, + // props.mainSrc becomes props.nextSrc, etc. + onMovePrevRequest: PropTypes.func, + + // Move to next image event + // Should change the parent state such that props.nextSrc becomes props.mainSrc, + // props.mainSrc becomes props.prevSrc, etc. + onMoveNextRequest: PropTypes.func, + + // Called when an image fails to load + // (imageSrc: string, srcType: string, errorEvent: object): void + onImageLoadError: PropTypes.func, + + // Called when image successfully loads + onImageLoad: PropTypes.func, + + // Open window event + onAfterOpen: PropTypes.func, + + //----------------------------- + // Download discouragement settings + //----------------------------- + + // Enable download discouragement (prevents [right-click -> Save Image As...]) + discourageDownloads: PropTypes.bool, + + //----------------------------- + // Animation settings + //----------------------------- + + // Disable all animation + animationDisabled: PropTypes.bool, + + // Disable animation on actions performed with keyboard shortcuts + animationOnKeyInput: PropTypes.bool, + + // Animation duration (ms) + animationDuration: PropTypes.number, + + //----------------------------- + // Keyboard shortcut settings + //----------------------------- + + // Required interval of time (ms) between key actions + // (prevents excessively fast navigation of images) + keyRepeatLimit: PropTypes.number, + + // Amount of time (ms) restored after each keyup + // (makes rapid key presses slightly faster than holding down the key to navigate images) + keyRepeatKeyupBonus: PropTypes.number, + + //----------------------------- + // Image info + //----------------------------- + + // Image title + imageTitle: PropTypes.node, + + // Image caption + imageCaption: PropTypes.node, + + // Optional crossOrigin attribute + imageCrossOrigin: PropTypes.string, + + //----------------------------- + // Lightbox style + //----------------------------- + + // Set z-index style, etc., for the parent react-modal (format: https://github.com/reactjs/react-modal#styles ) + reactModalStyle: PropTypes.shape({}), + + // Padding (px) between the edge of the window and the lightbox + imagePadding: PropTypes.number, + + wrapperClassName: PropTypes.string, + + //----------------------------- + // Other + //----------------------------- + + // Array of custom toolbar buttons + toolbarButtons: PropTypes.arrayOf(PropTypes.node), + + // When true, clicks outside of the image close the lightbox + clickOutsideToClose: PropTypes.bool, + + // Set to false to disable zoom functionality and hide zoom buttons + enableZoom: PropTypes.bool, + + // Override props set on react-modal (https://github.com/reactjs/react-modal) + reactModalProps: PropTypes.shape({}), + + // Aria-labels + nextLabel: PropTypes.string, + prevLabel: PropTypes.string, + zoomInLabel: PropTypes.string, + zoomOutLabel: PropTypes.string, + closeLabel: PropTypes.string, + + imageLoadErrorMessage: PropTypes.node, +}; + +ReactImageLightbox.defaultProps = { + imageTitle: null, + imageCaption: null, + toolbarButtons: null, + reactModalProps: {}, + animationDisabled: false, + animationDuration: 300, + animationOnKeyInput: false, + clickOutsideToClose: true, + closeLabel: 'Close lightbox', + discourageDownloads: false, + enableZoom: true, + imagePadding: 10, + imageCrossOrigin: null, + keyRepeatKeyupBonus: 40, + keyRepeatLimit: 180, + mainSrcThumbnail: null, + nextLabel: 'Next image', + nextSrc: null, + nextSrcThumbnail: null, + onAfterOpen: () => {}, + onImageLoadError: () => {}, + onImageLoad: () => {}, + onMoveNextRequest: () => {}, + onMovePrevRequest: () => {}, + prevLabel: 'Previous image', + prevSrc: null, + prevSrcThumbnail: null, + reactModalStyle: {}, + wrapperClassName: '', + zoomInLabel: 'Zoom in', + zoomOutLabel: 'Zoom out', + imageLoadErrorMessage: 'This image failed to load', +}; + +export default ReactImageLightbox; diff --git a/front/odiparpack/app/components/ImageLightbox/constant.js b/front/odiparpack/app/components/ImageLightbox/constant.js new file mode 100644 index 0000000..c310b9e --- /dev/null +++ b/front/odiparpack/app/components/ImageLightbox/constant.js @@ -0,0 +1,39 @@ +// Min image zoom level +export const MIN_ZOOM_LEVEL = 0; + +// Max image zoom level +export const MAX_ZOOM_LEVEL = 300; + +// Size ratio between previous and next zoom levels +export const ZOOM_RATIO = 1.007; + +// How much to increase/decrease the zoom level when the zoom buttons are clicked +export const ZOOM_BUTTON_INCREMENT_SIZE = 100; + +// Used to judge the amount of horizontal scroll needed to initiate a image move +export const WHEEL_MOVE_X_THRESHOLD = 200; + +// Used to judge the amount of vertical scroll needed to initiate a zoom action +export const WHEEL_MOVE_Y_THRESHOLD = 1; + +export const KEYS = { + ESC: 27, + LEFT_ARROW: 37, + RIGHT_ARROW: 39, +}; + +// Actions +export const ACTION_NONE = 0; +export const ACTION_MOVE = 1; +export const ACTION_SWIPE = 2; +export const ACTION_PINCH = 3; +export const ACTION_ROTATE = 4; + +// Events source +export const SOURCE_ANY = 0; +export const SOURCE_MOUSE = 1; +export const SOURCE_TOUCH = 2; +export const SOURCE_POINTER = 3; + +// Minimal swipe distance +export const MIN_SWIPE_DISTANCE = 200; diff --git a/front/odiparpack/app/components/ImageLightbox/util.js b/front/odiparpack/app/components/ImageLightbox/util.js new file mode 100644 index 0000000..a76383a --- /dev/null +++ b/front/odiparpack/app/components/ImageLightbox/util.js @@ -0,0 +1,46 @@ +/** + * Placeholder for future translate functionality + */ +export function translate(str, replaceStrings = null) { + if (!str) { + return ''; + } + + let translated = str; + if (replaceStrings) { + Object.keys(replaceStrings).forEach(placeholder => { + translated = translated.replace(placeholder, replaceStrings[placeholder]); + }); + } + + return translated; +} + +export function getWindowWidth() { + return typeof global.window !== 'undefined' ? global.window.innerWidth : 0; +} + +export function getWindowHeight() { + return typeof global.window !== 'undefined' ? global.window.innerHeight : 0; +} + +// Get the highest window context that isn't cross-origin +// (When in an iframe) +export function getHighestSafeWindowContext(self = global.window.self) { + // If we reached the top level, return self + if (self === global.window.top) { + return self; + } + + const getOrigin = href => href.match(/(.*\/\/.*?)(\/|$)/)[1]; + + // If parent is the same origin, we can move up one context + // Reference: https://stackoverflow.com/a/21965342/1601953 + if (getOrigin(self.location.href) === getOrigin(self.document.referrer)) { + return getHighestSafeWindowContext(self.parent); + } + + // If a different origin, we consider the current level + // as the top reachable one + return self; +} diff --git a/front/odiparpack/app/components/Loading/index.js b/front/odiparpack/app/components/Loading/index.js new file mode 100644 index 0000000..41c92af --- /dev/null +++ b/front/odiparpack/app/components/Loading/index.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { PropTypes } from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { CircularProgress } from '@material-ui/core'; +const styles = { + circularProgress: { + position: 'fixed', + top: 'calc(50% - 30px)', + left: 'calc(50% - 30px)', + } +}; + +function Loading(props) { + return (<CircularProgress className={props.classes.circularProgress} size={60} color="secondary" />); +} + +Loading.propTypes = { + classes: PropTypes.object.isRequired, +}; +export default (withStyles(styles)(Loading)); diff --git a/front/odiparpack/app/components/Notification/Notification.js b/front/odiparpack/app/components/Notification/Notification.js new file mode 100644 index 0000000..7e73896 --- /dev/null +++ b/front/odiparpack/app/components/Notification/Notification.js @@ -0,0 +1,59 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import CloseIcon from '@material-ui/icons/Close'; + +import { Snackbar, IconButton } from '@material-ui/core'; + +const styles = theme => ({ + close: { + width: theme.spacing(4), + }, +}); + +class Notification extends React.Component { + handleClose = (event, reason) => { + if (reason === 'clickaway') { + return; + } + this.props.close('crudTableDemo'); + }; + + render() { + const { classes, message } = this.props; + return ( + <Snackbar + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'left', + }} + open={message !== ''} + autoHideDuration={3000} + onClose={() => this.handleClose()} + ContentProps={{ + 'aria-describedby': 'message-id', + }} + message={message} + action={[ + <IconButton + key="close" + aria-label="Close" + color="inherit" + className={classes.close} + onClick={() => this.handleClose()} + > + <CloseIcon /> + </IconButton>, + ]} + /> + ); + } +} + +Notification.propTypes = { + classes: PropTypes.object.isRequired, + close: PropTypes.func.isRequired, + message: PropTypes.string.isRequired, +}; + +export default withStyles(styles)(Notification); diff --git a/front/odiparpack/app/components/Pagination/Pagination.js b/front/odiparpack/app/components/Pagination/Pagination.js new file mode 100644 index 0000000..2ac2e29 --- /dev/null +++ b/front/odiparpack/app/components/Pagination/Pagination.js @@ -0,0 +1,189 @@ +import React from 'react'; +import { createUltimatePagination, ITEM_TYPES } from 'react-ultimate-pagination'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import NavigationFirstPage from '@material-ui/icons/FirstPage'; +import NavigationLastPage from '@material-ui/icons/LastPage'; +import NavigationChevronLeft from '@material-ui/icons/ChevronLeft'; +import NavigationChevronRight from '@material-ui/icons/ChevronRight'; + +import { Button, Hidden, IconButton } from '@material-ui/core'; + +const flatButtonStyle = { + minWidth: 36 +}; + +const styles = { + paging: { + marginTop: 10, + display: 'flex', + justifyContent: 'center', + alignItems: 'center' + } +}; + +const Page = ({ + value, + isActive, + onClick, + isDisabled +}) => ( + <Button + style={flatButtonStyle} + color={isActive ? 'primary' : 'default'} + onClick={onClick} + disabled={isDisabled} + > + {value.toString()} + </Button> +); + +Page.propTypes = { + value: PropTypes.number.isRequired, + isActive: PropTypes.bool.isRequired, + onClick: PropTypes.func.isRequired, + isDisabled: PropTypes.bool.isRequired, +}; + +const Ellipsis = ({ onClick, isDisabled }) => ( + <Button + style={flatButtonStyle} + onClick={onClick} + disabled={isDisabled} + > + ... + </Button> +); + +Ellipsis.propTypes = { + onClick: PropTypes.func.isRequired, + isDisabled: PropTypes.bool.isRequired, +}; + +const FirstPageLink = ({ onClick, isDisabled }) => ( + <IconButton + style={flatButtonStyle} + onClick={onClick} + disabled={isDisabled} + > + <NavigationFirstPage /> + </IconButton> +); + + +FirstPageLink.propTypes = { + onClick: PropTypes.func.isRequired, + isDisabled: PropTypes.bool.isRequired, +}; + +const PreviousPageLink = ({ onClick, isDisabled }) => ( + <IconButton + style={flatButtonStyle} + onClick={onClick} + disabled={isDisabled} + > + <NavigationChevronLeft /> + </IconButton> +); + +PreviousPageLink.propTypes = { + onClick: PropTypes.func.isRequired, + isDisabled: PropTypes.bool.isRequired, +}; + +const NextPageLink = ({ onClick, isDisabled }) => ( + <IconButton + style={flatButtonStyle} + onClick={onClick} + disabled={isDisabled} + > + <NavigationChevronRight /> + </IconButton> +); + +NextPageLink.propTypes = { + onClick: PropTypes.func.isRequired, + isDisabled: PropTypes.bool.isRequired, +}; + +const LastPageLink = ({ onClick, isDisabled }) => ( + <IconButton + style={flatButtonStyle} + onClick={onClick} + disabled={isDisabled} + > + <NavigationLastPage /> + </IconButton> +); + +LastPageLink.propTypes = { + onClick: PropTypes.func.isRequired, + isDisabled: PropTypes.bool.isRequired, +}; + + +const itemTypeToComponent = { + [ITEM_TYPES.PAGE]: Page, + [ITEM_TYPES.ELLIPSIS]: Ellipsis, + [ITEM_TYPES.FIRST_PAGE_LINK]: FirstPageLink, + [ITEM_TYPES.PREVIOUS_PAGE_LINK]: PreviousPageLink, + [ITEM_TYPES.NEXT_PAGE_LINK]: NextPageLink, + [ITEM_TYPES.LAST_PAGE_LINK]: LastPageLink +}; + +const UltmPagination = createUltimatePagination({ itemTypeToComponent }); + +class Pagination extends React.Component { + constructor(props) { + super(); + this.state = { + totalPages: props.totpages + }; + } + + render() { + const hide = true; + const { totalPages } = this.state; + const { + classes, + curpage, + onChange, + onGoFirst, + onPrev, + onNext, + onGoLast, + ...rest + } = this.props; + return ( + <div className={classes.paging}> + <FirstPageLink isDisabled={curpage <= 1} onClick={onGoFirst} /> + <PreviousPageLink isDisabled={curpage <= 1} onClick={onPrev} /> + <Hidden xsDown> + <UltmPagination + currentPage={curpage} + totalPages={totalPages} + onChange={onChange} + hidePreviousAndNextPageLinks={hide} + hideFirstAndLastPageLinks={hide} + {...rest} + /> + </Hidden> + <NextPageLink isDisabled={curpage >= totalPages} onClick={onNext} /> + <LastPageLink isDisabled={curpage >= totalPages} onClick={onGoLast} /> + </div> + ); + } +} + +Pagination.propTypes = { + curpage: PropTypes.number.isRequired, + totpages: PropTypes.number.isRequired, + onChange: PropTypes.func.isRequired, + onPrev: PropTypes.func.isRequired, + onNext: PropTypes.func.isRequired, + onGoFirst: PropTypes.func.isRequired, + onGoLast: PropTypes.func.isRequired, + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Pagination); diff --git a/front/odiparpack/app/components/Panel/FloatingPanel.js b/front/odiparpack/app/components/Panel/FloatingPanel.js new file mode 100644 index 0000000..675166a --- /dev/null +++ b/front/odiparpack/app/components/Panel/FloatingPanel.js @@ -0,0 +1,90 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { isWidthDown } from '@material-ui/core/withWidth'; +import classNames from 'classnames'; +import CloseIcon from '@material-ui/icons/Close'; +import ExpandIcon from '@material-ui/icons/CallMade'; +import MinimizeIcon from '@material-ui/icons/CallReceived'; +import { withWidth, Tooltip, IconButton } from '@material-ui/core'; +import styles from './panel-jss'; + + +class FloatingPanel extends React.Component { + state = { + expanded: false + } + + toggleExpand() { + this.setState({ expanded: !this.state.expanded }); + } + + render() { + const { + classes, + openForm, + closeForm, + children, + branch, + title, + extraSize, + width + } = this.props; + const { expanded } = this.state; + return ( + <div> + <div className={ + classNames( + classes.formOverlay, + openForm && (isWidthDown('sm', width) || expanded) ? classes.showForm : classes.hideForm + )} + /> + <section className={ + classNames( + !openForm ? classes.hideForm : classes.showForm, + expanded ? classes.expanded : '', + classes.floatingForm, + classes.formTheme, + extraSize && classes.large + )} + > + <header> + { title } + <div className={classes.btnOpt}> + <Tooltip title={expanded ? 'Exit Full Screen' : 'Full Screen'}> + <IconButton className={classes.expandButton} onClick={() => this.toggleExpand()} aria-label="Expand"> + {expanded ? <MinimizeIcon /> : <ExpandIcon />} + </IconButton> + </Tooltip> + <Tooltip title="Close"> + <IconButton className={classes.closeButton} onClick={() => closeForm(branch)} aria-label="Close"> + <CloseIcon /> + </IconButton> + </Tooltip> + </div> + </header> + {children} + </section> + </div> + ); + } +} + +FloatingPanel.propTypes = { + classes: PropTypes.object.isRequired, + openForm: PropTypes.bool.isRequired, + closeForm: PropTypes.func.isRequired, + children: PropTypes.node.isRequired, + branch: PropTypes.string.isRequired, + width: PropTypes.string.isRequired, + title: PropTypes.string, + extraSize: PropTypes.bool, +}; + +FloatingPanel.defaultProps = { + title: 'Add New Item', + extraSize: false, +}; + +const FloatingPanelResponsive = withWidth()(FloatingPanel); +export default withStyles(styles)(FloatingPanelResponsive); diff --git a/front/odiparpack/app/components/Panel/panel-jss.js b/front/odiparpack/app/components/Panel/panel-jss.js new file mode 100644 index 0000000..d5d5e9c --- /dev/null +++ b/front/odiparpack/app/components/Panel/panel-jss.js @@ -0,0 +1,95 @@ +import { darken } from '@material-ui/core/styles/colorManipulator'; +const expand = { + bottom: 'auto', + right: 'auto', + left: '50%', + top: '50%', + transform: 'translateX(-50%) translateY(-50%)' +}; + +const styles = theme => ({ + formTheme: { + background: darken(theme.palette.primary.dark, 0.2), + boxShadow: theme.shadows[7] + }, + hideForm: { + display: 'none' + }, + showForm: { + display: 'block' + }, + btnOpt: {}, + expandButton: { + [theme.breakpoints.down('sm')]: { + display: 'none' + } + }, + floatingForm: { + position: 'fixed', + width: 500, + bottom: 10, + right: 10, + zIndex: 1300, + borderRadius: 3, + overflow: 'hidden', + [theme.breakpoints.down('sm')]: { + width: '95% !important', + ...expand + }, + '& header': { + color: theme.palette.common.white, + padding: '15px 20px', + '& $btnOpt': { + position: 'absolute', + right: 0, + top: 0, + '& > *': { + margin: '0 5px' + }, + '& $expandButton': { + transform: 'rotate(270deg)' + }, + '& svg': { + fill: theme.palette.common.white, + } + } + }, + }, + bodyForm: { + position: 'relative', + background: theme.palette.common.white, + padding: '15px 30px', + maxHeight: 900, + overflow: 'auto' + }, + buttonArea: { + background: theme.palette.grey[100], + position: 'relative', + bottom: 0, + left: 0, + width: '100%', + textAlign: 'right', + padding: '10px 30px', + '& button': { + marginRight: 5 + } + }, + expanded: { + ...expand + }, + formOverlay: { + position: 'fixed', + background: theme.palette.grey[900], + opacity: 0.7, + width: '100%', + height: '100%', + top: 0, + left: 0, + zIndex: 1300, + }, + large: { + width: 650 + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/PapperBlock/PapperBlock.js b/front/odiparpack/app/components/PapperBlock/PapperBlock.js new file mode 100644 index 0000000..6663ae6 --- /dev/null +++ b/front/odiparpack/app/components/PapperBlock/PapperBlock.js @@ -0,0 +1,55 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { withStyles } from '@material-ui/core/styles'; +import { Paper, Typography } from '@material-ui/core'; +import styles from './papperStyle-jss'; + + +function PaperSheet(props) { + const { + classes, + title, + desc, + children, + whiteBg, + noMargin, + colorMode, + overflowX + } = props; + return ( + <div> + <Paper className={classNames(classes.root, noMargin && classes.noMargin, colorMode && classes.colorMode)} elevation={4}> + <Typography variant="h6" component="h2" className={classes.title}> + {title} + </Typography> + <Typography component="p" className={classes.description}> + {desc} + </Typography> + <section className={classNames(classes.content, whiteBg && classes.whiteBg, overflowX && classes.overflowX)}> + {children} + </section> + </Paper> + </div> + ); +} + +PaperSheet.propTypes = { + classes: PropTypes.object.isRequired, + title: PropTypes.string.isRequired, + desc: PropTypes.string.isRequired, + children: PropTypes.node.isRequired, + whiteBg: PropTypes.bool, + colorMode: PropTypes.bool, + noMargin: PropTypes.bool, + overflowX: PropTypes.bool, +}; + +PaperSheet.defaultProps = { + whiteBg: false, + noMargin: false, + colorMode: false, + overflowX: false +}; + +export default withStyles(styles)(PaperSheet); diff --git a/front/odiparpack/app/components/PapperBlock/papperStyle-jss.js b/front/odiparpack/app/components/PapperBlock/papperStyle-jss.js new file mode 100644 index 0000000..521e349 --- /dev/null +++ b/front/odiparpack/app/components/PapperBlock/papperStyle-jss.js @@ -0,0 +1,58 @@ +const styles = theme => ({ + root: theme.mixins.gutters({ + paddingTop: theme.spacing(3), + paddingBottom: theme.spacing(3), + marginTop: theme.spacing(3), + '&$noMargin': { + margin: 0 + }, + }), + title: { + marginBottom: theme.spacing(4), + paddingBottom: theme.spacing(2), + position: 'relative', + textTransform: 'capitalize', + fontSize: 28, + '&:after': { + content: '""', + display: 'block', + position: 'absolute', + bottom: 0, + left: 0, + width: 40, + borderBottom: `4px solid ${theme.palette.primary.main}` + } + }, + description: { + maxWidth: 960, + fontSize: 16, + }, + content: { + marginTop: theme.spacing(2), + padding: theme.spacing(1), + backgroundColor: theme.palette.background.default, + }, + whiteBg: { + backgroundColor: 'transparent', + margin: 0, + }, + noMargin: {}, + colorMode: { + backgroundColor: theme.palette.secondary.main, + '& $title': { + color: theme.palette.grey[100], + '&:after': { + borderBottom: `5px solid ${theme.palette.primary.light}` + } + }, + '& $description': { + color: theme.palette.grey[100], + } + }, + overflowX: { + width: '100%', + overflowX: 'auto', + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/Profile/About.js b/front/odiparpack/app/components/Profile/About.js new file mode 100644 index 0000000..9872a47 --- /dev/null +++ b/front/odiparpack/app/components/Profile/About.js @@ -0,0 +1,243 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { withStyles } from '@material-ui/core/styles'; +import LocalPhone from '@material-ui/icons/LocalPhone'; +import DateRange from '@material-ui/icons/DateRange'; +import LocationOn from '@material-ui/icons/LocationOn'; +import InfoIcon from '@material-ui/icons/Info'; +import Check from '@material-ui/icons/Check'; +import AcUnit from '@material-ui/icons/AcUnit'; +import Adb from '@material-ui/icons/Adb'; +import AllInclusive from '@material-ui/icons/AllInclusive'; +import AssistantPhoto from '@material-ui/icons/AssistantPhoto'; +import imgData from 'ba-api/imgData'; +import Type from 'ba-styles/Typography.scss'; +import { + Grid, + Paper, + Typography, + List, + ListItem, + ListItemText, + ListItemAvatar, + Avatar, + Button, + LinearProgress, + Divider, + Chip, + GridList, + GridListTile, + GridListTileBar, + IconButton, +} from '@material-ui/core'; +import Timeline from '../SocialMedia/Timeline'; +import PapperBlock from '../PapperBlock/PapperBlock'; +import styles from './profile-jss'; + + +class About extends React.Component { + render() { + const { classes, data } = this.props; + return ( + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={3} + > + <Grid item md={7} xs={12}> + <div> + <Timeline dataTimeline={data} /> + </div> + </Grid> + <Grid item md={5} xs={12}> + {/* Profile Progress */} + <div className={classes.progressRoot}> + <Paper className={classes.styledPaper} elevation={4}> + <Typography className={classes.title} variant="h5" component="h3"> + <span className={Type.light}>Profile Strength: </span> + <span className={Type.bold}>Intermediate</span> + </Typography> + <Grid container justify="center"> + <Chip + avatar={( + <Avatar> + <Check /> + </Avatar> + )} + label="60% Progress" + className={classes.chip} + color="primary" + /> + </Grid> + <LinearProgress variant="determinate" className={classes.progress} value={60} /> + </Paper> + </div> + {/* ----------------------------------------------------------------------*/} + {/* About Me */} + <PapperBlock title="About Me" whiteBg noMargin desc="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum."> + <Divider className={classes.divider} /> + <List dense className={classes.profileList}> + <ListItem> + <ListItemAvatar> + <Avatar> + <DateRange /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Born" secondary="Jan 9, 1994" /> + </ListItem> + <ListItem> + <ListItemAvatar> + <Avatar> + <LocalPhone /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Phone" secondary="(+62)8765432190" /> + </ListItem> + <ListItem> + <ListItemAvatar> + <Avatar> + <LocationOn /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Address" secondary="Chicendo Street no.105 Block A/5A - Barcelona, Spain" /> + </ListItem> + </List> + </PapperBlock> + {/* ----------------------------------------------------------------------*/} + {/* My Albums */} + <PapperBlock title="My Albums (6)" whiteBg desc=""> + <div className={classes.albumRoot}> + <GridList cellHeight={180} className={classes.gridList}> + { + imgData.map((tile, index) => { + if (index >= 4) { + return false; + } + return ( + <GridListTile key={index.toString()}> + <img src={tile.img} className={classes.img} alt={tile.title} /> + <GridListTileBar + title={tile.title} + subtitle={( + <span> +by: + {tile.author} + </span> + )} + actionIcon={( + <IconButton className={classes.icon}> + <InfoIcon /> + </IconButton> + )} + /> + </GridListTile> + ); + }) + } + </GridList> + </div> + <Divider className={classes.divider} /> + <Grid container justify="center"> + <Button color="secondary" className={classes.button}> + See All + </Button> + </Grid> + </PapperBlock> + {/* ----------------------------------------------------------------------*/} + {/* My Connection Me */} + <PapperBlock title="My Connection" whiteBg desc=""> + <List dense className={classes.profileList}> + <ListItem button> + <ListItemAvatar> + <Avatar className={classNames(classes.avatar, classes.orangeAvatar)}>H</Avatar> + </ListItemAvatar> + <ListItemText primary="Harry Wells" secondary="2 Mutual Connection" /> + </ListItem> + <ListItem button> + <ListItemAvatar> + <Avatar className={classNames(classes.avatar, classes.purpleAvatar)}>J</Avatar> + </ListItemAvatar> + <ListItemText primary="John DOe" secondary="8 Mutual Connection" /> + </ListItem> + <ListItem button> + <ListItemAvatar> + <Avatar className={classNames(classes.avatar, classes.pinkAvatar)}>V</Avatar> + </ListItemAvatar> + <ListItemText primary="Victor Wanggai" secondary="12 Mutual Connection" /> + </ListItem> + <ListItem button> + <ListItemAvatar> + <Avatar className={classNames(classes.avatar, classes.greenAvatar)}>H</Avatar> + </ListItemAvatar> + <ListItemText primary="Baron Phoenix" secondary="10 Mutual Connection" /> + </ListItem> + </List> + <Divider className={classes.divider} /> + <Grid container justify="center"> + <Button color="secondary" className={classes.button}> + See All + </Button> + </Grid> + </PapperBlock> + {/* ----------------------------------------------------------------------*/} + {/* My Interests */} + <PapperBlock title="My Interests" whiteBg desc=""> + <Grid container className={classes.colList}> + <Grid item md={6}> + <ListItem> + <ListItemAvatar> + <Avatar className={classNames(classes.avatar, classes.purpleAvatar)}> + <AcUnit /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Snow" secondary="100 Connected" /> + </ListItem> + </Grid> + <Grid item md={6}> + <ListItem> + <ListItemAvatar> + <Avatar className={classNames(classes.avatar, classes.greenAvatar)}> + <Adb /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Android" secondary="120 Connected" /> + </ListItem> + </Grid> + <Grid item md={6}> + <ListItem> + <ListItemAvatar> + <Avatar className={classNames(classes.avatar, classes.pinkAvatar)}> + <AllInclusive /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="All Inclusive" secondary="999+ Connected" /> + </ListItem> + </Grid> + <Grid item md={6}> + <ListItem> + <ListItemAvatar> + <Avatar className={classNames(classes.avatar, classes.orangeAvatar)}> + <AssistantPhoto /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="My Country" secondary="99+ Connected" /> + </ListItem> + </Grid> + </Grid> + </PapperBlock> + {/* ----------------------------------------------------------------------*/} + </Grid> + </Grid> + ); + } +} + +About.propTypes = { + classes: PropTypes.object.isRequired, + data: PropTypes.object.isRequired +}; + +export default withStyles(styles)(About); diff --git a/front/odiparpack/app/components/Profile/Albums.js b/front/odiparpack/app/components/Profile/Albums.js new file mode 100644 index 0000000..dbb6d19 --- /dev/null +++ b/front/odiparpack/app/components/Profile/Albums.js @@ -0,0 +1,152 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import imgData from 'ba-api/imgData'; +import { Grid, GridList, GridListTile, ButtonBase, Typography } from '@material-ui/core'; +import styles from './profile-jss'; + + +function Albums(props) { + const { classes } = props; + + return ( + <div className={classes.root}> + <Grid + container + direction="row" + spacing={3} + > + <Grid item md={6} sm={12} xs={12}> + <ButtonBase + focusRipple + className={classes.image} + focusVisibleClassName={classes.focusVisible} + > + <GridList cellHeight={160} className={classes.gridList} cols={3}> + {imgData.map((tile, index) => { + if (index > 6) { + return false; + } + return ( + <GridListTile key={index.toString()} cols={tile.cols || 1}> + <img src={tile.img} className={classes.img} alt={tile.title} /> + </GridListTile> + ); + })} + </GridList> + <span className={classes.imageBackdrop} /> + <span className={classes.imageButton}> + <Typography + component="span" + variant="subtitle1" + color="inherit" + className={classes.imageTitle} + > + Album Number One + <span className={classes.imageMarked} /> + </Typography> + </span> + </ButtonBase> + <ButtonBase + focusRipple + className={classes.image} + focusVisibleClassName={classes.focusVisible} + > + <GridList cellHeight={160} className={classes.gridListAlbum} cols={3}> + {imgData.map((tile, index) => { + if (index > 2 && index < 9) { + return false; + } + return ( + <GridListTile key={index.toString()} cols={tile.cols || 1}> + <img src={tile.img} className={classes.img} alt={tile.title} /> + </GridListTile> + ); + })} + </GridList> + <span className={classes.imageBackdrop} /> + <span className={classes.imageButton}> + <Typography + component="span" + variant="subtitle1" + color="inherit" + className={classes.imageTitle} + > + Album Number Three + <span className={classes.imageMarked} /> + </Typography> + </span> + </ButtonBase> + </Grid> + <Grid item md={6} sm={12} xs={12}> + <ButtonBase + focusRipple + className={classes.image} + focusVisibleClassName={classes.focusVisible} + > + <GridList cellHeight={160} className={classes.gridList} cols={3}> + {imgData.map((tile, index) => { + if (index > 4 && index < 10) { + return false; + } + return ( + <GridListTile key={index.toString()} cols={tile.cols || 1}> + <img src={tile.img} className={classes.img} alt={tile.title} /> + </GridListTile> + ); + })} + </GridList> + <span className={classes.imageBackdrop} /> + <span className={classes.imageButton}> + <Typography + component="span" + variant="subtitle1" + color="inherit" + className={classes.imageTitle} + > + Album Number Two + <span className={classes.imageMarked} /> + </Typography> + </span> + </ButtonBase> + <ButtonBase + focusRipple + className={classes.image} + focusVisibleClassName={classes.focusVisible} + > + <GridList cellHeight={160} className={classes.gridList} cols={3}> + {imgData.map((tile, index) => { + if (index % 2) { + return false; + } + return ( + <GridListTile key={index.toString()} cols={tile.cols || 1}> + <img src={tile.img} className={classes.img} alt={tile.title} /> + </GridListTile> + ); + })} + </GridList> + <span className={classes.imageBackdrop} /> + <span className={classes.imageButton}> + <Typography + component="span" + variant="subtitle1" + color="inherit" + className={classes.imageTitle} + > + Album Number Four + <span className={classes.imageMarked} /> + </Typography> + </span> + </ButtonBase> + </Grid> + </Grid> + </div> + ); +} + +Albums.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Albums); diff --git a/front/odiparpack/app/components/Profile/Connection.js b/front/odiparpack/app/components/Profile/Connection.js new file mode 100644 index 0000000..beef128 --- /dev/null +++ b/front/odiparpack/app/components/Profile/Connection.js @@ -0,0 +1,45 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import datas from 'ba-api/connectionData'; +import { Grid } from '@material-ui/core'; +import ProfileCard from '../CardPaper/ProfileCard'; +import styles from './profile-jss'; + +class Connection extends React.Component { + render() { + const { classes } = this.props; + return ( + <Grid + container + alignItems="flex-start" + justify="space-between" + direction="row" + spacing={2} + className={classes.rootx} + > + { + datas.map((data, index) => ( + <Grid item md={4} sm={6} xs={12} key={index.toString()}> + <ProfileCard + cover={data.cover} + avatar={data.avatar} + name={data.name} + title={data.title} + connection={data.connection} + isVerified={data.verified} + btnText="See Profile" + /> + </Grid> + )) + } + </Grid> + ); + } +} + +Connection.propTypes = { + classes: PropTypes.object.isRequired +}; + +export default withStyles(styles)(Connection); diff --git a/front/odiparpack/app/components/Profile/Favorites.js b/front/odiparpack/app/components/Profile/Favorites.js new file mode 100644 index 0000000..ac6b506 --- /dev/null +++ b/front/odiparpack/app/components/Profile/Favorites.js @@ -0,0 +1,130 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import imgApi from 'ba-api/images'; +import avatarApi from 'ba-api/avatars'; +import { Typography, Grid, Divider } from '@material-ui/core'; +import GeneralCard from '../CardPaper/GeneralCard'; +import PostCard from '../CardPaper/PostCard'; +import Quote from '../Quote/Quote'; + + +const styles = theme => ({ + divider: { + margin: `${theme.spacing(3)}px 0`, + }, +}); + +class Favorites extends React.Component { + render() { + const { classes } = this.props; + const bull = <span className={classes.bullet}>•</span>; + return ( + <Grid + container + justify="center" + direction="row" + spacing={3} + > + <Grid item md={6}> + <PostCard + liked={1} + shared={20} + commented={15} + date="Sept, 25 2018" + content="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum." + image={imgApi[5]} + avatar={avatarApi[6]} + name="John Doe" + /> + <Divider className={classes.divider} /> + <GeneralCard liked={1} shared={20} commented={15}> + <Typography className={classes.title} color="textSecondary"> + Word of the Day + </Typography> + <Typography variant="h5" component="h2"> + be + {bull} +nev + {bull} +o + {bull} +lent + </Typography> + <Typography className={classes.pos} color="textSecondary"> + adjective + </Typography> + <Typography component="p"> + well meaning and kindly. + <br /> + {'"a benevolent smile"'} + </Typography> + </GeneralCard> + <Divider className={classes.divider} /> + <PostCard + liked={1} + shared={20} + commented={15} + date="Sept, 25 2018" + content="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum." + image={imgApi[16]} + avatar={avatarApi[6]} + name="John Doe" + /> + <Divider className={classes.divider} /> + <PostCard + liked={90} + shared={10} + commented={22} + date="Sept, 15 2018" + content="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum." + avatar={avatarApi[5]} + name="Jane Doe" + /> + </Grid> + <Grid item md={6}> + <PostCard + liked={90} + shared={10} + commented={22} + date="Sept, 15 2018" + content="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum." + avatar={avatarApi[4]} + name="Jane Doe" + /> + <Divider className={classes.divider} /> + <PostCard + liked={1} + shared={20} + commented={15} + date="Sept, 25 2018" + content="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum." + image={imgApi[20]} + avatar={avatarApi[6]} + name="John Doe" + /> + <Divider className={classes.divider} /> + <GeneralCard liked={1} shared={20} commented={15}> + <Quote align="left" content="Imagine all the people living life in peace. You may say I'm a dreamer, but I'm not the only one. I hope someday you'll join us, and the world will be as one." footnote="John Lennon" /> + </GeneralCard> + <Divider className={classes.divider} /> + <PostCard + liked={90} + shared={10} + commented={22} + date="Sept, 15 2018" + content="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum." + avatar={avatarApi[1]} + name="Jane Doe" + /> + </Grid> + </Grid> + ); + } +} + +Favorites.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Favorites); diff --git a/front/odiparpack/app/components/Profile/profile-jss.js b/front/odiparpack/app/components/Profile/profile-jss.js new file mode 100644 index 0000000..32c3308 --- /dev/null +++ b/front/odiparpack/app/components/Profile/profile-jss.js @@ -0,0 +1,155 @@ +import { deepOrange, deepPurple, pink, green } from '@material-ui/core/colors'; +const styles = theme => ({ + profileList: { + padding: 0, + '& li': { + paddingLeft: 0 + } + }, + avatar: { + margin: 10, + }, + orangeAvatar: { + backgroundColor: deepOrange[500], + }, + purpleAvatar: { + backgroundColor: deepPurple[500], + }, + pinkAvatar: { + backgroundColor: pink[500], + }, + greenAvatar: { + backgroundColor: green[500], + }, + divider: { + margin: `${theme.spacing(3)}px 0`, + }, + albumRoot: { + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'space-around', + overflow: 'hidden', + backgroundColor: theme.palette.background.paper, + }, + gridList: { + width: 500, + height: 'auto', + }, + icon: { + color: 'rgba(255, 255, 255, 0.54)', + }, + img: { + maxWidth: 'none' + }, + root: theme.mixins.gutters({ + paddingTop: 16, + paddingBottom: 16, + marginTop: theme.spacing(3), + }), + progressRoot: { + marginBottom: 30, + }, + styledPaper: { + backgroundColor: theme.palette.secondary.main, + padding: 20, + '& $title, & $subtitle': { + color: theme.palette.common.white + } + }, + progress: { + marginTop: 20, + background: theme.palette.secondary.dark, + '& div': { + background: theme.palette.primary.light, + } + }, + chip: { + marginTop: 20, + background: theme.palette.primary.light, + '& div': { + background: green[500], + color: theme.palette.common.white + } + }, + colList: { + '& li': { + padding: '10px 0' + }, + '& $avatar': { + margin: 0 + } + }, + title: {}, + subtitle: {}, + rootAlbum: { + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'space-around', + overflow: 'hidden', + }, + image: { + position: 'relative', + height: 'auto', + boxShadow: theme.shadows[6], + borderRadius: 2, + overflow: 'hidden', + marginBottom: 30, + width: '100% !important', // Overrides inline-style + '&:hover, &$focusVisible': { + zIndex: 1, + '& $imageBackdrop': { + opacity: 0.6, + }, + '& $imageMarked': { + opacity: 0, + }, + '& $imageTitle': { + border: '4px solid currentColor', + }, + }, + }, + imageButton: { + position: 'absolute', + left: 0, + right: 0, + top: 0, + bottom: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + color: theme.palette.common.white, + }, + imageBackdrop: { + position: 'absolute', + left: 0, + right: 0, + top: 0, + bottom: 0, + backgroundColor: theme.palette.common.black, + opacity: 0.4, + transition: theme.transitions.create('opacity'), + }, + imageTitle: { + position: 'relative', + padding: `${theme.spacing(2)}px ${theme.spacing(4)}px ${theme.spacing(1) + 6}px`, + }, + imageMarked: { + height: 3, + width: 18, + backgroundColor: theme.palette.common.white, + position: 'absolute', + bottom: -2, + left: 'calc(50% - 9px)', + transition: theme.transitions.create('opacity'), + }, + focusVisible: {}, + gridListAlbum: { + height: 'auto', + background: theme.palette.common.black + }, + subheader: { + width: '100%', + }, +}); + +export default styles; diff --git a/front/odiparpack/app/components/Quote/Quote.js b/front/odiparpack/app/components/Quote/Quote.js new file mode 100644 index 0000000..21665d5 --- /dev/null +++ b/front/odiparpack/app/components/Quote/Quote.js @@ -0,0 +1,81 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import { blueGrey } from '@material-ui/core/colors'; +import { Typography } from '@material-ui/core'; + +const styles = ({ + quoteWrap: { + padding: '0 25', + margin: 10, + position: 'relative', + '&:before': { + color: blueGrey[100], + fontSize: '4em', + lineHeight: '.1em', + marginRight: '.25em', + verticalAlign: '-.4em' + } + }, + quoteLeft: { + extend: 'quoteWrap', + textAlign: 'left', + borderLeft: '5px solid' + blueGrey[50], + paddingLeft: 25, + '&:before': { + content: 'open-quote', + } + }, + quoteRight: { + extend: 'quoteWrap', + textAlign: 'right', + borderRight: '5px solid' + blueGrey[50], + paddingRight: 25, + '&:before': { + content: 'close-quote', + } + }, + quoteBody: { + minHeight: 100, + marginBottom: 20 + } +}); + + +class Quote extends React.Component { + render() { + const { + align, + content, + footnote, + classes + } = this.props; + return ( + <div + className={ + classNames( + classes.quoteWrap, + align === 'right' ? classes.quoteRight : classes.quoteLeft + ) + } + > + <Typography variant="subtitle1" className={classes.quoteBody} gutterBottom> + {content} + </Typography> + <Typography variant="caption"> + {footnote} + </Typography> + </div> + ); + } +} + +Quote.propTypes = { + align: PropTypes.string.isRequired, + content: PropTypes.string.isRequired, + footnote: PropTypes.string.isRequired, + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Quote); diff --git a/front/odiparpack/app/components/Rating/Rating.js b/front/odiparpack/app/components/Rating/Rating.js new file mode 100644 index 0000000..6f65d06 --- /dev/null +++ b/front/odiparpack/app/components/Rating/Rating.js @@ -0,0 +1,144 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import ToggleStar from '@material-ui/icons/Star'; +import ToggleStarBorder from '@material-ui/icons/StarBorder'; +import { orange, grey } from '@material-ui/core/colors'; + +import { IconButton } from '@material-ui/core'; + +const styles = { + disabled: { + pointerEvents: 'none' + } +}; + +class Rating extends Component { + constructor(props) { + super(props); + this.state = { + hoverValue: props.value + }; + } + + renderIcon(i) { + const filled = i <= this.props.value; + const hovered = i <= this.state.hoverValue; + + if ((hovered && !filled) || (!hovered && filled)) { + return this.props.iconHoveredRenderer ? this.props.iconHoveredRenderer({ + ...this.props, + index: i + }) : this.props.iconHovered; + } if (filled) { + return this.props.iconFilledRenderer ? this.props.iconFilledRenderer({ + ...this.props, + index: i + }) : this.props.iconFilled; + } + return this.props.iconNormalRenderer ? this.props.iconNormalRenderer({ + ...this.props, + index: i + }) : this.props.iconNormal; + } + + render() { + const { + disabled, + iconFilled, + iconHovered, + iconNormal, + tooltip, + tooltipRenderer, + tooltipPosition, + tooltipStyles, + iconFilledRenderer, + iconHoveredRenderer, + iconNormalRenderer, + itemStyle, + itemClassName, + itemIconStyle, + max, + onChange, + readOnly, + style, + value, + ...other + } = this.props; + + const rating = []; + + for (let i = 1; i <= max; i += 1) { + rating.push( + <IconButton + key={i} + className={itemClassName} + disabled={disabled} + onMouseEnter={() => this.setState({ hoverValue: i })} + onMouseLeave={() => this.setState({ hoverValue: value })} + onClick={() => { + if (!readOnly && onChange) { + onChange(i); + } + }} + > + {this.renderIcon(i)} + </IconButton> + ); + } + + return ( + <div + style={this.props.disabled || this.props.readOnly ? { ...styles.disabled, ...this.props.style } : this.props.style} + {...other} + > + {rating} + </div> + ); + } +} + +Rating.propTypes = { + disabled: PropTypes.bool, + iconFilled: PropTypes.node, + iconHovered: PropTypes.node, + iconNormal: PropTypes.node, + tooltip: PropTypes.node, + tooltipRenderer: PropTypes.func, + tooltipPosition: PropTypes.string, + tooltipStyles: PropTypes.object, + iconFilledRenderer: PropTypes.func, + iconHoveredRenderer: PropTypes.func, + iconNormalRenderer: PropTypes.func, + itemStyle: PropTypes.object, + itemClassName: PropTypes.object, + itemIconStyle: PropTypes.object, + max: PropTypes.number, + onChange: PropTypes.func, + readOnly: PropTypes.bool, + style: PropTypes.object, + value: PropTypes.number +}; + +Rating.defaultProps = { + disabled: false, + iconFilled: <ToggleStar style={{ color: orange[500] }} />, + iconHovered: <ToggleStarBorder style={{ color: orange[500] }} />, + iconNormal: <ToggleStarBorder style={{ color: grey[300] }} />, + tooltipPosition: 'bottom-center', + max: 5, + readOnly: false, + value: 0, + tooltip: null, + tooltipRenderer: () => {}, + tooltipStyles: null, + iconFilledRenderer: undefined, + iconHoveredRenderer: undefined, + iconNormalRenderer: undefined, + itemStyle: undefined, + itemClassName: undefined, + itemIconStyle: undefined, + onChange: () => {}, + style: null, +}; + +export default Rating; diff --git a/front/odiparpack/app/components/Search/SearchProduct.js b/front/odiparpack/app/components/Search/SearchProduct.js new file mode 100644 index 0000000..b465808 --- /dev/null +++ b/front/odiparpack/app/components/Search/SearchProduct.js @@ -0,0 +1,84 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import ShoppingCartIcon from '@material-ui/icons/ShoppingCart'; +import SearchIcon from '@material-ui/icons/Search'; +import { AppBar, Toolbar, IconButton, Badge } from '@material-ui/core'; +import Cart from '../Cart/Cart'; +import styles from './search-jss'; + + +class SearchProduct extends React.Component { + state = { + anchorEl: null, + }; + + handleClick = event => { + this.setState({ anchorEl: event.currentTarget }); + }; + + handleClose = () => { + this.setState({ anchorEl: null }); + }; + + render() { + const { anchorEl } = this.state; + const { + classes, + dataCart, + removeItem, + checkout, + totalItems, + totalPrice, + search + } = this.props; + return ( + <div className={classes.root}> + <AppBar position="static" color="inherit"> + <Toolbar> + <div className={classes.flex}> + <div className={classes.wrapper}> + <div className={classes.search}> + <SearchIcon /> + </div> + <input className={classes.input} placeholder="Search Product" onChange={(event) => search(event)} /> + </div> + </div> + <div> + <IconButton + color="inherit" + aria-owns={anchorEl ? 'simple-menu' : null} + aria-haspopup="true" + onClick={this.handleClick} + > + <Badge badgeContent={totalItems} color="secondary"> + <ShoppingCartIcon /> + </Badge> + </IconButton> + <Cart + anchorEl={anchorEl} + dataCart={dataCart} + close={this.handleClose} + removeItem={removeItem} + checkout={checkout} + totalPrice={totalPrice} + /> + </div> + </Toolbar> + </AppBar> + </div> + ); + } +} + +SearchProduct.propTypes = { + classes: PropTypes.object.isRequired, + dataCart: PropTypes.object.isRequired, + removeItem: PropTypes.func.isRequired, + search: PropTypes.func.isRequired, + checkout: PropTypes.func.isRequired, + totalItems: PropTypes.number.isRequired, + totalPrice: PropTypes.number.isRequired, +}; + +export default withStyles(styles)(SearchProduct); diff --git a/front/odiparpack/app/components/Search/search-jss.js b/front/odiparpack/app/components/Search/search-jss.js new file mode 100644 index 0000000..5ba6ee0 --- /dev/null +++ b/front/odiparpack/app/components/Search/search-jss.js @@ -0,0 +1,48 @@ +const styles = theme => ({ + root: { + flexGrow: 1, + marginTop: 20, + marginBottom: 40 + }, + flex: { + flex: 1, + }, + menuButton: { + marginLeft: -12, + marginRight: 20, + }, + wrapper: { + fontFamily: theme.typography.fontFamily, + position: 'relative', + marginRight: theme.spacing(2), + marginLeft: theme.spacing(1), + borderRadius: 2, + display: 'block', + }, + search: { + width: 'auto', + height: '100%', + position: 'absolute', + pointerEvents: 'none', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + input: { + font: 'inherit', + padding: `${theme.spacing(1)}px ${theme.spacing(1)}px ${theme.spacing(1)}px ${theme.spacing(4)}px`, + border: 0, + display: 'block', + verticalAlign: 'middle', + whiteSpace: 'normal', + background: 'none', + margin: 0, // Reset for Safari + color: 'inherit', + width: '100%', + '&:focus': { + outline: 0, + }, + }, +}); + +export default styles; diff --git a/front/odiparpack/app/components/Sidebar/MainMenu.js b/front/odiparpack/app/components/Sidebar/MainMenu.js new file mode 100644 index 0000000..edeb420 --- /dev/null +++ b/front/odiparpack/app/components/Sidebar/MainMenu.js @@ -0,0 +1,130 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { NavLink } from 'react-router-dom'; +import ExpandLess from '@material-ui/icons/ExpandLess'; +import ExpandMore from '@material-ui/icons/ExpandMore'; +// Menu Object +import MenuContent from 'ba-api/menu'; +import { List, ListItem, ListItemIcon, ListItemText, Collapse, Icon } from '@material-ui/core'; +import styles from './sidebar-jss'; + +function sortByKey(array, key) { + return array.sort((a, b) => { + const x = a[key]; const y = b[key]; + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + }); +} + +const LinkBtn = React.forwardRef(function LinkBtn(props, ref) { // eslint-disable-line + return <NavLink to={props.to} {...props} innerRef={ref} />; // eslint-disable-line +}); + +function MainMenu(props) { + const { + classes, + toggleDrawerOpen, + loadTransition, + openSubMenu, + open, + } = props; + + const handleClick = () => { + toggleDrawerOpen(); + loadTransition(false); + }; + + const getMenus = menuArray => menuArray.map((item, index) => { + if (item.child) { + return ( + <div key={index.toString()}> + <ListItem + button + className={classNames(classes.head, open.indexOf(item.key) > -1 ? classes.opened : '')} + onClick={() => openSubMenu(item.key, item.keyParent)} + > + {item.icon + && ( + <ListItemIcon className={classes.iconWrapper}> + <Icon className={classes.icon}>{item.icon}</Icon> + </ListItemIcon> + ) + } + <ListItemText classes={{ primary: classes.primary }} variant="inset" primary={item.name} /> + { open.indexOf(item.key) > -1 ? <ExpandLess /> : <ExpandMore /> } + </ListItem> + <Collapse + component="li" + className={classNames( + classes.nolist, + (item.keyParent ? classes.child : ''), + )} + in={open.indexOf(item.key) > -1} + timeout="auto" + unmountOnExit + > + <List className={classes.dense} dense> + { getMenus(sortByKey(item.child, 'key')) } + </List> + </Collapse> + </div> + ); + } + return ( + <ListItem + key={index.toString()} + button + exact + className={classes.nested} + activeClassName={classes.active} + component={LinkBtn} + to={item.link} + onClick={handleClick} + > + {item.icon + && ( + <ListItemIcon> + <Icon className={classes.icon}>{item.icon}</Icon> + </ListItemIcon> + ) + } + <ListItemText classes={{ primary: classes.primary }} inset primary={item.name} /> + </ListItem> + ); + }); + return ( + <div> + {getMenus(MenuContent)} + </div> + ); +} + +MainMenu.propTypes = { + classes: PropTypes.object.isRequired, + open: PropTypes.object.isRequired, + openSubMenu: PropTypes.func.isRequired, + toggleDrawerOpen: PropTypes.func.isRequired, + loadTransition: PropTypes.func.isRequired, +}; + +const openAction = (key, keyParent) => ({ type: 'OPEN_SUBMENU', key, keyParent }); +const reducer = 'ui'; + +const mapStateToProps = state => ({ + force: state, // force active class for sidebar menu + open: state.getIn([reducer, 'subMenuOpen']) +}); + +const mapDispatchToProps = dispatch => ({ + openSubMenu: bindActionCreators(openAction, dispatch) +}); + +const MainMenuMapped = connect( + mapStateToProps, + mapDispatchToProps +)(MainMenu); + +export default withStyles(styles)(MainMenuMapped); diff --git a/front/odiparpack/app/components/Sidebar/OtherMenu.js b/front/odiparpack/app/components/Sidebar/OtherMenu.js new file mode 100644 index 0000000..ea2f597 --- /dev/null +++ b/front/odiparpack/app/components/Sidebar/OtherMenu.js @@ -0,0 +1,44 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { NavLink } from 'react-router-dom'; +import OtherMenuContent from 'ba-api/otherMenu'; +import { ListItem, ListItemText } from '@material-ui/core'; +import styles from './sidebar-jss'; + +const LinkBtn = React.forwardRef(function LinkBtn(props, ref) { // eslint-disable-line + return <NavLink to={props.to} {...props} innerRef={ref} />; // eslint-disable-line +}); + +function OtherMenu(props) { + const { toggleDrawerOpen, classes } = props; + const getOtherMenu = menuArray => menuArray.map((item, index) => { + const keyIndex = index.toString(); + return ( + <div key={keyIndex}> + <ListItem + button + component={LinkBtn} + to={item.link} + activeClassName={classes.active} + onClick={toggleDrawerOpen} + > + <ListItemText secondary={item.name} /> + </ListItem> + </div> + ); + }); + + return ( + <div> + {getOtherMenu(OtherMenuContent)} + </div> + ); +} + +OtherMenu.propTypes = { + classes: PropTypes.object.isRequired, + toggleDrawerOpen: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(OtherMenu); diff --git a/front/odiparpack/app/components/Sidebar/Sidebar.js b/front/odiparpack/app/components/Sidebar/Sidebar.js new file mode 100644 index 0000000..01de4ec --- /dev/null +++ b/front/odiparpack/app/components/Sidebar/Sidebar.js @@ -0,0 +1,122 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import brand from 'ba-api/brand'; +import dummy from 'ba-api/dummyContents'; +import logo from 'ba-images/logo.svg'; +import { Hidden, Drawer, SwipeableDrawer, List, Divider, Avatar } from '@material-ui/core'; +import MainMenu from './MainMenu'; +import OtherMenu from './OtherMenu'; +import styles from './sidebar-jss'; + +const MenuContent = props => { + const { + classes, + turnDarker, + drawerPaper, + toggleDrawerOpen, + loadTransition + } = props; + return ( + <div className={classNames(classes.drawerInner, !drawerPaper ? classes.drawerPaperClose : '')}> + <div className={classes.drawerHeader}> + <div className={classNames(classes.brand, classes.brandBar, turnDarker && classes.darker)}> + <img src={logo} alt={brand.name} /> + <h3>{brand.name}</h3> + </div> + <div className={classNames(classes.profile, classes.user)}> + <Avatar + alt={dummy.user.name} + src={dummy.user.avatar} + className={classNames(classes.avatar, classes.bigAvatar)} + /> + <div> + <h4>{dummy.user.name}</h4> + <span>{dummy.user.title}</span> + </div> + </div> + </div> + <div className={classes.menuContainer}> + <MainMenu loadTransition={loadTransition} toggleDrawerOpen={toggleDrawerOpen} /> + <Divider className={classes.divider} /> + <List> + <OtherMenu toggleDrawerOpen={toggleDrawerOpen} /> + </List> + </div> + </div> + ); +}; + +MenuContent.propTypes = { + classes: PropTypes.object.isRequired, + drawerPaper: PropTypes.bool.isRequired, + turnDarker: PropTypes.bool, + toggleDrawerOpen: PropTypes.func, + loadTransition: PropTypes.func, +}; + +MenuContent.defaultProps = { + turnDarker: false +}; + +MenuContent.defaultProps = { + toggleDrawerOpen: () => {}, + loadTransition: () => {}, +}; + +const MenuContentStyle = withStyles(styles)(MenuContent); + +function Sidebar(props) { + const { + classes, + open, + toggleDrawerOpen, + loadTransition, + turnDarker + } = props; + + return ( + <Fragment> + <Hidden lgUp> + <SwipeableDrawer + onClose={toggleDrawerOpen} + onOpen={toggleDrawerOpen} + open={!open} + anchor="left" + > + <div className={classes.swipeDrawerPaper}> + <MenuContentStyle drawerPaper toggleDrawerOpen={toggleDrawerOpen} loadTransition={loadTransition} /> + </div> + </SwipeableDrawer> + </Hidden> + <Hidden mdDown> + <Drawer + variant="permanent" + onClose={toggleDrawerOpen} + classes={{ + paper: classNames(classes.drawer, classes.drawerPaper, !open ? classes.drawerPaperClose : ''), + }} + open={open} + anchor="left" + > + <MenuContentStyle + drawerPaper={open} + turnDarker={turnDarker} + loadTransition={loadTransition} + /> + </Drawer> + </Hidden> + </Fragment> + ); +} + +Sidebar.propTypes = { + classes: PropTypes.object.isRequired, + toggleDrawerOpen: PropTypes.func.isRequired, + loadTransition: PropTypes.func.isRequired, + turnDarker: PropTypes.bool.isRequired, + open: PropTypes.bool.isRequired, +}; + +export default withStyles(styles)(Sidebar); diff --git a/front/odiparpack/app/components/Sidebar/sidebar-jss.js b/front/odiparpack/app/components/Sidebar/sidebar-jss.js new file mode 100644 index 0000000..e9bf4f6 --- /dev/null +++ b/front/odiparpack/app/components/Sidebar/sidebar-jss.js @@ -0,0 +1,205 @@ +const drawerWidth = 240; +const styles = theme => ({ + user: { + justifyContent: 'center' + }, + drawerPaper: { + position: 'relative', + height: '100%', + overflow: 'hidden', + backgroundColor: theme.palette.background.default, + border: 'none', + width: drawerWidth, + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + }, + swipeDrawerPaper: { + width: drawerWidth, + }, + opened: { + background: theme.palette.grey[200], + '& $primary, & $icon': { + color: theme.palette.secondary.dark, + }, + }, + drawerInner: { + height: '100%', + position: 'fixed', + width: drawerWidth, + }, + drawerPaperClose: { + width: 66, + position: 'fixed', + overflowX: 'hidden', + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + '& $user': { + justifyContent: 'flex-start' + }, + '& $bigAvatar': { + width: 40, + height: 40, + }, + '& li ul': { + display: 'none' + }, + '&:hover': { + width: drawerWidth, + boxShadow: theme.shadows[6], + '& li ul': { + display: 'block' + } + }, + '& $menuContainer': { + paddingLeft: theme.spacing(1.5), + paddingRight: theme.spacing(1.5), + width: drawerWidth, + }, + '& $drawerInner': { + width: 'auto' + }, + '& $brandBar': { + opacity: 0 + } + }, + drawerHeader: { + background: theme.palette.primary.main, + color: theme.palette.primary.contrastText, + padding: '0', + ...theme.mixins.toolbar, + '& h3': { + color: theme.palette.primary.contrastText, + } + }, + avatar: { + margin: 10, + }, + bigAvatar: { + width: 80, + height: 80, + }, + brandBar: { + transition: theme.transitions.create(['width', 'margin', 'background'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + '&:after': { + transition: theme.transitions.create(['box-shadow'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + } + }, + darker: { + background: theme.palette.primary.dark, + }, + title: {}, + nested: { + paddingLeft: 0, + paddingTop: theme.spacing(0.5), + paddingBottom: theme.spacing(0.5), + '& > div > span': { + fontSize: '0.8125rem' + } + }, + child: { + '& a': { + paddingLeft: theme.spacing(3), + } + }, + dense: { + '& > $title:first-child': { + margin: '0' + }, + '& $head': { + paddingLeft: theme.spacing(7) + } + }, + active: { + backgroundColor: theme.palette.primary.light, + '& $primary, & $icon': { + color: theme.palette.secondary.dark, + }, + '&:hover': { + backgroundColor: theme.palette.primary.light, + } + }, + nolist: { + listStyle: 'none', + }, + primary: {}, + iconWrapper: { + width: theme.spacing(5), + minWidth: 0, + marginRight: 0, + marginLeft: theme.spacing(2) + }, + icon: { + marginRight: 0, + color: theme.palette.secondary.dark, + }, + head: { + paddingLeft: 0 + }, + brand: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + padding: '10px 10px 5px', + height: 64, + position: 'relative', + '& img': { + width: 20 + }, + '& h3': { + fontSize: 16, + margin: 0, + paddingLeft: 10, + fontWeight: 500 + } + }, + profile: { + height: 120, + display: 'flex', + fontSize: 14, + padding: 10, + alignItems: 'center', + '& h4': { + fontSize: 18, + marginBottom: 0, + textOverflow: 'ellipsis', + overflow: 'hidden', + whiteSpace: 'nowrap', + width: 110 + }, + '& span': { + fontSize: 12, + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + width: 110, + display: 'block', + overflow: 'hidden' + } + }, + menuContainer: { + padding: theme.spacing(1), + background: theme.palette.background.default, + [theme.breakpoints.up('lg')]: { + padding: theme.spacing(1.5), + }, + paddingRight: theme.spacing(1), + overflow: 'auto', + height: 'calc(100% - 185px)', + position: 'relative', + display: 'block' + }, + divider: { + marginTop: theme.spacing(1) + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/SocialMedia/Comment.js b/front/odiparpack/app/components/SocialMedia/Comment.js new file mode 100644 index 0000000..9ead6e7 --- /dev/null +++ b/front/odiparpack/app/components/SocialMedia/Comment.js @@ -0,0 +1,135 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import Type from 'ba-styles/Typography.scss'; +import { withStyles } from '@material-ui/core/styles'; +import Send from '@material-ui/icons/Send'; +import CommentIcon from '@material-ui/icons/Comment'; +import CloseIcon from '@material-ui/icons/Close'; +import dummy from 'ba-api/dummyContents'; +import { + Typography, + List, + ListItem, + Avatar, + Input, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + Fab, + Slide, + Divider, + withMobileDialog, +} from '@material-ui/core'; +import styles from './jss/socialMedia-jss'; + +const Transition = React.forwardRef(function Transition(props, ref) { // eslint-disable-line + return <Slide direction="up" ref={ref} {...props} />; +}); + +class Comment extends React.Component { + state = { + comment: '' + }; + + handleChange = event => { + this.setState({ comment: event.target.value }); + }; + + handleSubmit = comment => { + this.props.submitComment(comment); + this.setState({ comment: '' }); + } + + render() { + const { + open, + handleClose, + classes, + dataComment, + fullScreen + } = this.props; + const { comment } = this.state; + const getItem = dataArray => dataArray.map(data => ( + <Fragment key={data.get('id')}> + <ListItem> + <div className={classes.commentContent}> + <div className={classes.commentHead}> + <Avatar alt="avatar" src={data.get('avatar')} className={classes.avatar} /> + <section> + <Typography variant="subtitle1">{data.get('from')}</Typography> + <Typography variant="caption"><span className={classNames(Type.light, Type.textGrey)}>{data.get('date')}</span></Typography> + </section> + </div> + <Typography className={classes.commentText}>{data.get('message')}</Typography> + </div> + </ListItem> + <Divider variant="inset" /> + </Fragment> + )); + + return ( + <div> + <Dialog + fullScreen={fullScreen} + open={open} + onClose={handleClose} + aria-labelledby="form-dialog-title" + TransitionComponent={Transition} + maxWidth="md" + > + <DialogTitle id="form-dialog-title"> + <CommentIcon /> + {' '} + {dataComment !== undefined && dataComment.size} + Comment + {dataComment !== undefined && dataComment.size > 1 ? 's' : ''} + <IconButton onClick={handleClose} className={classes.buttonClose} aria-label="Close"> + <CloseIcon /> + </IconButton> + </DialogTitle> + <DialogContent> + <List> + {dataComment !== undefined && getItem(dataComment)} + </List> + </DialogContent> + <DialogActions className={classes.commentAction}> + <div className={classes.commentForm}> + <Avatar alt="avatar" src={dummy.user.avatar} className={classes.avatarMini} /> + <Input + placeholder="Write Comment" + onChange={this.handleChange} + value={comment} + className={classes.input} + inputProps={{ + 'aria-label': 'Comment', + }} + /> + <Fab size="small" onClick={() => this.handleSubmit(comment)} color="secondary" aria-label="send" className={classes.button}> + <Send /> + </Fab> + </div> + </DialogActions> + </Dialog> + </div> + ); + } +} + +Comment.propTypes = { + open: PropTypes.bool.isRequired, + handleClose: PropTypes.func.isRequired, + submitComment: PropTypes.func.isRequired, + classes: PropTypes.object.isRequired, + dataComment: PropTypes.object, + fullScreen: PropTypes.bool.isRequired, +}; + +Comment.defaultProps = { + dataComment: undefined +}; + +const CommentResponsive = withMobileDialog()(Comment); +export default withStyles(styles)(CommentResponsive); diff --git a/front/odiparpack/app/components/SocialMedia/Cover.js b/front/odiparpack/app/components/SocialMedia/Cover.js new file mode 100644 index 0000000..4823f13 --- /dev/null +++ b/front/odiparpack/app/components/SocialMedia/Cover.js @@ -0,0 +1,103 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import VerifiedUser from '@material-ui/icons/VerifiedUser'; +import Info from '@material-ui/icons/Info'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import { withStyles } from '@material-ui/core/styles'; +import { Avatar, Typography, Menu, MenuItem, Button, IconButton } from '@material-ui/core'; +import styles from './jss/cover-jss'; + + +const optionsOpt = [ + 'Edit Profile', + 'Change Cover', + 'Option 1', + 'Option 2', + 'Option 3', +]; + +const ITEM_HEIGHT = 48; + +class Cover extends React.Component { + state = { + anchorElOpt: null, + }; + + handleClickOpt = event => { + this.setState({ anchorElOpt: event.currentTarget }); + }; + + handleCloseOpt = () => { + this.setState({ anchorElOpt: null }); + }; + + render() { + const { + classes, + avatar, + name, + desc, + coverImg, + } = this.props; + const { anchorElOpt } = this.state; + return ( + <div className={classes.cover} style={{ backgroundImage: `url(${coverImg})` }}> + <div className={classes.opt}> + <IconButton className={classes.button} aria-label="Delete"> + <Info /> + </IconButton> + <IconButton + aria-label="More" + aria-owns={anchorElOpt ? 'long-menu' : null} + aria-haspopup="true" + className={classes.button} + onClick={this.handleClickOpt} + > + <MoreVertIcon /> + </IconButton> + <Menu + id="long-menu" + anchorEl={anchorElOpt} + open={Boolean(anchorElOpt)} + onClose={this.handleCloseOpt} + PaperProps={{ + style: { + maxHeight: ITEM_HEIGHT * 4.5, + width: 200, + }, + }} + > + {optionsOpt.map(option => ( + <MenuItem key={option} selected={option === 'Edit Profile'} onClick={this.handleCloseOpt}> + {option} + </MenuItem> + ))} + </Menu> + </div> + <div className={classes.content}> + <Avatar alt={name} src={avatar} className={classes.avatar} /> + <Typography variant="h4" className={classes.name} gutterBottom> + {name} + <VerifiedUser className={classes.verified} /> + </Typography> + <Typography className={classes.subheading} gutterBottom> + {desc} + </Typography> + <Button className={classes.button} size="large" variant="contained" color="secondary"> + Add to Connection + </Button> + </div> + </div> + ); + } +} + +Cover.propTypes = { + classes: PropTypes.object.isRequired, + avatar: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + desc: PropTypes.string.isRequired, + coverImg: PropTypes.string.isRequired, +}; + +export default withStyles(styles)(Cover); diff --git a/front/odiparpack/app/components/SocialMedia/SideSection.js b/front/odiparpack/app/components/SocialMedia/SideSection.js new file mode 100644 index 0000000..1fe8d9c --- /dev/null +++ b/front/odiparpack/app/components/SocialMedia/SideSection.js @@ -0,0 +1,209 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { withStyles } from '@material-ui/core/styles'; +import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft'; +import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight'; +import SwipeableViews from 'react-swipeable-views'; +import imgApi from 'ba-api/images'; +import avatarApi from 'ba-api/avatars'; +import { + Grid, + Typography, + MobileStepper, + Paper, + Avatar, + Button, + Divider, + List, + ListItem, + ListItemText, +} from '@material-ui/core'; +import PapperBlock from '../PapperBlock/PapperBlock'; +import NewsCard from '../CardPaper/NewsCard'; +import ProfileCard from '../CardPaper/ProfileCard'; +import styles from './jss/socialMedia-jss'; + + +const slideData = [ + { + label: 'How to be happy :)', + imgPath: imgApi[49], + }, + { + label: '1. Work with something that you like, like…', + imgPath: imgApi[17], + }, + { + label: '2. Keep your friends close to you and hangout with them', + imgPath: imgApi[34], + }, + { + label: '3. Travel everytime that you have a chance', + imgPath: imgApi[10], + }, + { + label: '4. And contribute to Material-UI :D', + imgPath: imgApi[40] + }, +]; + +class SideSection extends React.Component { + state = { + activeStepSwipe: 0, + }; + + handleNextSwipe = () => { + this.setState(prevState => ({ + activeStepSwipe: prevState.activeStepSwipe + 1, + })); + }; + + handleBackSwipe = () => { + this.setState(prevState => ({ + activeStepSwipe: prevState.activeStepSwipe - 1, + })); + }; + + handleStepChangeSwipe = activeStepSwipe => { + this.setState({ activeStepSwipe }); + }; + + render() { + const { classes, theme } = this.props; + const { activeStepSwipe } = this.state; + + const maxStepsSwipe = slideData.length; + return ( + <div> + {/* Profile */} + <ProfileCard + cover={imgApi[43]} + avatar={avatarApi[6]} + name="John Doe" + title="UX designer" + connection={10} + btnText="My Profile" + isVerified + /> + <Divider className={classes.divider} /> + {/* ----------------------------------------------------------------------*/} + {/* News Or Ads Block */} + <Paper> + <SwipeableViews + axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'} + index={this.state.activeStepSwipe} + onChangeIndex={this.handleStepChangeSwipe} + enableMouseEvents + className={classes.sliderWrap} + > + {slideData.map((slide, index) => ( + <div className={classes.figure} key={index.toString()}> + <NewsCard + image={slide.imgPath} + title="slide.label" + > + <Typography gutterBottom className={classes.title} variant="h5" component="h2"> + {slide.label} + </Typography> + </NewsCard> + </div> + ))} + </SwipeableViews> + <MobileStepper + variant="dots" + steps={maxStepsSwipe} + position="static" + activeStep={activeStepSwipe} + className={classes.mobileStepper} + nextButton={( + <Button size="small" onClick={this.handleNextSwipe} disabled={activeStepSwipe === maxStepsSwipe - 1}> + Next + {theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />} + </Button> + )} + backButton={( + <Button size="small" onClick={this.handleBackSwipe} disabled={activeStepSwipe === 0}> + {theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />} + Back + </Button> + )} + /> + </Paper> + {/* ----------------------------------------------------------------------*/} + {/* People */} + <PapperBlock title="People You May Know" whiteBg noMargin desc=""> + <List component="nav" dense className={classes.profileList}> + <ListItem button className={classes.noPadding}> + <Avatar className={classNames(classes.avatar, classes.orangeAvatar)}>H</Avatar> + <ListItemText primary="Harry Wells" secondary="2 Mutual Connection" /> + <Button color="secondary" size="small">Connect</Button> + </ListItem> + <ListItem button className={classes.noPadding}> + <Avatar className={classNames(classes.avatar, classes.purpleAvatar)}>J</Avatar> + <ListItemText primary="John Doe" secondary="8 Mutual Connection" /> + <Button color="secondary" size="small">Connect</Button> + </ListItem> + <ListItem button className={classes.noPadding}> + <Avatar className={classNames(classes.avatar, classes.pinkAvatar)}>V</Avatar> + <ListItemText primary="Victor Wanggai" secondary="12 Mutual Connection" /> + <Button color="secondary" size="small">Connect</Button> + </ListItem> + <ListItem button className={classes.noPadding}> + <Avatar className={classNames(classes.avatar, classes.greenAvatar)}>H</Avatar> + <ListItemText primary="Baron Phoenix" secondary="10 Mutual Connection" /> + <Button color="secondary" size="small">Connect</Button> + </ListItem> + </List> + <Divider className={classes.divider} /> + <Grid container justify="center"> + <Button color="secondary" className={classes.button}> + See All + </Button> + </Grid> + </PapperBlock> + {/* ----------------------------------------------------------------------*/} + {/* Trending */} + <PapperBlock title="Trends For You" whiteBg desc=""> + <List dense className={classes.trendingList}> + <ListItem className={classes.noPadding}> + <a href="#" className={classes.link}>#Lorem ipsum dolor</a> + <ListItemText secondary="2987 Posts" /> + </ListItem> + <ListItem className={classes.noPadding}> + <a href="#" className={classes.link}>#Aliquam venenatis</a> + <ListItemText secondary="2345 Posts" /> + </ListItem> + <ListItem className={classes.noPadding}> + <a href="#" className={classes.link}>#Nam sollicitudin</a> + <ListItemText secondary="1234 Posts" /> + </ListItem> + <ListItem className={classes.noPadding}> + <a href="#" className={classes.link}>#Cras convallis</a> + <ListItemText secondary="6789 Connection" /> + </ListItem> + <ListItem className={classes.noPadding}> + <a href="#" className={classes.link}>#Aenean sit amet</a> + <ListItemText secondary="2987 Connection" /> + </ListItem> + <ListItem className={classes.noPadding}> + <a href="#" className={classes.link}>#Quisque</a> + <ListItemText secondary="1456 Connection" /> + </ListItem> + <ListItem className={classes.noPadding}> + <a href="#" className={classes.link}>#Lorem ipusm dolor</a> + <ListItemText secondary="2987 Connection" /> + </ListItem> + </List> + </PapperBlock> + </div> + ); + } +} + +SideSection.propTypes = { + classes: PropTypes.object.isRequired, + theme: PropTypes.object.isRequired, +}; + +export default withStyles(styles, { withTheme: true })(SideSection); diff --git a/front/odiparpack/app/components/SocialMedia/Timeline.js b/front/odiparpack/app/components/SocialMedia/Timeline.js new file mode 100644 index 0000000..e46826b --- /dev/null +++ b/front/odiparpack/app/components/SocialMedia/Timeline.js @@ -0,0 +1,177 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import FavoriteIcon from '@material-ui/icons/Favorite'; +import ShareIcon from '@material-ui/icons/Share'; +import CommentIcon from '@material-ui/icons/Comment'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import { + Typography, + Card, + Menu, + MenuItem, + CardHeader, + CardMedia, + CardContent, + CardActions, + IconButton, + Icon, + Avatar, + Tooltip, +} from '@material-ui/core'; +import Comment from './Comment'; +import styles from './jss/timeline-jss'; + + +const optionsOpt = [ + 'Option 1', + 'Option 2', + 'Option 3', +]; + +const ITEM_HEIGHT = 48; + +class Timeline extends React.Component { + state = { + anchorElOpt: null, + openComment: false, + }; + + handleClickOpt = event => { + this.setState({ anchorElOpt: event.currentTarget }); + }; + + handleCloseOpt = () => { + this.setState({ anchorElOpt: null }); + }; + + handleOpenComment = (data) => { + this.props.fetchComment(data); + this.setState({ openComment: true }); + }; + + handleCloseComment = () => { + this.setState({ openComment: false }); + }; + + render() { + const { + classes, + dataTimeline, + onlike, + commentIndex, + submitComment, + } = this.props; + const { anchorElOpt, openComment } = this.state; + const getItem = dataArray => dataArray.map(data => ( + <li key={data.get('id')}> + <div className={classes.iconBullet}> + <Tooltip id={'tooltip-icon-' + data.get('id')} title={data.get('time')}> + <Icon className={classes.icon}> + {data.get('icon')} + </Icon> + </Tooltip> + </div> + <Card className={classes.cardSocmed}> + <CardHeader + avatar={ + <Avatar alt="avatar" src={data.get('avatar')} className={classes.avatar} /> + } + action={( + <IconButton + aria-label="More" + aria-owns={anchorElOpt ? 'long-menu' : null} + aria-haspopup="true" + className={classes.button} + onClick={this.handleClickOpt} + > + <MoreVertIcon /> + </IconButton> + )} + title={data.get('name')} + subheader={data.get('date')} + /> + { data.get('image') !== '' + && ( + <CardMedia + className={classes.media} + image={data.get('image')} + title={data.get('name')} + /> + ) + } + <CardContent> + <Typography component="p"> + {data.get('content')} + </Typography> + </CardContent> + <CardActions className={classes.actions}> + <IconButton aria-label="Like this" onClick={() => onlike(data)}> + <FavoriteIcon className={data.get('liked') ? classes.liked : ''} /> + </IconButton> + <IconButton aria-label="Share"> + <ShareIcon /> + </IconButton> + <div className={classes.rightIcon}> + <Typography variant="caption" component="span"> + {data.get('comments') !== undefined ? data.get('comments').size : 0} + </Typography> + <IconButton aria-label="Comment" onClick={() => this.handleOpenComment(data)}> + <CommentIcon /> + </IconButton> + </div> + </CardActions> + </Card> + </li> + )); + return ( + <Fragment> + <Menu + id="long-menu" + anchorEl={anchorElOpt} + open={Boolean(anchorElOpt)} + onClose={this.handleCloseOpt} + PaperProps={{ + style: { + maxHeight: ITEM_HEIGHT * 4.5, + width: 200, + }, + }} + > + {optionsOpt.map(option => ( + <MenuItem key={option} selected={option === 'Edit Profile'} onClick={this.handleCloseOpt}> + {option} + </MenuItem> + ))} + </Menu> + <Comment + open={openComment} + handleClose={this.handleCloseComment} + submitComment={submitComment} + dataComment={dataTimeline.getIn([commentIndex, 'comments'])} + /> + <ul className={classes.timeline}> + {getItem(dataTimeline)} + </ul> + </Fragment> + ); + } +} + +Timeline.propTypes = { + classes: PropTypes.object.isRequired, + onlike: PropTypes.func, + dataTimeline: PropTypes.object.isRequired, + fetchComment: PropTypes.func, + submitComment: PropTypes.func, + commentIndex: PropTypes.number, +}; + +Timeline.defaultProps = { + onlike: () => (false), + fetchComment: () => {}, + submitComment: () => {}, + commentIndex: 0, +}; + +export default withStyles(styles)(Timeline); diff --git a/front/odiparpack/app/components/SocialMedia/WritePost.js b/front/odiparpack/app/components/SocialMedia/WritePost.js new file mode 100644 index 0000000..3452aea --- /dev/null +++ b/front/odiparpack/app/components/SocialMedia/WritePost.js @@ -0,0 +1,169 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import Dropzone from 'react-dropzone'; +import { withStyles } from '@material-ui/core/styles'; +import PhotoCamera from '@material-ui/icons/PhotoCamera'; +import Send from '@material-ui/icons/Send'; +import ActionDelete from '@material-ui/icons/Delete'; +import dummy from 'ba-api/dummyContents'; +import { IconButton, Fab, MenuItem, FormControl, Avatar, Paper, Select, Tooltip } from '@material-ui/core'; +import styles from './jss/writePost-jss'; + + +function isImage(file) { + const fileName = file.name || file.path; + const suffix = fileName.substr(fileName.indexOf('.') + 1).toLowerCase(); + if (suffix === 'jpg' || suffix === 'jpeg' || suffix === 'bmp' || suffix === 'png') { + return true; + } + return false; +} + +class WritePost extends React.Component { + constructor(props) { + super(props); + this.state = { + privacy: 'public', + files: [], + message: '' + }; + this.onDrop = this.onDrop.bind(this); + } + + onDrop(filesVal) { + const { files } = this.state; + let oldFiles = files; + const filesLimit = 2; + oldFiles = oldFiles.concat(filesVal); + if (oldFiles.length > filesLimit) { + console.log('Cannot upload more than ' + filesLimit + ' items.'); + } else { + this.setState({ files: filesVal }); + } + } + + handleRemove(file, fileIndex) { + const thisFiles = this.state.files; + // This is to prevent memory leaks. + window.URL.revokeObjectURL(file.preview); + + thisFiles.splice(fileIndex, 1); + this.setState({ files: thisFiles }); + } + + handleChange = event => { + this.setState({ privacy: event.target.value }); + }; + + handleWrite = event => { + this.setState({ message: event.target.value }); + }; + + handlePost = (message, files, privacy) => { + // Submit Post to reducer + this.props.submitPost(message, files, privacy); + // Reset all fields + this.setState({ + privacy: 'public', + files: [], + message: '' + }); + } + + render() { + const { classes } = this.props; + let dropzoneRef; + const { privacy, files, message } = this.state; + const acceptedFiles = ['image/jpeg', 'image/png', 'image/bmp']; + const fileSizeLimit = 3000000; + const deleteBtn = (file, index) => ( + <div className={classNames(classes.removeBtn, 'middle')}> + <IconButton onClick={() => this.handleRemove(file, index)}> + <ActionDelete className="removeBtn" /> + </IconButton> + </div> + ); + const previews = filesArray => filesArray.map((file, index) => { + const path = URL.createObjectURL(file) || '/pic' + file.path; + if (isImage(file)) { + return ( + <div key={index.toString()}> + <figure><img src={path} alt="preview" /></figure> + {deleteBtn(file, index)} + </div> + ); + } + return false; + }); + return ( + <div className={classes.statusWrap}> + <Paper> + <Avatar alt="avatar" src={dummy.user.avatar} className={classes.avatarMini} /> + <textarea + row="2" + placeholder="What's on your mind?" + value={message} + onChange={this.handleWrite} + /> + <Dropzone + className={classes.hiddenDropzone} + accept={acceptedFiles.join(',')} + acceptClassName="stripes" + onDrop={this.onDrop} + maxSize={fileSizeLimit} + ref={(node) => { dropzoneRef = node; }} + > + {({ getRootProps, getInputProps }) => ( + <div {...getRootProps()}> + <input {...getInputProps()} /> + </div> + )} + </Dropzone> + <div className={classes.preview}> + {previews(files)} + </div> + <div className={classes.control}> + <Tooltip id="tooltip-upload" title="Upload Photo"> + <IconButton + className={classes.button} + component="button" + onClick={() => { + dropzoneRef.open(); + }} + > + <PhotoCamera /> + </IconButton> + </Tooltip> + <div className={classes.privacy}> + <FormControl className={classes.formControl}> + <Select + value={privacy} + onChange={this.handleChange} + name="privacy" + className={classes.selectEmpty} + > + <MenuItem value="public">Public</MenuItem> + <MenuItem value="friends">Friends</MenuItem> + <MenuItem value="private">Only Me</MenuItem> + </Select> + </FormControl> + </div> + <Tooltip id="tooltip-post" title="Post"> + <Fab onClick={() => this.handlePost(message, files, privacy)} size="small" color="secondary" aria-label="send" className={classes.sendBtn}> + <Send /> + </Fab> + </Tooltip> + </div> + </Paper> + </div> + ); + } +} + +WritePost.propTypes = { + classes: PropTypes.object.isRequired, + submitPost: PropTypes.func.isRequired +}; + +export default withStyles(styles)(WritePost); diff --git a/front/odiparpack/app/components/SocialMedia/jss/cover-jss.js b/front/odiparpack/app/components/SocialMedia/jss/cover-jss.js new file mode 100644 index 0000000..695512d --- /dev/null +++ b/front/odiparpack/app/components/SocialMedia/jss/cover-jss.js @@ -0,0 +1,55 @@ +import { fade } from '@material-ui/core/styles/colorManipulator'; +const styles = theme => ({ + root: { + flexGrow: 1, + }, + cover: { + '& $name, & $subheading': { + color: theme.palette.common.white + }, + position: 'relative', + width: '100%', + overflow: 'hidden', + height: 360, + backgroundColor: theme.palette.primary.main, + display: 'flex', + justifyContent: 'center', + alignItems: 'flex-end', + borderRadius: 2, + backgroundSize: 'cover', + textAlign: 'center', + boxShadow: theme.shadows[7] + }, + content: { + background: fade(theme.palette.secondary.main, 0.3), + height: '100%', + width: '100%', + padding: `70px ${theme.spacing(3)}px 30px` + }, + name: {}, + subheading: {}, + avatar: { + margin: '0 auto', + width: 120, + height: 120, + border: '3px solid rgba(255, 255, 255, .5)' + }, + opt: { + position: 'absolute', + top: 10, + right: 10, + '& button': { + color: theme.palette.common.white + } + }, + verified: { + margin: theme.spacing(1), + top: 10, + position: 'relative' + }, + button: { + marginTop: theme.spacing(1) + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/SocialMedia/jss/socialMedia-jss.js b/front/odiparpack/app/components/SocialMedia/jss/socialMedia-jss.js new file mode 100644 index 0000000..03f5726 --- /dev/null +++ b/front/odiparpack/app/components/SocialMedia/jss/socialMedia-jss.js @@ -0,0 +1,89 @@ +import { deepOrange, deepPurple, pink, green } from '@material-ui/core/colors'; + +const styles = theme => ({ + mobileStepper: { + margin: `0 auto ${theme.spacing(4)}px`, + textAlign: 'center' + }, + avatar: { + marginRight: 15, + }, + orangeAvatar: { + backgroundColor: deepOrange[500], + }, + purpleAvatar: { + backgroundColor: deepPurple[500], + }, + pinkAvatar: { + backgroundColor: pink[500], + }, + greenAvatar: { + backgroundColor: green[500], + }, + divider: { + margin: `${theme.spacing(2)}px 0`, + background: 'none' + }, + link: { + color: theme.palette.primary.main + }, + noPadding: { + padding: '5px', + marginLeft: -10 + }, + sliderWrap: { + height: 310, + overflow: 'hidden' + }, + title: { + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + overflow: 'hidden', + fontSize: 18 + }, + profileList: {}, + trendingList: { + '& li': { + display: 'block' + } + }, + input: {}, + commentContent: { + padding: 10 + }, + commentText: { + marginTop: 5 + }, + buttonClose: { + position: 'absolute', + top: 20, + right: 20 + }, + avatarMini: { + width: 30, + height: 30, + }, + commentAction: { + background: theme.palette.grey[100], + margin: 0, + }, + commentForm: { + display: 'flex', + alignItems: 'center', + [theme.breakpoints.up('md')]: { + minWidth: 600, + }, + width: '100%', + padding: '15px 20px', + margin: 0, + '& $input': { + flex: 1, + margin: '0 10px' + } + }, + commentHead: { + display: 'flex' + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/SocialMedia/jss/timeline-jss.js b/front/odiparpack/app/components/SocialMedia/jss/timeline-jss.js new file mode 100644 index 0000000..dcdb018 --- /dev/null +++ b/front/odiparpack/app/components/SocialMedia/jss/timeline-jss.js @@ -0,0 +1,95 @@ +import { pink } from '@material-ui/core/colors'; +const styles = theme => ({ + card: { + display: 'flex', + justifyContent: 'space-between' + }, + content: { + flex: '1 0 auto', + }, + cover: { + width: 150, + height: 150, + }, + avatar: { + width: 40, + height: 40 + }, + cardSocmed: { + [theme.breakpoints.up('md')]: { + marginLeft: 90, + minWidth: 400, + }, + marginBottom: theme.spacing(3), + position: 'relative', + }, + media: { + height: 0, + paddingTop: '56.25%', // 16:9 + }, + actions: { + display: 'flex', + }, + expandOpen: { + transform: 'rotate(180deg)', + }, + iconBullet: {}, + icon: {}, + timeline: { + position: 'relative', + '&:before': { + left: 39, + content: '""', + top: 40, + height: '101%', + border: `1px solid ${theme.palette.grey[300]}`, + position: 'absolute', + [theme.breakpoints.down('sm')]: { + display: 'none' + }, + }, + '& li': { + position: 'relative', + display: 'block' + }, + '& time': { + top: 70, + left: 20, + position: 'absolute', + textAlign: 'center', + background: theme.palette.common.white, + boxShadow: theme.shadows[3], + padding: '4px 40px 4px 15px', + borderLeft: `3px solid ${theme.palette.secondary.main}` + }, + '& $iconBullet': { + position: 'absolute', + borderRadius: '50%', + top: 20, + width: 40, + height: 40, + background: theme.palette.secondary.main, + boxShadow: theme.shadows[5], + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + left: 20, + '& $icon': { + color: theme.palette.common.white, + }, + [theme.breakpoints.down('sm')]: { + display: 'none' + }, + }, + }, + rightIcon: { + marginLeft: 'auto', + display: 'flex', + alignItems: 'center' + }, + liked: { + color: pink[500] + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/SocialMedia/jss/writePost-jss.js b/front/odiparpack/app/components/SocialMedia/jss/writePost-jss.js new file mode 100644 index 0000000..cd18422 --- /dev/null +++ b/front/odiparpack/app/components/SocialMedia/jss/writePost-jss.js @@ -0,0 +1,73 @@ +const styles = theme => ({ + statusWrap: { + marginBottom: theme.spacing(3), + '& > div': { + overflow: 'hidden' + }, + '& textarea': { + border: 'none', + padding: '20px 20px 20px 50px', + outline: 'none', + width: '100%', + resize: 'none', + overflow: 'hidden', + height: 50, + transition: theme.transitions.create(['height'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + '&:focus': { + height: 100, + overflow: 'auto', + } + } + }, + avatarMini: { + width: 30, + height: 30, + position: 'absolute', + top: 40, + left: 10 + }, + control: { + padding: '10px 20px 0', + display: 'flex' + }, + privacy: { + flex: 1, + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + textAlign: 'right', + }, + button: { + margin: theme.spacing(0.5) + }, + sendBtn: { + position: 'relative', + top: 5 + }, + formControl: { + margin: '0 20px', + width: 150, + paddingLeft: 10, + textAlign: 'left', + '&:before, &:after': { + borderBottom: 'none' + } + }, + hiddenDropzone: { + display: 'none' + }, + preview: { + position: 'relative', + '& figure': { + textAlign: 'center' + } + }, + removeBtn: { + opacity: 1 + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/SourceReader/SourceReader.js b/front/odiparpack/app/components/SourceReader/SourceReader.js new file mode 100644 index 0000000..e1303a0 --- /dev/null +++ b/front/odiparpack/app/components/SourceReader/SourceReader.js @@ -0,0 +1,114 @@ +import React, { Component } from 'react'; +import { PropTypes } from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Axios from 'axios'; +import SyntaxHighlighter, { registerLanguage } from 'react-syntax-highlighter/prism-light'; +import jsx from 'react-syntax-highlighter/languages/prism/jsx'; +import themeSource from 'react-syntax-highlighter/styles/prism/xonokai'; +import classNames from 'classnames'; +import Code from '@material-ui/icons/Code'; +import Close from '@material-ui/icons/Close'; +import { Button, LinearProgress, Icon } from '@material-ui/core'; +import codePreview from '../../config/codePreview'; + + +const url = '/api/docs?src='; + +const styles = theme => ({ + button: { + margin: '8px 5px', + }, + iconSmall: { + fontSize: 20, + }, + leftIcon: { + marginRight: theme.spacing(1), + }, + source: { + overflow: 'hidden', + height: 0, + position: 'relative', + transition: 'all .5s', + margin: '0 -10px' + }, + preloader: { + position: 'absolute', + top: 36, + left: 0, + width: '100%' + }, + open: { + height: 'auto', + }, + src: { + textAlign: 'center', + margin: 10, + fontFamily: 'monospace', + '& span': { + fontSize: 14, + marginRight: 5, + top: 3, + position: 'relative' + } + } +}); + +class SourceReader extends Component { + state = { raws: [], open: false, loading: false }; + + sourceOpen = () => { + const name = this.props.componentName; + this.setState({ loading: true }, () => { + Axios.get(url + name).then(result => this.setState({ + raws: result.data.records, + loading: false + })); + this.setState({ open: !this.state.open }); + }); + }; + + render() { + const { raws, open, loading } = this.state; + const { classes } = this.props; + registerLanguage('jsx', jsx); + if (codePreview.enable) { + return ( + <div> + <Button onClick={this.sourceOpen} color="secondary" className={classes.button} size="small"> + { open + ? <Close className={classNames(classes.leftIcon, classes.iconSmall)} /> + : <Code className={classNames(classes.leftIcon, classes.iconSmall)} /> + } + { open ? 'Hide Code' : 'Show Code' } + </Button> + <section className={classNames(classes.source, open ? classes.open : '')}> + <p className={classes.src}> + <Icon className="description">description</Icon> + src/app/ + {this.props.componentName} + </p> + {loading + && <LinearProgress color="secondary" className={classes.preloader} /> + } + {raws.map((raw, index) => ([ + <div key={index.toString()}> + <SyntaxHighlighter language="jsx" style={themeSource} showLineNumbers="true"> + {raw.source.toString()} + </SyntaxHighlighter> + </div> + ]) + )} + </section> + </div> + ); + } + return false; + } +} + +SourceReader.propTypes = { + componentName: PropTypes.string.isRequired, + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(SourceReader); diff --git a/front/odiparpack/app/components/Tables/AdvTable.js b/front/odiparpack/app/components/Tables/AdvTable.js new file mode 100644 index 0000000..acb2803 --- /dev/null +++ b/front/odiparpack/app/components/Tables/AdvTable.js @@ -0,0 +1,206 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import tableStyles from 'ba-styles/Table.scss'; +import { Table, TableBody, TableCell, TableRow, TablePagination, Paper, Checkbox } from '@material-ui/core'; +import EnhancedTableHead from './tableParts/TableHeader'; +import EnhancedTableToolbar from './tableParts/TableToolbar'; + + +const styles = theme => ({ + root: { + width: '100%', + marginTop: theme.spacing(3), + }, + table: { + minWidth: 1020, + }, + tableWrapper: { + overflowX: 'auto', + }, +}); + +class AdvTable extends React.Component { + constructor(props, context) { + super(props, context); + + this.state = { + order: this.props.order, + orderBy: this.props.orderBy, + selected: this.props.selected, + data: this.props.data.sort((a, b) => (a.calories < b.calories ? -1 : 1)), + page: this.props.page, + rowsPerPage: this.props.rowsPerPage, + defaultPerPage: this.props.defaultPerPage, + filterText: this.props.filterText, + }; + } + + handleRequestSort = (event, property) => { + const orderBy = property; + let order = 'desc'; + + if (this.state.orderBy === property && this.state.order === 'desc') { + order = 'asc'; + } + + const data = order === 'desc' + ? this.state.data.sort((a, b) => (b[orderBy] < a[orderBy] ? -1 : 1)) + : this.state.data.sort((a, b) => (a[orderBy] < b[orderBy] ? -1 : 1)); + + this.setState({ data, order, orderBy }); + }; + + handleSelectAllClick = (event, checked) => { + if (checked) { + this.setState({ selected: this.state.data.map(n => n.id) }); + return; + } + this.setState({ selected: [] }); + }; + + handleClick = (event, id) => { + const { selected } = this.state; + const selectedIndex = selected.indexOf(id); + let newSelected = []; + + if (selectedIndex === -1) { + newSelected = newSelected.concat(selected, id); + } else if (selectedIndex === 0) { + newSelected = newSelected.concat(selected.slice(1)); + } else if (selectedIndex === selected.length - 1) { + newSelected = newSelected.concat(selected.slice(0, -1)); + } else if (selectedIndex > 0) { + newSelected = newSelected.concat( + selected.slice(0, selectedIndex), + selected.slice(selectedIndex + 1), + ); + } + + this.setState({ selected: newSelected }); + }; + + handleChangePage = (event, page) => { + this.setState({ page }); + }; + + handleChangeRowsPerPage = event => { + this.setState({ rowsPerPage: event.target.value }); + }; + + isSelected = id => this.state.selected.indexOf(id) !== -1; + + handleUserInput(value) { + // Show all item first + if (value !== '') { + this.setState({ rowsPerPage: this.state.data.length }); + } else { + this.setState({ rowsPerPage: this.state.defaultPerPage }); + } + + // Show result base on keyword + this.setState({ filterText: value.toLowerCase() }); + } + + render() { + const { classes } = this.props; + const { + data, + order, + orderBy, + selected, + rowsPerPage, + page, + filterText + } = this.state; + const { columnData } = this.props; + const checkcell = true; + const emptyRows = rowsPerPage - Math.min(rowsPerPage, data.length - (page * rowsPerPage)); + const renderCell = (dataArray, keyArray) => keyArray.map((itemCell, index) => ( + <TableCell align={itemCell.numeric ? 'right' : 'left'} key={index.toString()}>{dataArray[itemCell.id]}</TableCell> + )); + return ( + <Paper className={classes.root}> + <EnhancedTableToolbar + numSelected={selected.length} + filterText={filterText} + onUserInput={(event) => this.handleUserInput(event)} + /> + <div className={classes.tableWrapper}> + <Table className={classNames(classes.table, tableStyles.stripped)}> + <EnhancedTableHead + numSelected={selected.length} + order={order} + orderBy={orderBy} + onSelectAllClick={this.handleSelectAllClick} + onRequestSort={this.handleRequestSort} + rowCount={data.length} + columnData={columnData} + checkcell={checkcell} + /> + <TableBody> + {data.slice(page * rowsPerPage, (page * rowsPerPage) + rowsPerPage).map(n => { + const isSelected = this.isSelected(n.id); + if (n.name.toLowerCase().indexOf(filterText) === -1) { + return false; + } + return ( + <TableRow + hover + onClick={event => this.handleClick(event, n.id)} + role="checkbox" + aria-checked={isSelected} + tabIndex={-1} + key={n.id} + selected={isSelected} + > + <TableCell padding="checkbox"> + <Checkbox checked={isSelected} /> + </TableCell> + {renderCell(n, columnData)} + </TableRow> + ); + })} + {emptyRows > 0 && ( + <TableRow style={{ height: 49 * emptyRows }}> + <TableCell colSpan={6} /> + </TableRow> + )} + </TableBody> + </Table> + </div> + <TablePagination + component="div" + rowsPerPageOptions={[5, 10, 25]} + count={data.length} + rowsPerPage={rowsPerPage} + page={page} + backIconButtonProps={{ + 'aria-label': 'Previous Page', + }} + nextIconButtonProps={{ + 'aria-label': 'Next Page', + }} + onChangePage={this.handleChangePage} + onChangeRowsPerPage={this.handleChangeRowsPerPage} + /> + </Paper> + ); + } +} + +AdvTable.propTypes = { + classes: PropTypes.object.isRequired, + data: PropTypes.array.isRequired, + order: PropTypes.string.isRequired, + orderBy: PropTypes.string.isRequired, + selected: PropTypes.array.isRequired, + rowsPerPage: PropTypes.number.isRequired, + page: PropTypes.number.isRequired, + defaultPerPage: PropTypes.number.isRequired, + filterText: PropTypes.string.isRequired, + columnData: PropTypes.array.isRequired, +}; + +export default withStyles(styles)(AdvTable); diff --git a/front/odiparpack/app/components/Tables/CrudTable.js b/front/odiparpack/app/components/Tables/CrudTable.js new file mode 100644 index 0000000..d3dd164 --- /dev/null +++ b/front/odiparpack/app/components/Tables/CrudTable.js @@ -0,0 +1,52 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import MainTable from './tableParts/MainTable'; + +class CrudTable extends React.Component { + componentDidMount() { + this.props.fetchData(this.props.dataInit, this.props.branch); + } + + render() { + const { + title, + dataTable, + addEmptyRow, + removeRow, + updateRow, + editRow, + finishEditRow, + anchor, + branch + } = this.props; + return ( + <MainTable + title={title} + addEmptyRow={addEmptyRow} + items={dataTable} + removeRow={removeRow} + updateRow={updateRow} + editRow={editRow} + finishEditRow={finishEditRow} + anchor={anchor} + branch={branch} + /> + ); + } +} + +CrudTable.propTypes = { + title: PropTypes.string.isRequired, + anchor: PropTypes.array.isRequired, + dataInit: PropTypes.array.isRequired, + dataTable: PropTypes.object.isRequired, + fetchData: PropTypes.func.isRequired, + addEmptyRow: PropTypes.func.isRequired, + removeRow: PropTypes.func.isRequired, + updateRow: PropTypes.func.isRequired, + editRow: PropTypes.func.isRequired, + finishEditRow: PropTypes.func.isRequired, + branch: PropTypes.string.isRequired, +}; + +export default CrudTable; diff --git a/front/odiparpack/app/components/Tables/CrudTableForm.js b/front/odiparpack/app/components/Tables/CrudTableForm.js new file mode 100644 index 0000000..d2d2ea8 --- /dev/null +++ b/front/odiparpack/app/components/Tables/CrudTableForm.js @@ -0,0 +1,70 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Form from './tableParts/Form'; +import MainTableForm from './tableParts/MainTableForm'; +import FloatingPanel from './../Panel/FloatingPanel'; + +class CrudTableForm extends React.Component { + componentDidMount() { + this.props.fetchData(this.props.dataInit, this.props.branch); + } + + sendValues = (values) => { + setTimeout(() => { + this.props.submit(values, this.props.branch); + }, 500); + } + + render() { + const { + title, + dataTable, + openForm, + closeForm, + removeRow, + addNew, + editRow, + anchor, + children, + branch, + initValues + } = this.props; + return ( + <div> + <FloatingPanel openForm={openForm} branch={branch} closeForm={closeForm}> + <Form onSubmit={this.sendValues} initValues={initValues} branch={branch}> + {children} + </Form> + </FloatingPanel> + <MainTableForm + title={title} + addNew={addNew} + items={dataTable} + removeRow={removeRow} + editRow={editRow} + anchor={anchor} + branch={branch} + /> + </div> + ); + } +} + +CrudTableForm.propTypes = { + title: PropTypes.string.isRequired, + anchor: PropTypes.array.isRequired, + dataInit: PropTypes.array.isRequired, + dataTable: PropTypes.object.isRequired, + fetchData: PropTypes.func.isRequired, + submit: PropTypes.func.isRequired, + addNew: PropTypes.func.isRequired, + openForm: PropTypes.bool.isRequired, + closeForm: PropTypes.func.isRequired, + removeRow: PropTypes.func.isRequired, + editRow: PropTypes.func.isRequired, + children: PropTypes.node.isRequired, + initValues: PropTypes.object.isRequired, + branch: PropTypes.string.isRequired, +}; + +export default CrudTableForm; diff --git a/front/odiparpack/app/components/Tables/EmptyData.js b/front/odiparpack/app/components/Tables/EmptyData.js new file mode 100644 index 0000000..a59c3d6 --- /dev/null +++ b/front/odiparpack/app/components/Tables/EmptyData.js @@ -0,0 +1,14 @@ +import React from 'react'; +import tableStyles from 'ba-styles/Table.scss'; +import TableIcon from '@material-ui/icons/Apps'; + +function EmptyData() { + return ( + <div className={tableStyles.nodata}> + <TableIcon /> + No Data + </div> + ); +} + +export default EmptyData; diff --git a/front/odiparpack/app/components/Tables/TreeTable.js b/front/odiparpack/app/components/Tables/TreeTable.js new file mode 100644 index 0000000..12eeb19 --- /dev/null +++ b/front/odiparpack/app/components/Tables/TreeTable.js @@ -0,0 +1,190 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import ExpandLess from '@material-ui/icons/KeyboardArrowRight'; +import ExpandMore from '@material-ui/icons/ExpandMore'; +import Add from '@material-ui/icons/AddCircle'; +import Remove from '@material-ui/icons/RemoveCircleOutline'; + +import { Table, TableBody, TableCell, TableHead, TableRow } from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '100%', + marginTop: theme.spacing(3), + overflowX: 'auto', + }, + table: { + minWidth: 700, + }, + hideRow: { + display: 'none' + }, + anchor: { + cursor: 'pointer' + }, + icon: { + top: 5, + position: 'relative', + left: -5 + } +}); + +let RenderRow = props => { + const { + classes, + toggleTree, + treeOpen, + item, + parent, + arrowMore, + icon, + branch + } = props; + + const keyID = item.id; + const dataBody = Object.keys(item); + const dataBodyVal = Object.values(item); + + const renderIconMore = (iconName) => { + if (iconName === 'arrow') { + return <ExpandMore className={classes.icon} />; + } + return <Remove className={classes.icon} />; + }; + + const renderIconLess = (iconName) => { + if (iconName === 'arrow') { + return <ExpandLess className={classes.icon} />; + } + return <Add className={classes.icon} />; + }; + + const renderCell = (dataArray, parentCell) => dataArray.map((itemCell, index) => { + if (index < 1) { + if (parentCell) { + return ( + <TableCell key={index.toString()} style={{ paddingLeft: (keyID.split('_').length) * 20 }}> + {arrowMore.indexOf(keyID) > -1 ? renderIconMore(icon) : renderIconLess(icon)} + {keyID} + </TableCell> + ); + } + return ( + <TableCell key={index.toString()} style={{ paddingLeft: (keyID.split('_').length) * 20 }}>{keyID}</TableCell> + ); + } + + if (itemCell !== 'child') { + return ( + <TableCell key={index.toString()}>{dataBodyVal[index]}</TableCell> + ); + } + + return false; + }); + + const row = parent ? ( + <TableRow + key={keyID} + className={treeOpen.indexOf(keyID) < 0 && keyID.indexOf('_') > -1 ? classes.hideRow : classes.anchor} + onClick={() => toggleTree(keyID, item.child, branch)} + > + {renderCell(dataBody, true)} + </TableRow> + ) : ( + <TableRow + key={keyID} + className={treeOpen.indexOf(keyID) < 0 && keyID.indexOf('_') > -1 ? classes.hideRow : ''} + > + {renderCell(dataBody, false)} + </TableRow> + ); + + return [row]; +}; + +RenderRow.propTypes = { + classes: PropTypes.object.isRequired, + item: PropTypes.object.isRequired, + parent: PropTypes.bool.isRequired, + toggleTree: PropTypes.func.isRequired, + treeOpen: PropTypes.object.isRequired, + arrowMore: PropTypes.object.isRequired, + branch: PropTypes.string.isRequired, + icon: PropTypes.string.isRequired +}; + +RenderRow = withStyles(styles)(RenderRow); + +class TreeTable extends React.Component { + render() { + const { + classes, + dataTable, + icon, + treeOpen, + arrowMore, + toggleTree, + branch + } = this.props; + const parentRow = true; + const getData = dataArray => dataArray.map((item, index) => { + if (item.child) { + return [ + <RenderRow + icon={icon} + treeOpen={treeOpen} + arrowMore={arrowMore} + toggleTree={toggleTree} + item={item} + key={index.toString()} + parent={parentRow} + branch={branch} + />, + getData(item.child) + ]; + } + return ( + <RenderRow + icon={icon} + item={item} + treeOpen={treeOpen} + arrowMore={arrowMore} + toggleTree={toggleTree} + key={index.toString()} + branch={branch} + parent={false} + /> + ); + }); + + const getHead = dataArray => dataArray.map((item, index) => <TableCell key={index.toString()}>{item.label}</TableCell> + ); + + return ( + <Table className={classes.table}> + <TableHead> + <TableRow> + { getHead(dataTable.head) } + </TableRow> + </TableHead> + <TableBody> + { getData(dataTable.body) } + </TableBody> + </Table> + ); + } +} + +TreeTable.propTypes = { + classes: PropTypes.object.isRequired, + dataTable: PropTypes.object.isRequired, + treeOpen: PropTypes.object.isRequired, + toggleTree: PropTypes.func.isRequired, + arrowMore: PropTypes.object.isRequired, + branch: PropTypes.string.isRequired, + icon: PropTypes.string.isRequired +}; + +export default withStyles(styles)(TreeTable); diff --git a/front/odiparpack/app/components/Tables/tableParts/DatePickerCell.js b/front/odiparpack/app/components/Tables/tableParts/DatePickerCell.js new file mode 100644 index 0000000..161d0eb --- /dev/null +++ b/front/odiparpack/app/components/Tables/tableParts/DatePickerCell.js @@ -0,0 +1,59 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { KeyboardDatePicker, MuiPickersUtilsProvider } from '@material-ui/pickers'; +import MomentUtils from '@date-io/moment'; +import css from 'ba-styles/Table.scss'; +import { TableCell } from '@material-ui/core'; + +class DatePickerCell extends React.Component { + state = { + event: { + target: { + name: this.props.cellData.type, // eslint-disable-line + value: this.props.cellData.value, // eslint-disable-line + } + } + } + + handleDateChange = date => { + const { event } = this.state; + const { branch, updateRow } = this.props; + event.target.value = date; + updateRow(event, branch); + } + + render() { + const { + edited, + cellData + } = this.props; + const { event } = this.state; + return ( + <TableCell padding="none" className="text-center" textalign="center"> + <MuiPickersUtilsProvider utils={MomentUtils}> + <KeyboardDatePicker + clearable + name={cellData.type} + className={css.crudInput} + format="DD/MM/YYYY" + placeholder="10/10/2018" + mask={[/\d/, /\d/, '/', /\d/, /\d/, '/', /\d/, /\d/, /\d/, /\d/]} + value={event.target.value} + disabled={!edited} + onChange={this.handleDateChange} + animateYearScrolling={false} + /> + </MuiPickersUtilsProvider> + </TableCell> + ); + } +} + +DatePickerCell.propTypes = { + cellData: PropTypes.object.isRequired, + updateRow: PropTypes.func.isRequired, + edited: PropTypes.bool.isRequired, + branch: PropTypes.string.isRequired, +}; + +export default DatePickerCell; diff --git a/front/odiparpack/app/components/Tables/tableParts/EditableCell.js b/front/odiparpack/app/components/Tables/tableParts/EditableCell.js new file mode 100644 index 0000000..2c7ba8f --- /dev/null +++ b/front/odiparpack/app/components/Tables/tableParts/EditableCell.js @@ -0,0 +1,86 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import css from 'ba-styles/Table.scss'; + +import { TableCell, Input, TextField } from '@material-ui/core'; + +class EditableCell extends React.Component { + handleUpdate(event) { + event.persist(); + this.props.updateRow(event, this.props.branch); + } + + render() { + const { + cellData, + edited, + inputType + } = this.props; + switch (inputType) { + case 'text': + return ( + <TableCell padding="none"> + <Input + placeholder={cellData.type} + name={cellData.type} + className={css.crudInput} + id={cellData.id.toString()} + value={cellData.value} + onChange={(event) => this.handleUpdate(event)} + disabled={!edited} + margin="none" + inputProps={{ + 'aria-label': 'Description', + }} + /> + </TableCell> + ); + case 'number': + return ( + <TableCell padding="none"> + <TextField + id={cellData.id.toString()} + name={cellData.type} + className={css.crudInput} + value={cellData.value} + onChange={(event) => this.handleUpdate(event)} + type="number" + InputLabelProps={{ + shrink: true, + }} + margin="none" + disabled={!edited} + /> + </TableCell> + ); + default: + return ( + <TableCell padding="none"> + <Input + placeholder={cellData.type} + name={cellData.type} + className={css.crudInput} + id={cellData.id.toString()} + value={cellData.value} + onChange={(event) => this.handleUpdate(event)} + disabled={!edited} + margin="none" + inputProps={{ + 'aria-label': 'Description', + }} + /> + </TableCell> + ); + } + } +} + +EditableCell.propTypes = { + inputType: PropTypes.string.isRequired, + cellData: PropTypes.object.isRequired, + updateRow: PropTypes.func.isRequired, + edited: PropTypes.bool.isRequired, + branch: PropTypes.string.isRequired, +}; + +export default EditableCell; diff --git a/front/odiparpack/app/components/Tables/tableParts/Form.js b/front/odiparpack/app/components/Tables/tableParts/Form.js new file mode 100644 index 0000000..da66966 --- /dev/null +++ b/front/odiparpack/app/components/Tables/tableParts/Form.js @@ -0,0 +1,71 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { reduxForm } from 'redux-form/immutable'; +import css from 'ba-styles/Form.scss'; +import { Button } from '@material-ui/core'; + +class Form extends Component { + componentDidMount() { + // this.ref // the Field + // .getRenderedComponent() // on Field, returns ReduxFormMaterialUITextField + // .getRenderedComponent() // on ReduxFormMaterialUITextField, returns TextField + // .focus() // on TextField + // console.log(this.props.initValues); + } + + render() { + const { + handleSubmit, + children, + reset, + pristine, + submitting, + } = this.props; + + return ( + <div> + <form onSubmit={handleSubmit}> + <section className={css.bodyForm}> + {children} + </section> + <div className={css.buttonArea}> + <Button variant="contained" color="secondary" type="submit" disabled={submitting}> + Submit + </Button> + <Button + type="button" + disabled={pristine || submitting} + onClick={reset} + > + Reset + </Button> + </div> + </form> + </div> + ); + } +} + +Form.propTypes = { + children: PropTypes.node.isRequired, + handleSubmit: PropTypes.func.isRequired, + reset: PropTypes.func.isRequired, + pristine: PropTypes.bool.isRequired, + submitting: PropTypes.bool.isRequired, +}; + +const FormMapped = reduxForm({ + form: 'immutableExample', + enableReinitialize: true, +})(Form); + + +const FormMappedInit = connect( + state => ({ + initialValues: state.getIn(['crudTableForm', 'formValues']) + }) +)(FormMapped); + + +export default FormMappedInit; diff --git a/front/odiparpack/app/components/Tables/tableParts/MainTable.js b/front/odiparpack/app/components/Tables/tableParts/MainTable.js new file mode 100644 index 0000000..973bccf --- /dev/null +++ b/front/odiparpack/app/components/Tables/tableParts/MainTable.js @@ -0,0 +1,104 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import AddIcon from '@material-ui/icons/Add'; +import css from 'ba-styles/Table.scss'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Toolbar, + Typography, + Tooltip, + Button, +} from '@material-ui/core'; +import Row from './Row'; +import styles from './tableStyle-jss'; + + +class MainTable extends React.Component { + render() { + const { + classes, + items, + addEmptyRow, + removeRow, + updateRow, + editRow, + finishEditRow, + anchor, + branch, + title + } = this.props; + + const getItems = dataArray => dataArray.map(item => ( + <Row + anchor={anchor} + updateRow={(event) => updateRow(event, item, branch)} + item={item} + removeRow={() => removeRow(item, branch)} + key={item.get('id')} + editRow={() => editRow(item, branch)} + finishEditRow={() => finishEditRow(item, branch)} + branch={branch} + /> + )); + + const getHead = dataArray => dataArray.map((item, index) => { + if (!item.hidden) { + return ( + <TableCell padding="none" key={index.toString()} width={item.width}>{item.label}</TableCell> + ); + } + return false; + }); + return ( + <div> + <Toolbar className={classes.toolbar}> + <div className={classes.title}> + <Typography variant="h6">{title}</Typography> + </div> + <div className={classes.spacer} /> + <div className={classes.actions}> + <Tooltip title="Add Item"> + <Button variant="contained" onClick={() => addEmptyRow(anchor, branch)} color="secondary" className={classes.button}> + <AddIcon className={classNames(classes.leftIcon, classes.iconSmall)} /> + Add New + </Button> + </Tooltip> + </div> + </Toolbar> + <div className={classes.rootTable}> + <Table className={classNames(css.tableCrud, classes.table, css.stripped)}> + <TableHead> + <TableRow> + { getHead(anchor) } + </TableRow> + </TableHead> + <TableBody> + {getItems(items)} + </TableBody> + </Table> + </div> + </div> + ); + } +} + +MainTable.propTypes = { + title: PropTypes.string.isRequired, + classes: PropTypes.object.isRequired, + items: PropTypes.object.isRequired, + anchor: PropTypes.array.isRequired, + addEmptyRow: PropTypes.func.isRequired, + removeRow: PropTypes.func.isRequired, + updateRow: PropTypes.func.isRequired, + editRow: PropTypes.func.isRequired, + finishEditRow: PropTypes.func.isRequired, + branch: PropTypes.string.isRequired +}; + +export default withStyles(styles)(MainTable); diff --git a/front/odiparpack/app/components/Tables/tableParts/MainTableForm.js b/front/odiparpack/app/components/Tables/tableParts/MainTableForm.js new file mode 100644 index 0000000..ccf0e4a --- /dev/null +++ b/front/odiparpack/app/components/Tables/tableParts/MainTableForm.js @@ -0,0 +1,97 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import AddIcon from '@material-ui/icons/Add'; +import css from 'ba-styles/Table.scss'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Toolbar, + Typography, + Tooltip, + Button, +} from '@material-ui/core'; +import RowReadOnly from './RowReadOnly'; +import styles from './tableStyle-jss'; + + +class MainTableForm extends React.Component { + render() { + const { + title, + classes, + items, + removeRow, + editRow, + addNew, + anchor, + branch + } = this.props; + const getItems = dataArray => dataArray.map(item => ( + <RowReadOnly + item={item} + removeRow={() => removeRow(item, branch)} + key={item.get('id')} + editRow={() => editRow(item, branch)} + anchor={anchor} + branch={branch} + /> + )); + + const getHead = dataArray => dataArray.map((item, index) => { + if (!item.hidden) { + return ( + <TableCell padding="none" key={index.toString()} width={item.width}>{item.label}</TableCell> + ); + } + return false; + }); + return ( + <div> + <Toolbar className={classes.toolbar}> + <div className={classes.title}> + <Typography variant="h6">{title}</Typography> + </div> + <div className={classes.spacer} /> + <div className={classes.actions}> + <Tooltip title="Add Item"> + <Button variant="contained" onClick={() => addNew(anchor, branch)} color="secondary" className={classes.button}> + <AddIcon className={classNames(classes.leftIcon, classes.iconSmall)} /> + Add New + </Button> + </Tooltip> + </div> + </Toolbar> + <div className={classes.rootTable}> + <Table className={classNames(css.tableCrud, classes.table, css.stripped)}> + <TableHead> + <TableRow> + { getHead(anchor) } + </TableRow> + </TableHead> + <TableBody> + {getItems(items)} + </TableBody> + </Table> + </div> + </div> + ); + } +} + +MainTableForm.propTypes = { + title: PropTypes.string.isRequired, + classes: PropTypes.object.isRequired, + items: PropTypes.object.isRequired, + anchor: PropTypes.array.isRequired, + addNew: PropTypes.func.isRequired, + removeRow: PropTypes.func.isRequired, + editRow: PropTypes.func.isRequired, + branch: PropTypes.string.isRequired, +}; + +export default withStyles(styles)(MainTableForm); diff --git a/front/odiparpack/app/components/Tables/tableParts/Row.js b/front/odiparpack/app/components/Tables/tableParts/Row.js new file mode 100644 index 0000000..67e7a4d --- /dev/null +++ b/front/odiparpack/app/components/Tables/tableParts/Row.js @@ -0,0 +1,167 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import DeleteIcon from '@material-ui/icons/Delete'; +import EditIcon from '@material-ui/icons/BorderColor'; +import DoneIcon from '@material-ui/icons/Done'; +import css from 'ba-styles/Table.scss'; +import { TableCell, IconButton } from '@material-ui/core'; +import EditableCell from './EditableCell'; +import SelectableCell from './SelectableCell'; +import ToggleCell from './ToggleCell'; +import DatePickerCell from './DatePickerCell'; +import TimePickerCell from './TimePickerCell'; + + +const styles = theme => ({ + button: { + margin: theme.spacing(1), + }, +}); + +class Row extends React.Component { + render() { + const { + classes, + anchor, + item, + removeRow, + updateRow, + editRow, + finishEditRow, + branch + } = this.props; + const eventDel = () => { + removeRow(item, branch); + }; + const eventEdit = () => { + editRow(item, branch); + }; + const eventDone = () => { + finishEditRow(item, branch); + }; + const renderCell = dataArray => dataArray.map((itemCell, index) => { + if (itemCell.name !== 'action' && !itemCell.hidden) { + const inputType = anchor[index].type; + switch (inputType) { + case 'selection': + return ( + <SelectableCell + updateRow={(event) => updateRow(event, branch)} + cellData={{ + type: itemCell.name, + value: item.get(itemCell.name), + id: item.get('id'), + }} + edited={item.get('edited')} + key={index.toString()} + options={anchor[index].options} + branch={branch} + /> + ); + case 'toggle': + return ( + <ToggleCell + updateRow={(event) => updateRow(event, branch)} + cellData={{ + type: itemCell.name, + value: item.get(itemCell.name), + id: item.get('id'), + }} + edited={item.get('edited')} + key={index.toString()} + branch={branch} + /> + ); + case 'date': + return ( + <DatePickerCell + updateRow={(event) => updateRow(event, branch)} + cellData={{ + type: itemCell.name, + value: item.get(itemCell.name), + id: item.get('id'), + }} + edited={item.get('edited')} + key={index.toString()} + branch={branch} + /> + ); + case 'time': + return ( + <TimePickerCell + updateRow={(event) => updateRow(event, branch)} + cellData={{ + type: itemCell.name, + value: item.get(itemCell.name), + id: item.get('id'), + }} + edited={item.get('edited')} + key={index.toString()} + branch={branch} + /> + ); + default: + return ( + <EditableCell + updateRow={(event) => updateRow(event, branch)} + cellData={{ + type: itemCell.name, + value: item.get(itemCell.name), + id: item.get('id'), + }} + edited={item.get('edited')} + key={index.toString()} + inputType={inputType} + branch={branch} + /> + ); + } + } + return false; + }); + return ( + <tr className={item.get('edited') ? css.editing : ''}> + {renderCell(anchor)} + <TableCell padding="none"> + <IconButton + onClick={() => eventEdit(this)} + className={classNames((item.get('edited') ? css.hideAction : ''), classes.button)} + aria-label="Edit" + > + <EditIcon /> + </IconButton> + <IconButton + onClick={() => eventDone(this)} + color="secondary" + className={classNames((!item.get('edited') ? css.hideAction : ''), classes.button)} + aria-label="Done" + > + <DoneIcon /> + </IconButton> + <IconButton + onClick={() => eventDel(this)} + className={classes.button} + aria-label="Delete" + > + <DeleteIcon /> + </IconButton> + </TableCell> + </tr> + ); + } +} + +Row.propTypes = { + classes: PropTypes.object.isRequired, + anchor: PropTypes.array.isRequired, + item: PropTypes.object.isRequired, + removeRow: PropTypes.func.isRequired, + updateRow: PropTypes.func.isRequired, + editRow: PropTypes.func.isRequired, + finishEditRow: PropTypes.func.isRequired, + branch: PropTypes.string.isRequired +}; + +export default withStyles(styles)(Row); diff --git a/front/odiparpack/app/components/Tables/tableParts/RowReadOnly.js b/front/odiparpack/app/components/Tables/tableParts/RowReadOnly.js new file mode 100644 index 0000000..7da655f --- /dev/null +++ b/front/odiparpack/app/components/Tables/tableParts/RowReadOnly.js @@ -0,0 +1,76 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import css from 'ba-styles/Table.scss'; +import DeleteIcon from '@material-ui/icons/Delete'; +import EditIcon from '@material-ui/icons/BorderColor'; + +import { TableCell, IconButton } from '@material-ui/core'; + +const styles = theme => ({ + button: { + margin: theme.spacing(1), + }, +}); + +class RowReadOnly extends React.Component { + render() { + const { + anchor, + classes, + item, + removeRow, + editRow, + branch + } = this.props; + const eventDel = () => { + removeRow(item, branch); + }; + const eventEdit = () => { + editRow(item, branch); + }; + const renderCell = dataArray => dataArray.map((itemCell, index) => { + if (itemCell.name !== 'action' && !itemCell.hidden) { + return ( + <TableCell padding="none" key={index.toString()}> + {item.get(itemCell.name) !== undefined ? item.get(itemCell.name).toString() : ''} + </TableCell> + ); + } + return false; + }); + return ( + <tr> + {renderCell(anchor)} + <TableCell padding="none"> + <IconButton + onClick={() => eventEdit(this)} + className={classNames((item.get('edited') ? css.hideAction : ''), classes.button)} + aria-label="Edit" + > + <EditIcon /> + </IconButton> + <IconButton + onClick={() => eventDel(this)} + className={classes.button} + aria-label="Delete" + > + <DeleteIcon /> + </IconButton> + </TableCell> + </tr> + ); + } +} + +RowReadOnly.propTypes = { + anchor: PropTypes.array.isRequired, + classes: PropTypes.object.isRequired, + item: PropTypes.object.isRequired, + removeRow: PropTypes.func.isRequired, + editRow: PropTypes.func.isRequired, + branch: PropTypes.string.isRequired, +}; + +export default withStyles(styles)(RowReadOnly); diff --git a/front/odiparpack/app/components/Tables/tableParts/SelectableCell.js b/front/odiparpack/app/components/Tables/tableParts/SelectableCell.js new file mode 100644 index 0000000..66fde97 --- /dev/null +++ b/front/odiparpack/app/components/Tables/tableParts/SelectableCell.js @@ -0,0 +1,46 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import css from 'ba-styles/Table.scss'; + +import { Select, MenuItem, TableCell } from '@material-ui/core'; + +class SelectableCell extends React.Component { + handleChange = event => { + this.props.updateRow(event, this.props.branch); + this.setState({ [event.target.name]: event.target.value }); + }; + + render() { + const { + cellData, + edited, + options, + } = this.props; + return ( + <TableCell padding="none"> + <Select + name={cellData.type} + id={cellData.id.toString()} + className={css.crudInput} + value={cellData.value} + onChange={this.handleChange} + displayEmpty + disabled={!edited} + margin="none" + > + {options.map((option, index) => <MenuItem value={option} key={index.toString()}>{option}</MenuItem>)} + </Select> + </TableCell> + ); + } +} + +SelectableCell.propTypes = { + options: PropTypes.array.isRequired, + cellData: PropTypes.object.isRequired, + updateRow: PropTypes.func.isRequired, + edited: PropTypes.bool.isRequired, + branch: PropTypes.string.isRequired, +}; + +export default SelectableCell; diff --git a/front/odiparpack/app/components/Tables/tableParts/TableHeader.js b/front/odiparpack/app/components/Tables/tableParts/TableHeader.js new file mode 100644 index 0000000..4ef6b0d --- /dev/null +++ b/front/odiparpack/app/components/Tables/tableParts/TableHeader.js @@ -0,0 +1,74 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { TableCell, TableHead, TableRow, TableSortLabel, Checkbox, Tooltip } from '@material-ui/core'; + +class TableHeader extends React.Component { + createSortHandler = property => event => { + this.props.onRequestSort(event, property); + }; + + render() { + const { + onSelectAllClick, + order, + orderBy, + numSelected, + rowCount, + columnData, + checkcell + } = this.props; + + return ( + <TableHead> + <TableRow> + {checkcell + && ( + <TableCell padding="checkbox" width="80"> + <Checkbox + indeterminate={numSelected > 0 && numSelected < rowCount} + checked={numSelected === rowCount} + onChange={onSelectAllClick} + /> + </TableCell> + ) + } + {columnData.map(column => ( + <TableCell + key={column.id} + align={column.numeric ? 'right' : 'left'} + padding={column.disablePadding ? 'none' : 'default'} + sortDirection={orderBy === column.id ? order : false} + > + <Tooltip + title="Sort" + placement={column.numeric ? 'bottom-end' : 'bottom-start'} + enterDelay={300} + > + <TableSortLabel + active={orderBy === column.id} + direction={order} + onClick={this.createSortHandler(column.id)} + > + {column.label} + </TableSortLabel> + </Tooltip> + </TableCell> + ), this)} + </TableRow> + </TableHead> + ); + } +} + +TableHeader.propTypes = { + numSelected: PropTypes.number.isRequired, + onRequestSort: PropTypes.func.isRequired, + onSelectAllClick: PropTypes.func.isRequired, + order: PropTypes.string.isRequired, + orderBy: PropTypes.string.isRequired, + rowCount: PropTypes.number.isRequired, + columnData: PropTypes.array.isRequired, + checkcell: PropTypes.bool.isRequired, +}; + +export default TableHeader; diff --git a/front/odiparpack/app/components/Tables/tableParts/TableToolbar.js b/front/odiparpack/app/components/Tables/tableParts/TableToolbar.js new file mode 100644 index 0000000..940a82c --- /dev/null +++ b/front/odiparpack/app/components/Tables/tableParts/TableToolbar.js @@ -0,0 +1,123 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { withStyles } from '@material-ui/core/styles'; +import DeleteIcon from '@material-ui/icons/Delete'; +import ArchiveIcon from '@material-ui/icons/Archive'; +import BookmarkIcon from '@material-ui/icons/Bookmark'; +import FilterListIcon from '@material-ui/icons/FilterList'; +import SearchIcon from '@material-ui/icons/Search'; +import { + Toolbar, + Typography, + IconButton, + Tooltip, + FormControl, + Input, + InputAdornment, +} from '@material-ui/core'; +import styles from './tableStyle-jss'; + + +class TableToolbar extends React.Component { + state = { + showSearch: false, + } + + toggleSearch() { + this.setState({ showSearch: !this.state.showSearch }); + } + + handleChange(event) { + event.persist(); + this.props.onUserInput(event.target.value); + } + + render() { + const { numSelected, classes, filterText } = this.props; + const { showSearch } = this.state; + + return ( + <Toolbar + className={classNames(classes.root, { + [classes.highlight]: numSelected > 0, + })} + > + <div className={classes.titleToolbar}> + {numSelected > 0 ? ( + <Typography color="inherit" variant="subtitle1"> + {numSelected} + {' '} +selected + </Typography> + ) : ( + <Typography variant="h6">Nutrition</Typography> + )} + </div> + <div className={classes.spacer} /> + <div className={classes.actionsToolbar}> + {numSelected > 0 ? ( + <div> + <Tooltip title="Bookmark"> + <IconButton aria-label="Bookmark"> + <BookmarkIcon /> + </IconButton> + </Tooltip> + <Tooltip title="Archive"> + <IconButton aria-label="Archive"> + <ArchiveIcon /> + </IconButton> + </Tooltip> + <Tooltip title="Delete"> + <IconButton aria-label="Delete"> + <DeleteIcon /> + </IconButton> + </Tooltip> + </div> + ) : ( + <div className={classes.actions}> + {showSearch + && ( + <FormControl className={classNames(classes.textField)}> + <Input + id="search_filter" + type="text" + placeholder="Search Desert" + value={filterText} + onChange={(event) => this.handleChange(event)} + endAdornment={( + <InputAdornment position="end"> + <IconButton aria-label="Search filter"> + <SearchIcon /> + </IconButton> + </InputAdornment> + )} + /> + </FormControl> + ) + } + <Tooltip title="Filter list"> + <IconButton + aria-label="Filter list" + className={classes.filterBtn} + onClick={() => this.toggleSearch()} + > + <FilterListIcon /> + </IconButton> + </Tooltip> + </div> + )} + </div> + </Toolbar> + ); + } +} + +TableToolbar.propTypes = { + classes: PropTypes.object.isRequired, + filterText: PropTypes.string.isRequired, + onUserInput: PropTypes.func.isRequired, + numSelected: PropTypes.number.isRequired, +}; + +export default withStyles(styles)(TableToolbar); diff --git a/front/odiparpack/app/components/Tables/tableParts/TimePickerCell.js b/front/odiparpack/app/components/Tables/tableParts/TimePickerCell.js new file mode 100644 index 0000000..941df31 --- /dev/null +++ b/front/odiparpack/app/components/Tables/tableParts/TimePickerCell.js @@ -0,0 +1,66 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { TimePicker, MuiPickersUtilsProvider } from '@material-ui/pickers'; +import MomentUtils from '@date-io/moment'; +import css from 'ba-styles/Table.scss'; + +import { TableCell, InputAdornment, Icon, IconButton } from '@material-ui/core'; + +class TimePickerCell extends React.Component { + state = { + event: { + target: { + name: this.props.cellData.type, // eslint-disable-line + value: this.props.cellData.value, // eslint-disable-line + } + } + } + + handleTimeChange = date => { + const { event } = this.state; + const { updateRow, branch } = this.props; + event.target.value = date; + updateRow(event, branch); + } + + render() { + const { + edited, + cellData + } = this.props; + const { event } = this.state; + return ( + <TableCell padding="none"> + <MuiPickersUtilsProvider utils={MomentUtils}> + <TimePicker + name={cellData.type} + className={css.crudInput} + mask={[/\d/, /\d/, ':', /\d/, /\d/, ' ', /a|p/i, 'M']} + placeholder="08:00 AM" + value={event.target.value} + disabled={!edited} + InputProps={{ + endAdornment: ( + <InputAdornment position="end"> + <IconButton> + <Icon>access_time</Icon> + </IconButton> + </InputAdornment> + ), + }} + onChange={this.handleTimeChange} + /> + </MuiPickersUtilsProvider> + </TableCell> + ); + } +} + +TimePickerCell.propTypes = { + cellData: PropTypes.object.isRequired, + updateRow: PropTypes.func.isRequired, + edited: PropTypes.bool.isRequired, + branch: PropTypes.string.isRequired, +}; + +export default TimePickerCell; diff --git a/front/odiparpack/app/components/Tables/tableParts/ToggleCell.js b/front/odiparpack/app/components/Tables/tableParts/ToggleCell.js new file mode 100644 index 0000000..dc0af89 --- /dev/null +++ b/front/odiparpack/app/components/Tables/tableParts/ToggleCell.js @@ -0,0 +1,50 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import css from 'ba-styles/Table.scss'; + +import { TableCell, FormControlLabel, Switch } from '@material-ui/core'; + +class ToggleCell extends React.Component { + state = { + isChecked: this.props.cellData.value + }; + + handleChange = event => { + this.setState({ isChecked: event.target.checked }); + this.props.updateRow(event, this.props.branch); + }; + + render() { + const { + cellData, + edited, + } = this.props; + return ( + <TableCell className={css.toggleCell} padding="none" textalign="center"> + <div className={classNames(css.coverReadonly, !edited ? css.show : '')} /> + <FormControlLabel + control={( + <Switch + name={cellData.type} + id={cellData.id.toString()} + className={css.crudInput} + checked={this.state.isChecked} + onChange={this.handleChange} + value={cellData.value.toString()} + /> + )} + /> + </TableCell> + ); + } +} + +ToggleCell.propTypes = { + cellData: PropTypes.object.isRequired, + updateRow: PropTypes.func.isRequired, + edited: PropTypes.bool.isRequired, + branch: PropTypes.string.isRequired, +}; + +export default ToggleCell; diff --git a/front/odiparpack/app/components/Tables/tableParts/tableStyle-jss.js b/front/odiparpack/app/components/Tables/tableParts/tableStyle-jss.js new file mode 100644 index 0000000..bede0b8 --- /dev/null +++ b/front/odiparpack/app/components/Tables/tableParts/tableStyle-jss.js @@ -0,0 +1,63 @@ +import { lighten } from '@material-ui/core/styles/colorManipulator'; +const styles = theme => ({ + root: { + paddingRight: theme.spacing(1), + }, + rootTable: { + width: '100%', + marginTop: theme.spacing(3), + overflowX: 'auto', + }, + highlight: + theme.palette.type === 'light' ? { + color: theme.palette.secondary.main, + backgroundColor: lighten(theme.palette.secondary.light, 0.85), + } : { + color: theme.palette.text.primary, + backgroundColor: theme.palette.secondary.dark, + }, + spacer: { + flex: '1 1 100%', + }, + actionsToolbar: { + color: theme.palette.text.secondary, + flex: '1 0 auto', + }, + titleToolbar: { + flex: '0 0 auto', + }, + filterBtn: { + top: -5, + }, + textField: { + flexBasis: 200, + width: 300 + }, + table: { + minWidth: 900, + }, + actions: { + color: theme.palette.text.secondary, + margin: 10 + }, + toolbar: { + backgroundColor: theme.palette.grey[800], + }, + title: { + flex: '0 0 auto', + '& h6': { + color: theme.palette.common.white + } + }, + button: { + margin: theme.spacing(1), + }, + iconSmall: { + fontSize: 20, + }, + leftIcon: { + marginRight: theme.spacing(1), + }, +}); + +export default styles; diff --git a/front/odiparpack/app/components/TemplateSettings/ThemeThumb.js b/front/odiparpack/app/components/TemplateSettings/ThemeThumb.js new file mode 100644 index 0000000..df16917 --- /dev/null +++ b/front/odiparpack/app/components/TemplateSettings/ThemeThumb.js @@ -0,0 +1,74 @@ +import React from 'react'; +import classNames from 'classnames'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { withStyles } from '@material-ui/core/styles'; +import themePalette from 'ba-api/themePalette'; +import { Radio, Paper } from '@material-ui/core'; +import styles from './themeStyles-jss'; + +const ThemeThumb = props => { + const { classes } = props; + return ( + <Paper className={classNames(classes.thumb, props.theme === props.value ? classes.selectedTheme : '')}> + <Radio + checked={props.selectedValue === props.value} + value={props.value} + onChange={props.handleChange} + /> + { props.name } + <div className={classes.appPreview}> + <header style={{ background: themePalette(props.value).palette.primary.main }} /> + <aside> + <ul> + {[0, 1, 2, 3].map(index => { + if (index === 1) { + return ( + <li key={index.toString()}> + <i style={{ background: themePalette(props.value).palette.secondary.main }} /> + <p style={{ background: themePalette(props.value).palette.secondary.main }} /> + </li> + ); + } + return ( + <li key={index.toString()}> + <i style={{ background: themePalette(props.value).palette.secondary.main }} /> + <p /> + </li> + ); + })} + </ul> + </aside> + <Paper className={classes.content}> + <div className={classes.title} style={{ background: themePalette(props.value).palette.primary.main }} /> + <div className={classes.ctn1} style={{ background: themePalette(props.value).palette.secondary.main }} /> + <div className={classes.ctn2} style={{ background: themePalette(props.value).palette.primary.light }} /> + <div className={classes.ctn2} style={{ background: themePalette(props.value).palette.primary.light }} /> + <div className={classes.ctn2} style={{ background: themePalette(props.value).palette.secondary.light }} /> + <div className={classes.ctn2} style={{ background: themePalette(props.value).palette.secondary.light }} /> + </Paper> + </div> + </Paper> + ); +}; + +ThemeThumb.propTypes = { + value: PropTypes.string.isRequired, + theme: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + selectedValue: PropTypes.string.isRequired, + classes: PropTypes.object.isRequired, + handleChange: PropTypes.func.isRequired, +}; + +// Redux +const reducer = 'ui'; +const mapStateToProps = state => ({ + theme: state.getIn([reducer, 'theme']), +}); + +const ThumbsMapped = connect( + mapStateToProps, +)(ThemeThumb); + +export default withStyles(styles)(ThumbsMapped); diff --git a/front/odiparpack/app/components/TemplateSettings/index.js b/front/odiparpack/app/components/TemplateSettings/index.js new file mode 100644 index 0000000..d36f5ec --- /dev/null +++ b/front/odiparpack/app/components/TemplateSettings/index.js @@ -0,0 +1,93 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { + FormControl, + Grid, + FormControlLabel, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Icon, + Slide, + Button, +} from '@material-ui/core'; +import styles from './themeStyles-jss'; +import ThemeThumb from './ThemeThumb'; + +const Transition = React.forwardRef(function Transition(props, ref) { // eslint-disable-line + return <Slide direction="left" ref={ref} {...props} />; +}); + +function TemplateSettings(props) { + const { + classes, + palette, + open, + selectedValue, + changeTheme, + close + } = props; + + const getItem = dataArray => dataArray.map((item, index) => ( + <FormControlLabel + key={index.toString()} + className={classes.themeField} + control={( + <ThemeThumb + value={item.value} + selectedValue={selectedValue} + handleChange={changeTheme} + name={item.name} + /> + )} + /> + )); + + return ( + <Dialog + open={open} + TransitionComponent={Transition} + keepMounted + onClose={close} + className={classes.themeChooser} + aria-labelledby="alert-dialog-slide-title" + aria-describedby="alert-dialog-slide-description" + maxWidth="md" + > + <DialogTitle id="alert-dialog-slide-title"> + <Icon>palette</Icon> + {' '} + Choose Theme + </DialogTitle> + <DialogContent> + <Grid container> + <FormControl component="fieldset" className={classes.group}> + { palette !== undefined && getItem(palette) } + </FormControl> + </Grid> + </DialogContent> + <DialogActions> + <Button onClick={close} color="primary"> + Done + </Button> + </DialogActions> + </Dialog> + ); +} + +TemplateSettings.propTypes = { + classes: PropTypes.object.isRequired, + palette: PropTypes.object, + selectedValue: PropTypes.string.isRequired, + changeTheme: PropTypes.func.isRequired, + close: PropTypes.func.isRequired, + open: PropTypes.bool.isRequired, +}; + +TemplateSettings.defaultProps = { + palette: undefined +}; + +export default withStyles(styles)(TemplateSettings); diff --git a/front/odiparpack/app/components/TemplateSettings/themeStyles-jss.js b/front/odiparpack/app/components/TemplateSettings/themeStyles-jss.js new file mode 100644 index 0000000..efbac9c --- /dev/null +++ b/front/odiparpack/app/components/TemplateSettings/themeStyles-jss.js @@ -0,0 +1,106 @@ +const styles = theme => ({ + group: { + margin: `${theme.spacing(1)}px 0`, + maxWidth: 1000, + display: 'block', + '& label': { + display: 'inline-block', + margin: 0, + width: '99%', + [theme.breakpoints.up('sm')]: { + width: '45%' + }, + [theme.breakpoints.up('md')]: { + width: '33.33%' + }, + }, + '& > label': { + position: 'relative', + '& > span': { + marginTop: 10, + display: 'block', + textAlign: 'center', + position: 'absolute', + top: 12, + left: 48, + } + } + }, + thumb: { + margin: theme.spacing(1) + }, + selectedTheme: { + boxShadow: `0px 1px 5px 0px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 3px 1px -2px rgba(0, 0, 0, 0.12), 0 0 0px 4px ${theme.palette.secondary.main}` + }, + content: {}, + title: {}, + ctn1: {}, + ctn2: {}, + appPreview: { + width: '100%', + padding: 5, + height: 200, + position: 'relative', + display: 'flex', + background: theme.palette.grey[50], + '& header': { + width: '100%', + height: 50, + background: theme.palette.primary.main, + position: 'absolute', + top: 0, + left: 0 + }, + '& aside': { + width: '30%', + marginTop: 70, + '& li': { + margin: '0 10px 10px 5px', + display: 'flex', + '& i': { + borderRadius: '50%', + width: 8, + height: 8, + marginRight: 5, + marginTop: -3, + background: theme.palette.secondary.main, + }, + '& p': { + width: 40, + height: 2, + background: theme.palette.grey[400], + } + } + }, + '& $content': { + padding: 10, + marginTop: 20, + width: '70%', + zIndex: 10, + marginBottom: 10, + '& $title': { + background: theme.palette.primary.main, + height: 5, + width: '60%', + marginBottom: 10 + }, + '& $ctn1': { + margin: '5px 5px 10px 0', + width: '100%', + height: 30, + background: theme.palette.secondary.main, + display: 'block' + }, + '& $ctn2': { + width: '50%', + marginLeft: 0, + height: 40, + border: '2px solid #FFF', + background: theme.palette.secondary.light, + display: 'inline-block' + } + } + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/Widget/AlbumWidget.js b/front/odiparpack/app/components/Widget/AlbumWidget.js new file mode 100644 index 0000000..d691361 --- /dev/null +++ b/front/odiparpack/app/components/Widget/AlbumWidget.js @@ -0,0 +1,55 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import InfoIcon from '@material-ui/icons/Info'; +import imgData from 'ba-api/imgData'; +import { GridList, GridListTile, GridListTileBar, IconButton } from '@material-ui/core'; +import PapperBlock from '../PapperBlock/PapperBlock'; +import styles from './widget-jss'; + + +class AlbumWidget extends React.Component { + render() { + const { classes } = this.props; + return ( + <PapperBlock noMargin title="My Albums (4)" whiteBg desc=""> + <div className={classes.albumRoot}> + <GridList cellHeight={180} className={classes.gridList}> + { + imgData.map((tile, index) => { + if (index >= 4) { + return false; + } + return ( + <GridListTile key={index.toString()}> + <img src={tile.img} className={classes.img} alt={tile.title} /> + <GridListTileBar + title={tile.title} + subtitle={( + <span> +by: + {tile.author} + </span> + )} + actionIcon={( + <IconButton className={classes.icon}> + <InfoIcon /> + </IconButton> + )} + /> + </GridListTile> + ); + }) + } + </GridList> + </div> + </PapperBlock> + ); + } +} + +AlbumWidget.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(AlbumWidget); diff --git a/front/odiparpack/app/components/Widget/AreaChartWidget.js b/front/odiparpack/app/components/Widget/AreaChartWidget.js new file mode 100644 index 0000000..8e17311 --- /dev/null +++ b/front/odiparpack/app/components/Widget/AreaChartWidget.js @@ -0,0 +1,147 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles, createMuiTheme } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import CardGiftcard from '@material-ui/icons/CardGiftcard'; +import FilterVintage from '@material-ui/icons/FilterVintage'; +import LocalCafe from '@material-ui/icons/LocalCafe'; +import Style from '@material-ui/icons/Style'; +import themePallete from 'ba-api/themePalette'; +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer +} from 'recharts'; +import messageStyles from 'ba-styles/Messages.scss'; +import { data1 } from 'ba-api/chartData'; +import Type from 'ba-styles/Typography.scss'; +import { purple } from '@material-ui/core/colors'; +import { Grid, Chip, Avatar, Divider, CircularProgress, Typography } from '@material-ui/core'; +import styles from './widget-jss'; +import PapperBlock from '../PapperBlock/PapperBlock'; + + +const theme = createMuiTheme(themePallete('magentaTheme')); +const color = ({ + primary: theme.palette.primary.main, + primarydark: theme.palette.primary.dark, + secondary: theme.palette.secondary.main, + secondarydark: theme.palette.secondary.dark, + third: purple[500], + thirddark: purple[900], +}); + +class AreaChartWidget extends PureComponent { + render() { + const { + classes, + } = this.props; + return ( + <PapperBlock whiteBg noMargin title="Top Product Sales" desc=""> + <Grid container spacing={2}> + <Grid item md={8} xs={12}> + <ul className={classes.bigResume}> + <li> + <Avatar className={classNames(classes.avatar, classes.pinkAvatar)}> + <CardGiftcard /> + </Avatar> + <Typography variant="h6"> + 4321 + <Typography>Gift Card</Typography> + </Typography> + </li> + <li> + <Avatar className={classNames(classes.avatar, classes.purpleAvatar)}> + <FilterVintage /> + </Avatar> + <Typography variant="h6"> + 9876 + <Typography>Flowers</Typography> + </Typography> + </li> + <li> + <Avatar className={classNames(classes.avatar, classes.blueAvatar)}> + <LocalCafe /> + </Avatar> + <Typography variant="h6"> + 345 + <Typography>Cups</Typography> + </Typography> + </li> + <li> + <Avatar className={classNames(classes.avatar, classes.tealAvatar)}> + <Style /> + </Avatar> + <Typography variant="h6"> + 996 + <Typography>Name Cards</Typography> + </Typography> + </li> + </ul> + <div className={classes.chartWrap}> + <div className={classes.chartFluid}> + <ResponsiveContainer> + <AreaChart + width={600} + height={300} + data={data1} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <XAxis dataKey="name" /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip /> + <Area type="monotone" dataKey="uv" stackId="1" stroke={color.primarydark} fillOpacity="0.8" fill={color.primary} /> + <Area type="monotone" dataKey="pv" stackId="1" stroke={color.secondarydark} fillOpacity="0.8" fill={color.secondary} /> + <Area type="monotone" dataKey="amt" stackId="1" stroke={color.thirddark} fillOpacity="0.8" fill={color.third} /> + </AreaChart> + </ResponsiveContainer> + </div> + </div> + </Grid> + <Grid item md={4} xs={12}> + <Typography variant="button"><span className={Type.bold}>Performance Listing</span></Typography> + <Divider className={classes.divider} /> + <Grid container className={classes.secondaryWrap}> + <Grid item className={classes.centerItem} md={6}> + <Typography gutterBottom>Giftcard Quality</Typography> + <Chip label="Super" className={classNames(classes.chip, messageStyles.bgError)} /> + <CircularProgress variant="determinate" className={classNames(classes.progressCircle, classes.pinkProgress)} size={100} value={70} /> + </Grid> + <Grid className={classes.centerItem} item md={6}> + <Typography gutterBottom>Monitoring Quality</Typography> + <Chip label="Good" className={classNames(classes.chip, messageStyles.bgSuccess)} /> + <CircularProgress variant="determinate" className={classNames(classes.progressCircle, classes.greenProgress)} size={100} value={57} /> + </Grid> + <Grid className={classes.centerItem} item md={6}> + <Typography gutterBottom>Project Complete</Typography> + <Chip label="Poor" className={classNames(classes.chip, messageStyles.bgWarning)} /> + <CircularProgress variant="determinate" className={classNames(classes.progressCircle, classes.orangeProgress)} size={100} value={28} /> + </Grid> + <Grid className={classes.centerItem} item md={6}> + <Typography gutterBottom>Deploy Progress</Typography> + <Chip label="Medium" className={classNames(classes.chip, messageStyles.bgInfo)} /> + <CircularProgress variant="determinate" className={classNames(classes.progressCircle, classes.blueProgress)} size={100} value={70} /> + </Grid> + </Grid> + </Grid> + </Grid> + </PapperBlock> + ); + } +} + +AreaChartWidget.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(AreaChartWidget); diff --git a/front/odiparpack/app/components/Widget/BigChartWidget.js b/front/odiparpack/app/components/Widget/BigChartWidget.js new file mode 100644 index 0000000..718af45 --- /dev/null +++ b/front/odiparpack/app/components/Widget/BigChartWidget.js @@ -0,0 +1,142 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import Dvr from '@material-ui/icons/Dvr'; +import Explore from '@material-ui/icons/Explore'; +import Healing from '@material-ui/icons/Healing'; +import LocalActivity from '@material-ui/icons/LocalActivity'; +import { + ComposedChart, + Line, + Area, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer +} from 'recharts'; +import { data1 } from 'ba-api/chartData'; +import Type from 'ba-styles/Typography.scss'; +import colorfull from 'ba-api/colorfull'; +import { Grid, Avatar, Divider, LinearProgress, Typography } from '@material-ui/core'; +import styles from './widget-jss'; +import PapperBlock from '../PapperBlock/PapperBlock'; + + +const color = ({ + main: colorfull[5], + maindark: '#2196F3', + secondary: colorfull[3], + third: colorfull[0], +}); + +class BigChartWidget extends PureComponent { + render() { + const { + classes, + } = this.props; + return ( + <PapperBlock whiteBg noMargin title="Top Product Sales" desc=""> + <Grid container spacing={2}> + <Grid item md={8} xs={12}> + <ul className={classes.bigResume}> + <li> + <Avatar className={classNames(classes.avatar, classes.pinkAvatar)}> + <Dvr /> + </Avatar> + <Typography variant="h6"> + 1234 + <Typography>Monitors</Typography> + </Typography> + </li> + <li> + <Avatar className={classNames(classes.avatar, classes.purpleAvatar)}> + <Explore /> + </Avatar> + <Typography variant="h6"> + 5678 + <Typography>Compas</Typography> + </Typography> + </li> + <li> + <Avatar className={classNames(classes.avatar, classes.blueAvatar)}> + <Healing /> + </Avatar> + <Typography variant="h6"> + 910 + <Typography>Badges</Typography> + </Typography> + </li> + <li> + <Avatar className={classNames(classes.avatar, classes.tealAvatar)}> + <LocalActivity /> + </Avatar> + <Typography variant="h6"> + 1112 + <Typography>Tickets</Typography> + </Typography> + </li> + </ul> + <div className={classes.chartWrap}> + <div className={classes.chartFluid}> + <ResponsiveContainer> + <ComposedChart + data={data1} + margin={{ + top: 0, + right: 30, + left: 20, + bottom: 20 + }} + > + <CartesianGrid stroke="#f5f5f5" /> + <XAxis dataKey="name" /> + <YAxis /> + <Tooltip /> + <Area type="monotone" dataKey="amt" fill={color.main} stroke={color.maindark} /> + <Bar dataKey="pv" barSize={20} fill={color.secondary} /> + <Line type="monotone" dataKey="uv" stroke={color.third} /> + </ComposedChart> + </ResponsiveContainer> + </div> + </div> + </Grid> + <Grid item md={4} xs={12}> + <Typography variant="button"><span className={Type.bold}>Performance Listing</span></Typography> + <Divider className={classes.divider} /> + <ul className={classes.secondaryWrap}> + <li> + <Typography gutterBottom>Monitoring Quality</Typography> + <LinearProgress variant="determinate" className={classNames(classes.progress, classes.pinkProgress)} value={24} /> + </li> + <li> + <Typography gutterBottom>Compas Speed</Typography> + <LinearProgress variant="determinate" className={classNames(classes.progress, classes.purpleProgress)} value={89} /> + </li> + <li> + <Typography gutterBottom>Total Badges</Typography> + <LinearProgress variant="determinate" className={classNames(classes.progress, classes.orangeProgress)} value={78} /> + </li> + <li> + <Typography gutterBottom>Sold Ticket</Typography> + <LinearProgress variant="determinate" className={classNames(classes.progress, classes.greenProgress)} value={55} /> + </li> + <li> + <Typography gutterBottom>App Performance</Typography> + <LinearProgress variant="determinate" className={classNames(classes.progress, classes.blueProgress)} value={80} /> + </li> + </ul> + </Grid> + </Grid> + </PapperBlock> + ); + } +} + +BigChartWidget.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(BigChartWidget); diff --git a/front/odiparpack/app/components/Widget/CarouselWidget.js b/front/odiparpack/app/components/Widget/CarouselWidget.js new file mode 100644 index 0000000..51dd522 --- /dev/null +++ b/front/odiparpack/app/components/Widget/CarouselWidget.js @@ -0,0 +1,117 @@ +import React from 'react'; +import Slider from 'react-slick'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import ArrowForward from '@material-ui/icons/ArrowForward'; +import ArrowBack from '@material-ui/icons/ArrowBack'; +import carouselData from 'ba-api/carouselData'; +import 'ba-styles/vendors/slick-carousel/slick-carousel.css'; +import 'ba-styles/vendors/slick-carousel/slick.css'; +import 'ba-styles/vendors/slick-carousel/slick-theme.css'; +import { Typography, IconButton, Icon } from '@material-ui/core'; +import styles from './widget-jss'; + + +function SampleNextArrow(props) { + const { onClick } = props; + return ( + <IconButton + className="nav-next" + onClick={onClick} + > + <ArrowForward /> + </IconButton> + ); +} + +SampleNextArrow.propTypes = { + onClick: PropTypes.func, +}; + +SampleNextArrow.defaultProps = { + onClick: undefined, +}; + +function SamplePrevArrow(props) { + const { onClick } = props; + return ( + <IconButton + className="nav-prev" + onClick={onClick} + > + <ArrowBack /> + </IconButton> + ); +} + +SamplePrevArrow.propTypes = { + onClick: PropTypes.func, +}; + +SamplePrevArrow.defaultProps = { + onClick: undefined, +}; + +class CarouselWidget extends React.Component { + render() { + const { classes } = this.props; + const settings = { + dots: true, + infinite: true, + centerMode: false, + speed: 500, + autoplaySpeed: 5000, + pauseOnHover: true, + autoplay: true, + slidesToShow: 3, + slidesToScroll: 1, + responsive: [ + { + breakpoint: 960, + settings: { + slidesToShow: 2, + slidesToScroll: 1, + infinite: true, + dots: true + } + }, + { + breakpoint: 600, + settings: { + slidesToShow: 1, + slidesToScroll: 1, + infinite: true, + dots: true + } + }, + ], + cssEase: 'ease-out', + nextArrow: <SampleNextArrow />, + prevArrow: <SamplePrevArrow /> + }; + return ( + <div className="container custom-arrow"> + <Slider {...settings}> + {carouselData.map((item, index) => ( + <div key={index.toString()}> + <div style={{ backgroundColor: item.background }} className={classes.carouselItem}> + <Icon className={classes.iconBg}>{item.icon}</Icon> + <Typography className={classes.carouselTitle} variant="subtitle1"> + <Icon>{item.icon}</Icon> + {item.title} + </Typography> + <Typography className={classes.carouselDesc}>{item.desc}</Typography> + </div> + </div> + ))} + </Slider> + </div> + ); + } +} + +CarouselWidget.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CarouselWidget); diff --git a/front/odiparpack/app/components/Widget/CounterGroupWidget.js b/front/odiparpack/app/components/Widget/CounterGroupWidget.js new file mode 100644 index 0000000..92856b8 --- /dev/null +++ b/front/odiparpack/app/components/Widget/CounterGroupWidget.js @@ -0,0 +1,101 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { + BarChart, Bar, + AreaChart, Area, + LineChart, Line, + PieChart, Pie, Cell +} from 'recharts'; +import { data1, data2 } from 'ba-api/chartMiniData'; +import colorfull from 'ba-api/colorfull'; +import { red, blue, cyan, lime } from '@material-ui/core/colors'; +import { Grid } from '@material-ui/core'; +import CounterWidget from '../Counter/CounterWidget'; +import styles from './widget-jss'; + + +const colors = [red[300], blue[300], cyan[300], lime[300]]; + +class CounterGroupWidget extends PureComponent { + render() { + const { classes } = this.props; + return ( + <div className={classes.rootCounter}> + <Grid container spacing={3}> + <Grid item xs={6}> + <CounterWidget + color={colorfull[0]} + start={0} + end={105} + duration={3} + title="New Customers" + > + <BarChart width={100} height={40} data={data1}> + <Bar dataKey="uv" fill="#ffffff" /> + </BarChart> + </CounterWidget> + </Grid> + <Grid item xs={6}> + <CounterWidget + color={colorfull[1]} + start={0} + end={321} + duration={3} + title="New Articles" + > + <AreaChart width={100} height={60} data={data1}> + <Area type="monotone" dataKey="uv" stroke="#FFFFFF" fill="rgba(255,255,255,.5)" /> + </AreaChart> + </CounterWidget> + </Grid> + <Grid item xs={6}> + <CounterWidget + color={colorfull[2]} + start={0} + end={67} + duration={3} + title="New Contributor" + > + <LineChart width={100} height={80} data={data1}> + <Line type="monotone" dataKey="pv" stroke="#FFFFFF" strokeWidth={2} /> + </LineChart> + </CounterWidget> + </Grid> + <Grid item xs={6}> + <CounterWidget + color={colorfull[3]} + start={0} + end={80} + duration={3} + title="Average Income" + > + <PieChart width={100} height={100}> + <Pie + data={data2} + cx={50} + cy={50} + dataKey="value" + innerRadius={20} + outerRadius={40} + fill="#FFFFFF" + paddingAngle={5} + > + { + data2.map((entry, index) => <Cell key={index.toString()} fill={colors[index % colors.length]} />) + } + </Pie> + </PieChart> + </CounterWidget> + </Grid> + </Grid> + </div> + ); + } +} + +CounterGroupWidget.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CounterGroupWidget); diff --git a/front/odiparpack/app/components/Widget/CounterIconsWidget.js b/front/odiparpack/app/components/Widget/CounterIconsWidget.js new file mode 100644 index 0000000..9c10850 --- /dev/null +++ b/front/odiparpack/app/components/Widget/CounterIconsWidget.js @@ -0,0 +1,74 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import AccountBox from '@material-ui/icons/AccountBox'; +import ImportContacts from '@material-ui/icons/ImportContacts'; +import Pets from '@material-ui/icons/Pets'; +import Star from '@material-ui/icons/Star'; +import colorfull from 'ba-api/colorfull'; +import { Grid } from '@material-ui/core'; +import CounterWidget from '../Counter/CounterWidget'; +import styles from './widget-jss'; + + +class CounterIconWidget extends PureComponent { + render() { + const { classes } = this.props; + return ( + <div className={classes.rootCounterFull}> + <Grid container spacing={2}> + <Grid item md={3} xs={6}> + <CounterWidget + color={colorfull[0]} + start={0} + end={105} + duration={3} + title="New Customers" + > + <AccountBox className={classes.counterIcon} /> + </CounterWidget> + </Grid> + <Grid item md={3} xs={6}> + <CounterWidget + color={colorfull[1]} + start={0} + end={321} + duration={3} + title="New Articles" + > + <ImportContacts className={classes.counterIcon} /> + </CounterWidget> + </Grid> + <Grid item md={3} xs={6}> + <CounterWidget + color={colorfull[2]} + start={0} + end={67} + duration={3} + title="New Contributor" + > + <Pets className={classes.counterIcon} /> + </CounterWidget> + </Grid> + <Grid item md={3} xs={6}> + <CounterWidget + color={colorfull[3]} + start={0} + end={80} + duration={3} + title="Average Income" + > + <Star className={classes.counterIcon} /> + </CounterWidget> + </Grid> + </Grid> + </div> + ); + } +} + +CounterIconWidget.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CounterIconWidget); diff --git a/front/odiparpack/app/components/Widget/MapWidget.js b/front/odiparpack/app/components/Widget/MapWidget.js new file mode 100644 index 0000000..37204e9 --- /dev/null +++ b/front/odiparpack/app/components/Widget/MapWidget.js @@ -0,0 +1,60 @@ +import React from 'react'; +import { compose } from 'recompose'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import dummy from 'ba-api/dummyContents'; +import { + withScriptjs, + withGoogleMap, + GoogleMap, + Marker, +} from 'react-google-maps'; +import { Paper } from '@material-ui/core'; +import IdentityCard from '../CardPaper/IdentityCard'; +import styles from './widget-jss'; + +const MapWithAMarker = compose( + withScriptjs, + withGoogleMap +)(props => ( + <GoogleMap + {...props} + defaultZoom={8} + defaultCenter={{ lat: -34.300, lng: 119.344 }} + > + <Marker + position={{ lat: -34.300, lng: 118.044 }} + /> + </GoogleMap> +)); + +class MapWidget extends React.Component { + render() { + const { classes } = this.props; + return ( + <Paper className={classes.mapWrap}> + <MapWithAMarker + googleMapURL="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places" + loadingElement={<div style={{ height: '100%' }} />} + containerElement={<div style={{ height: '400px' }} />} + mapElement={<div style={{ height: '100%' }} />} + /> + <div className={classes.address}> + <IdentityCard + title="Contact and Address" + name={dummy.user.name} + avatar={dummy.user.avatar} + phone="(+8543201213)" + address="Town Hall Building no.45 Block C - ABC Street" + /> + </div> + </Paper> + ); + } +} + +MapWidget.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(MapWidget); diff --git a/front/odiparpack/app/components/Widget/ProfileWidget.js b/front/odiparpack/app/components/Widget/ProfileWidget.js new file mode 100644 index 0000000..3ae3690 --- /dev/null +++ b/front/odiparpack/app/components/Widget/ProfileWidget.js @@ -0,0 +1,54 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import LocalPhone from '@material-ui/icons/LocalPhone'; +import DateRange from '@material-ui/icons/DateRange'; +import LocationOn from '@material-ui/icons/LocationOn'; +import { + List, ListItem, ListItemAvatar, + ListItemText, Divider, Avatar +} from '@material-ui/core'; +import PapperBlock from '../PapperBlock/PapperBlock'; +import styles from './widget-jss'; + + +function ProfileWidget(props) { + const { classes } = props; + return ( + <PapperBlock title="About Me" whiteBg noMargin desc="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum."> + <Divider className={classes.divider} /> + <List dense className={classes.profileList}> + <ListItem> + <ListItemAvatar> + <Avatar> + <DateRange /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Born" secondary="Jan 9, 1994" /> + </ListItem> + <ListItem> + <ListItemAvatar> + <Avatar> + <LocalPhone /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Phone" secondary="(+62)8765432190" /> + </ListItem> + <ListItem> + <ListItemAvatar> + <Avatar> + <LocationOn /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Address" secondary="Chicendo Street no.105 Block A/5A - Barcelona, Spain" /> + </ListItem> + </List> + </PapperBlock> + ); +} + +ProfileWidget.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ProfileWidget); diff --git a/front/odiparpack/app/components/Widget/ProgressWidget.js b/front/odiparpack/app/components/Widget/ProgressWidget.js new file mode 100644 index 0000000..c0e6db6 --- /dev/null +++ b/front/odiparpack/app/components/Widget/ProgressWidget.js @@ -0,0 +1,39 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Type from 'ba-styles/Typography.scss'; +import Check from '@material-ui/icons/Check'; +import { withStyles } from '@material-ui/core/styles'; +import { LinearProgress, Paper, Typography, Grid, Avatar, Chip } from '@material-ui/core'; +import styles from './widget-jss'; + + +function ProgressWidget(props) { + const { classes } = props; + return ( + <Paper className={classes.styledPaper} elevation={4}> + <Typography className={classes.title} variant="h5" component="h3"> + <span className={Type.light}>Profile Strength: </span> + <span className={Type.bold}>Intermediate</span> + </Typography> + <Grid container justify="center"> + <Chip + avatar={( + <Avatar> + <Check /> + </Avatar> + )} + label="60% Progress" + className={classes.chipProgress} + color="primary" + /> + </Grid> + <LinearProgress variant="determinate" className={classes.progressWidget} value={60} /> + </Paper> + ); +} + +ProgressWidget.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ProgressWidget); diff --git a/front/odiparpack/app/components/Widget/SliderWidget.js b/front/odiparpack/app/components/Widget/SliderWidget.js new file mode 100644 index 0000000..1f53780 --- /dev/null +++ b/front/odiparpack/app/components/Widget/SliderWidget.js @@ -0,0 +1,69 @@ +import React from 'react'; +import Type from 'ba-styles/Typography.scss'; +import Slider from 'react-animated-slider'; +import 'ba-styles/vendors/react-animated-slider/react-animated-slider.css'; +import imgApi from 'ba-api/images'; +import avatarApi from 'ba-api/avatars'; + +import { Button, Typography } from '@material-ui/core'; + +const content = [ + { + title: 'Vulputate Mollis Ultricies', + description: + 'Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum.', + button: 'Read More', + image: imgApi[3], + user: 'Luanda Gjokaj', + userProfile: avatarApi[1] + }, + { + title: 'Tortor Dapibus', + description: + 'Cras mattis consectetur purus sit amet fermentum.', + button: 'Discover', + image: imgApi[15], + user: 'Erich Behrens', + userProfile: avatarApi[8] + }, + { + title: 'Phasellus volutpat', + description: + 'Lorem ipsum dolor sit amet', + button: 'Buy now', + image: imgApi[29], + user: 'Bruno Vizovskyy', + userProfile: avatarApi[10] + } +]; + +const SliderWidget = () => ( + <div> + <Slider className="slider-wrapper short" autoplay={3000}> + {content.map((item, index) => ( + <div + key={index.toString()} + className="slider-content" + style={{ background: `url('${item.image}') no-repeat center center` }} + > + <div className="inner"> + <Typography variant="subtitle1" component="h3" className={Type.light} gutterBottom>{item.title}</Typography> + <Button variant="contained" color="primary"> + {item.button} + </Button> + </div> + <section> + <img src={item.userProfile} alt={item.user} /> + <span> + Posted by + {' '} + <strong>{item.user}</strong> + </span> + </section> + </div> + ))} + </Slider> + </div> +); + +export default SliderWidget; diff --git a/front/odiparpack/app/components/Widget/TableWidget.js b/front/odiparpack/app/components/Widget/TableWidget.js new file mode 100644 index 0000000..47bb7ba --- /dev/null +++ b/front/odiparpack/app/components/Widget/TableWidget.js @@ -0,0 +1,124 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import messageStyles from 'ba-styles/Messages.scss'; +import progressStyles from 'ba-styles/Progress.scss'; +import avatarApi from 'ba-api/avatars'; +import { + Typography, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Chip, + LinearProgress, + Avatar, + Icon, +} from '@material-ui/core'; +import PapperBlock from '../PapperBlock/PapperBlock'; +import styles from './widget-jss'; + + +let id = 0; +function createData(name, avatar, title, type, taskNumber, taskTitle, progress, status) { + id += 1; + return { + id, + name, + avatar, + title, + type, + taskNumber, + taskTitle, + progress, + status, + }; +} + +const data = [ + createData('John Doe', avatarApi[6], 'Front End Developer', 'bug_report', 2214, 'Vivamus sit amet interdum elit', 30, 'Error'), + createData('Jim Doe', avatarApi[8], 'System Analyst', 'flag', 2455, 'Nam sollicitudin dignissim nunc', 70, 'Success'), + createData('Jane Doe', avatarApi[2], 'Back End Developer', 'whatshot', 3450, 'Quisque ut metus sit amet augue rutrum', 50, 'Warning'), + createData('Jack Doe', avatarApi[9], 'CTO', 'settings', 4905, 'Cras convallis lacus orci', 85, 'Info'), + createData('Jessica Doe', avatarApi[5], 'Project Manager', 'book', 4118, 'Aenean sit amet magna vel magna', 33, 'Default'), +]; + +function TableWidget(props) { + const { classes } = props; + const getStatus = status => { + switch (status) { + case 'Error': return messageStyles.bgError; + case 'Warning': return messageStyles.bgWarning; + case 'Info': return messageStyles.bgInfo; + case 'Success': return messageStyles.bgSuccess; + default: return messageStyles.bgDefault; + } + }; + const getProgress = status => { + switch (status) { + case 'Error': return progressStyles.bgError; + case 'Warning': return progressStyles.bgWarning; + case 'Info': return progressStyles.bgInfo; + case 'Success': return progressStyles.bgSuccess; + default: return progressStyles.bgDefault; + } + }; + const getType = type => { + switch (type) { + case 'bug_report': return classes.red; + case 'flag': return classes.indigo; + case 'whatshot': return classes.orange; + case 'settings': return classes.lime; + default: return classes.purple; + } + }; + return ( + <PapperBlock noMargin title="Tracking Table" whiteBg desc="Monitoring Your Team progress. Tracking task, current progress, and task status here."> + <div className={classes.root}> + <Table className={classNames(classes.table)}> + <TableHead> + <TableRow> + <TableCell>Name</TableCell> + <TableCell>Task</TableCell> + </TableRow> + </TableHead> + <TableBody> + {data.map(n => ([ + <TableRow key={n.id}> + <TableCell> + <div className={classes.user}> + <Avatar alt={n.name} src={n.avatar} className={classes.avatar} /> + <div> + <Typography variant="subtitle1">{n.name}</Typography> + <Typography>{n.title}</Typography> + </div> + </div> + </TableCell> + <TableCell> + <div className={classes.taskStatus}> + <Icon className={classNames(classes.taskIcon, getType(n.type))}>{n.type}</Icon> + <a href="#"> + # + {n.taskNumber} + </a> + <Chip label={n.status} className={classNames(classes.chip, getStatus(n.status))} /> + </div> + <LinearProgress variant="determinate" className={getProgress(n.status)} value={n.progress} /> + </TableCell> + </TableRow> + ]) + )} + </TableBody> + </Table> + </div> + </PapperBlock> + ); +} + +TableWidget.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(TableWidget); diff --git a/front/odiparpack/app/components/Widget/TaskWidget.js b/front/odiparpack/app/components/Widget/TaskWidget.js new file mode 100644 index 0000000..4136544 --- /dev/null +++ b/front/odiparpack/app/components/Widget/TaskWidget.js @@ -0,0 +1,83 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import CommentIcon from '@material-ui/icons/Comment'; +import { + List, + ListItem, + ListItemSecondaryAction, + ListItemText, + Checkbox, + IconButton, +} from '@material-ui/core'; +import PapperBlock from '../PapperBlock/PapperBlock'; +import styles from './widget-jss'; + + +class TaskWidget extends React.Component { + state = { + checked: [0], + }; + + handleToggle = value => () => { + const { checked } = this.state; + const currentIndex = checked.indexOf(value); + const newChecked = [...checked]; + + if (currentIndex === -1) { + newChecked.push(value); + } else { + newChecked.splice(currentIndex, 1); + } + + this.setState({ + checked: newChecked, + }); + }; + + render() { + const { classes } = this.props; + return ( + <PapperBlock title="My Task" whiteBg colorMode desc="All Your to do list. Just check it whenever You done." className={classes.root}> + <List className={classes.taskList}> + {[0, 1, 2, 3, 4].map(value => ( + <Fragment key={value}> + <ListItem + key={value} + role={undefined} + dense + button + onClick={this.handleToggle(value)} + className={ + classNames( + classes.listItem, + this.state.checked.indexOf(value) !== -1 ? classes.done : '' + ) + } + > + <Checkbox + checked={this.state.checked.indexOf(value) !== -1} + tabIndex={-1} + disableRipple + /> + <ListItemText primary={`Task item ${value + 1}`} secondary={`Task description ${value + 1}`} /> + <ListItemSecondaryAction> + <IconButton aria-label="Comments"> + <CommentIcon /> + </IconButton> + </ListItemSecondaryAction> + </ListItem> + </Fragment> + ))} + </List> + </PapperBlock> + ); + } +} + +TaskWidget.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(TaskWidget); diff --git a/front/odiparpack/app/components/Widget/widget-jss.js b/front/odiparpack/app/components/Widget/widget-jss.js new file mode 100644 index 0000000..d553eaa --- /dev/null +++ b/front/odiparpack/app/components/Widget/widget-jss.js @@ -0,0 +1,294 @@ +import colorfull from 'ba-api/colorfull'; + +const styles = theme => ({ + rootCounter: { + flexGrow: 1, + padding: theme.spacing(1.5), + [theme.breakpoints.up('lg')]: { + padding: `${theme.spacing(1.5)}px ${theme.spacing(1)}px`, + }, + [theme.breakpoints.down('xs')]: { + margin: `0 ${theme.spacing(1) * -1.5}px`, + } + }, + rootCounterFull: { + flexGrow: 1, + }, + divider: { + margin: `${theme.spacing(3)}px 0` + }, + dividerBig: { + margin: `${theme.spacing(2)}px 0` + }, + centerItem: {}, + secondaryWrap: { + background: theme.palette.grey[100], + padding: 20, + borderRadius: 4, + justifyContent: 'space-around', + '& > $centerItem': { + position: 'relative', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + }, + '& li': { + marginBottom: 30 + }, + '& $chip': { + top: 70, + position: 'absolute', + fontSize: 11, + fontWeight: 400, + } + }, + bigResume: { + marginBottom: 20, + justifyContent: 'space-between', + display: 'flex', + [theme.breakpoints.down('sm')]: { + height: 160, + display: 'block', + }, + '& li': { + paddingRight: 20, + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-start', + [theme.breakpoints.down('sm')]: { + width: '50%', + float: 'left' + }, + }, + }, + avatar: { + width: 50, + height: 50, + marginRight: 10, + '& svg': { + fontSize: 32 + } + }, + pinkAvatar: { + margin: 10, + color: '#fff', + backgroundColor: colorfull[0], + }, + purpleAvatar: { + margin: 10, + color: '#fff', + backgroundColor: colorfull[1], + }, + blueAvatar: { + margin: 10, + color: '#fff', + backgroundColor: colorfull[2], + }, + tealAvatar: { + margin: 10, + color: '#fff', + backgroundColor: colorfull[3], + }, + pinkProgress: { + color: colorfull[0], + '& div': { + backgroundColor: colorfull[0], + } + }, + greenProgress: { + color: colorfull[5], + '& div': { + backgroundColor: colorfull[5], + } + }, + orangeProgress: { + color: colorfull[4], + '& div': { + backgroundColor: colorfull[4], + } + }, + purpleProgress: { + color: colorfull[1], + '& div': { + backgroundColor: colorfull[1], + } + }, + blueProgress: { + color: colorfull[2], + '& div': { + backgroundColor: colorfull[2], + } + }, + root: { + width: '100%', + marginTop: theme.spacing(3), + overflowX: 'auto', + }, + chip: { + margin: '8px 0 8px auto', + color: '#FFF' + }, + table: { + minWidth: 500, + '& td': { + padding: 10, + } + }, + user: { + display: 'flex', + }, + textCenter: { + textAlign: 'center' + }, + red: {}, + orange: {}, + indigo: {}, + purple: {}, + lime: {}, + taskIcon: { + display: 'block', + textAlign: 'center', + margin: '0 10px', + '&$red': { + color: colorfull[0], + }, + '&$orange': { + color: colorfull[2], + }, + '&$purple': { + color: colorfull[1], + }, + '&$lime': { + color: colorfull[3], + }, + '&$indigo': { + color: colorfull[4], + } + }, + done: {}, + listItem: { + padding: 5, + background: theme.palette.common.white, + boxShadow: theme.shadows[3], + '&:hover': { + backgroundColor: theme.palette.grey[200] + }, + '&$done': { + textDecoration: 'line-through' + } + }, + title: {}, + subtitle: {}, + styledPaper: { + backgroundColor: theme.palette.secondary.main, + padding: 20, + '& $title, & $subtitle': { + color: theme.palette.common.white + } + }, + progressWidget: { + marginTop: 20, + background: theme.palette.secondary.dark, + '& div': { + background: theme.palette.primary.light, + } + }, + chipProgress: { + marginTop: 20, + background: theme.palette.primary.light, + color: theme.palette.secondary.main, + '& div': { + background: colorfull[4], + color: theme.palette.common.white + } + }, + taskStatus: { + display: 'flex', + alignItems: 'center', + '& a': { + textDecoration: 'none', + color: theme.palette.primary.main + } + }, + counterIcon: { + color: theme.palette.common.white, + opacity: 0.7, + fontSize: 84 + }, + progressCircle: { + margin: 20 + }, + itemCarousel: { + textAlign: 'center', + '& img': { + margin: '10px auto' + } + }, + albumRoot: { + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'space-around', + overflow: 'hidden', + backgroundColor: theme.palette.background.paper, + }, + gridList: { + height: 'auto', + [theme.breakpoints.up('sm')]: { + width: 500, + }, + }, + icon: { + color: 'rgba(255, 255, 255, 0.54)', + }, + img: { + maxWidth: 'none' + }, + mapWrap: { + position: 'relative' + }, + address: { + [theme.breakpoints.up('md')]: { + top: '50%', + right: 60, + position: 'absolute', + transform: 'translate(0, -50%)' + }, + }, + carouselItem: { + margin: '0 5px', + boxShadow: theme.shadows[3], + borderRadius: 4, + overflow: 'hidden', + height: 250, + padding: '60px 20px', + position: 'relative' + }, + iconBg: { + color: theme.palette.common.white, + opacity: 0.25, + position: 'absolute', + bottom: 10, + right: 10, + fontSize: 96 + }, + carouselTitle: { + color: theme.palette.common.white, + display: 'flex', + flexDirection: 'column', + fontWeight: 500, + fontSize: 20 + }, + carouselDesc: { + color: theme.palette.common.white + }, + chartWrap: { + overflow: 'auto', + }, + chartFluid: { + width: '100%', + minWidth: 400, + height: 300, + } +}); + +export default styles; diff --git a/front/odiparpack/app/components/index.js b/front/odiparpack/app/components/index.js new file mode 100644 index 0000000..9d7bdb3 --- /dev/null +++ b/front/odiparpack/app/components/index.js @@ -0,0 +1,87 @@ +export Header from './Header/Header'; +export Sidebar from './Sidebar/Sidebar'; +export BreadCrumb from './BreadCrumb/BreadCrumb'; +export SourceReader from './SourceReader/SourceReader'; +export PapperBlock from './PapperBlock/PapperBlock'; +// Dashboard and Widget +export CounterWidget from './Counter/CounterWidget'; +export SliderWidget from './Widget/SliderWidget'; +export CounterGroupWidget from './Widget/CounterGroupWidget'; +export CounterIconsWidget from './Widget/CounterIconsWidget'; +export BigChartWidget from './Widget/BigChartWidget'; +export TableWidget from './Widget/TableWidget'; +export TaskWidget from './Widget/TaskWidget'; +export ProfileWidget from './Widget/ProfileWidget'; +export ProgressWidget from './Widget/ProgressWidget'; +export AreaChartWidget from './Widget/AreaChartWidget'; +export CarouselWidget from './Widget/CarouselWidget'; +export AlbumWidget from './Widget/AlbumWidget'; +export MapWidget from './Widget/MapWidget'; +// Table Components +export TreeTable from './Tables/TreeTable'; +export CrudTable from './Tables/CrudTable'; +export CrudTableForm from './Tables/CrudTableForm'; +export AdvTable from './Tables/AdvTable'; +export EmptyData from './Tables/EmptyData'; +// Form +export Notification from './Notification/Notification'; +export MaterialDropZone from './Forms/MaterialDropZone'; +export LoginForm from './Forms/LoginForm'; +export RegisterForm from './Forms/RegisterForm'; +export ResetForm from './Forms/ResetForm'; +export LockForm from './Forms/LockForm'; +// UI +export LimitedBadges from './Badges/LimitedBadges'; +export Quote from './Quote/Quote'; +export Pagination from './Pagination/Pagination'; +export ImageLightbox from './ImageLightbox/ImageLightbox'; +export Rating from './Rating/Rating'; +// Social Media +export Cover from './SocialMedia/Cover'; +export Timeline from './SocialMedia/Timeline'; +export SideSection from './SocialMedia/SideSection'; +export WritePost from './SocialMedia/WritePost'; +// Profile +export About from './Profile/About'; +export Albums from './Profile/Albums'; +export Connection from './Profile/Connection'; +export Favorites from './Profile/Favorites'; +// Card +export ProfileCard from './CardPaper/ProfileCard'; +export GeneralCard from './CardPaper/GeneralCard'; +export NewsCard from './CardPaper/NewsCard'; +export PlayerCard from './CardPaper/PlayerCard'; +export PostCard from './CardPaper/PostCard'; +export ProductCard from './CardPaper/ProductCard'; +export VideoCard from './CardPaper/VideoCard'; +export IdentityCard from './CardPaper/IdentityCard'; +// Search +export SearchProduct from './Search/SearchProduct'; +// Gallery +export ProductGallery from './Gallery/ProductGallery'; +export PhotoGallery from './Gallery/PhotoGallery'; +// Panel +export FloatingPanel from './Panel/FloatingPanel'; +export Cart from './Cart/Cart'; +// Contact +export AddContact from './Contact/AddContact'; +export ContactList from './Contact/ContactList'; +export ContactHeader from './Contact/ContactHeader'; +export ContactDetail from './Contact/ContactDetail'; +// Chat +export ChatHeader from './Chat/ChatHeader'; +export ChatRoom from './Chat/ChatRoom'; +// Email +export EmailHeader from './Email/EmailHeader'; +export EmailSidebar from './Email/EmailSidebar'; +export EmailList from './Email/EmailList'; +export ComposeEmail from './Email/ComposeEmail'; +// Calendar +export EventCalendar from './Calendar/EventCalendar'; +export DetailEvent from './Calendar/DetailEvent'; +export AddEvent from './Calendar/AddEvent'; +export AddEventForm from './Calendar/AddEventForm'; +// Error +export ErrorWrap from './Error/ErrorWrap'; +// Template Settings +export TemplateSettings from './TemplateSettings'; diff --git a/front/odiparpack/app/config/codePreview.js b/front/odiparpack/app/config/codePreview.js new file mode 100644 index 0000000..85f8a33 --- /dev/null +++ b/front/odiparpack/app/config/codePreview.js @@ -0,0 +1,3 @@ +export default { + enable: true +}; diff --git a/front/odiparpack/app/containers/.DS_Store b/front/odiparpack/app/containers/.DS_Store Binary files differnew file mode 100644 index 0000000..71c8885 --- /dev/null +++ b/front/odiparpack/app/containers/.DS_Store diff --git a/front/odiparpack/app/containers/App/Application.js b/front/odiparpack/app/containers/App/Application.js new file mode 100644 index 0000000..0a3ffc2 --- /dev/null +++ b/front/odiparpack/app/containers/App/Application.js @@ -0,0 +1,131 @@ +import React from 'react'; +import { PropTypes } from 'prop-types'; +import { Switch, Route } from 'react-router-dom'; +import Dashboard from '../Templates/Dashboard'; +import { + DashboardV1, DashboardV2, + Parent, AppLayout, Responsive, Grid, + SimpleTable, AdvancedTable, TablePlayground, + TreeTable, CrudTable, + ReduxForm, DateTimePicker, CheckboxRadio, + Switches, Selectbox, Rating, + SliderRange, Buttons, Textbox, + Autocomplete, Upload, TextEditor, + Avatars, Accordion, Badges, + List, PopoverTooltip, Notification, + Typography, Tabs, Cards, + ImageGrid, Progress, DialogModal, + Steppers, Paginations, DrawerMenu, + Breadcrumbs, Icons, + SliderCarousel, Tags, Dividers, + LineCharts, BarCharts, AreaCharts, PieCharts, + RadarCharts, ScatterCharts, CompossedCharts, ResponsiveCharts, + Contact, Chat, Email, + Ecommerce, SocialMedia, Calendar, + Profile, BlankPage, + Photos, Error, Settings, + HelpSupport, MapMarker, MapDirection, SearchMap, + TrafficIndicator, StreetViewMap, NotFound +} from '../pageListAsync'; + +function Application(props) { + const { history } = props; + + return ( + <Dashboard history={history}> + <Switch> + <Route exact path="/app" component={DashboardV1} /> + <Route exact path="/app/dashboard-v2" component={DashboardV2} /> + { /* Layout */ } + <Route exact path="/app/layouts" component={Parent} /> + <Route path="/app/layouts/grid" component={Grid} /> + <Route path="/app/layouts/app-layout" component={AppLayout} /> + <Route path="/app/layouts/responsive" component={Responsive} /> + { /* Table */ } + <Route exact path="/app/tables" component={Parent} /> + <Route path="/app/tables/basic-table" component={SimpleTable} /> + <Route path="/app/tables/data-table" component={AdvancedTable} /> + <Route path="/app/tables/table-playground" component={TablePlayground} /> + <Route path="/app/tables/tree-table" component={TreeTable} /> + <Route path="/app/tables/crud-table" component={CrudTable} /> + { /* Form & Button */ } + <Route exact path="/app/forms" component={Parent} /> + <Route path="/app/forms/reduxform" component={ReduxForm} /> + <Route path="/app/forms/date-time-picker" component={DateTimePicker} /> + <Route path="/app/forms/checkbox-radio" component={CheckboxRadio} /> + <Route path="/app/forms/switches" component={Switches} /> + <Route path="/app/forms/selectbox" component={Selectbox} /> + <Route path="/app/forms/ratting" component={Rating} /> + <Route path="/app/forms/slider-range" component={SliderRange} /> + <Route path="/app/forms/buttons" component={Buttons} /> + <Route path="/app/forms/textfields" component={Textbox} /> + <Route path="/app/forms/autocomplete" component={Autocomplete} /> + <Route path="/app/forms/upload" component={Upload} /> + <Route path="/app/forms/wysiwyg-editor" component={TextEditor} /> + { /* Ui Components */} + <Route exact path="/app/ui" component={Parent} /> + <Route path="/app/ui/avatars" component={Avatars} /> + <Route path="/app/ui/accordion" component={Accordion} /> + <Route path="/app/ui/badges" component={Badges} /> + <Route path="/app/ui/list" component={List} /> + <Route path="/app/ui/popover-tooltip" component={PopoverTooltip} /> + <Route path="/app/ui/notification" component={Notification} /> + <Route path="/app/ui/typography" component={Typography} /> + <Route path="/app/ui/tabs" component={Tabs} /> + <Route path="/app/ui/card-papper" component={Cards} /> + <Route path="/app/ui/image-grid" component={ImageGrid} /> + <Route path="/app/ui/progress" component={Progress} /> + <Route path="/app/ui/dialog-modal" component={DialogModal} /> + <Route path="/app/ui/steppers" component={Steppers} /> + <Route path="/app/ui/paginations" component={Paginations} /> + <Route path="/app/ui/drawer-menu" component={DrawerMenu} /> + <Route path="/app/ui/breadcrumbs" component={Breadcrumbs} /> + <Route path="/app/ui/icons" component={Icons} /> + <Route path="/app/ui/slider-carousel" component={SliderCarousel} /> + <Route path="/app/ui/tags" component={Tags} /> + <Route path="/app/ui/dividers" component={Dividers} /> + { /* Chart */ } + <Route exact path="/app/charts" component={Parent} /> + <Route path="/app/charts/line-charts" component={LineCharts} /> + <Route path="/app/charts/bar-charts" component={BarCharts} /> + <Route path="/app/charts/area-charts" component={AreaCharts} /> + <Route path="/app/charts/pie-charts" component={PieCharts} /> + <Route path="/app/charts/radar-charts" component={RadarCharts} /> + <Route path="/app/charts/scatter-charts" component={ScatterCharts} /> + <Route path="/app/charts/compossed-chart" component={CompossedCharts} /> + <Route path="/app/charts/responsive-chart" component={ResponsiveCharts} /> + { /* Sample Apps */ } + <Route path="/app/pages/contact" component={Contact} /> + <Route path="/app/pages/chat" component={Chat} /> + <Route path="/app/pages/email" component={Email} /> + <Route path="/app/pages/social-media" component={SocialMedia} /> + <Route path="/app/pages/ecommerce" component={Ecommerce} /> + <Route path="/app/pages/calendar" component={Calendar} /> + { /* Pages */ } + <Route exact path="/app/pages" component={Parent} /> + <Route path="/app/pages/user-profile" component={Profile} /> + <Route path="/app/pages/blank-page" component={BlankPage} /> + <Route path="/app/pages/photo-gallery" component={Photos} /> + <Route path="/app/pages/not-found" component={NotFound} /> + <Route path="/app/pages/error" component={Error} /> + <Route path="/app/pages/settings" component={Settings} /> + <Route path="/app/pages/help-support" component={HelpSupport} /> + { /* Map */ } + <Route exact path="/app/maps" component={Parent} /> + <Route path="/app/maps/map-marker" component={MapMarker} /> + <Route path="/app/maps/map-direction" component={MapDirection} /> + <Route path="/app/maps/map-searchbox" component={SearchMap} /> + <Route path="/app/maps/map-traffic" component={TrafficIndicator} /> + <Route path="/app/maps/street-view" component={StreetViewMap} /> + { /* Default */ } + <Route component={NotFound} /> + </Switch> + </Dashboard> + ); +} + +Application.propTypes = { + history: PropTypes.object.isRequired, +}; + +export default Application; diff --git a/front/odiparpack/app/containers/App/Auth.js b/front/odiparpack/app/containers/App/Auth.js new file mode 100644 index 0000000..740f7f3 --- /dev/null +++ b/front/odiparpack/app/containers/App/Auth.js @@ -0,0 +1,28 @@ +import React from 'react'; +import { Switch, Route } from 'react-router-dom'; +import Outer from '../Templates/Outer'; +import { + Login, + Register, + ResetPassword, + NotFound, + Maintenance, + LockScreen +} from '../pageListAsync'; + +function Auth() { + return ( + <Outer> + <Switch> + <Route path="/login" component={Login} /> + <Route path="/register" component={Register} /> + <Route path="/reset-password" component={ResetPassword} /> + <Route path="/maintenance" component={Maintenance} /> + <Route path="/lock-screen" component={LockScreen} /> + <Route component={NotFound} /> + </Switch> + </Outer> + ); +} + +export default Auth; diff --git a/front/odiparpack/app/containers/App/ThemeWrapper.js b/front/odiparpack/app/containers/App/ThemeWrapper.js new file mode 100644 index 0000000..f39945f --- /dev/null +++ b/front/odiparpack/app/containers/App/ThemeWrapper.js @@ -0,0 +1,111 @@ +import React, { useState, useEffect } from 'react'; +import { PropTypes } from 'prop-types'; +import { connect } from 'react-redux'; +import Loading from 'react-loading-bar'; +import { bindActionCreators } from 'redux'; +import { + withStyles, + createMuiTheme, + MuiThemeProvider +} from '@material-ui/core/styles'; +import 'ba-styles/vendors/react-loading-bar/index.css'; +import { changeThemeAction } from 'ba-actions/UiActions'; +import themePallete from 'ba-api/themePalette'; +import TemplateSettings from 'ba-components/TemplateSettings'; +//import { Button, Icon } from '@material-ui/core'; +import styles from '../Templates/appStyles-jss'; +import { esES } from '@material-ui/core/locale'; + +function ThemeWrapper(props) { + const { + classes, + children, + palette, + color, + changeTheme + } = props; + + const [pageLoaded, setPageLoaded] = useState(true); + const [open, setOpen] = useState(false); + const [newPalette, setNewPalette] = useState(undefined); + const [theme, setTheme] = useState( + createMuiTheme(themePallete(color),esES) + ); + + useEffect(() => { + setNewPalette(palette); + setPageLoaded(true); + setTimeout(() => { + setPageLoaded(false); + }, 500); + return () => { + setPageLoaded(true); + }; + }, []); + + const handleOpenPallete = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + const handleChangeTheme = event => { + setTheme(createMuiTheme(themePallete(event.target.value),esES)); + changeTheme(event.target.value); + }; + + return ( + <MuiThemeProvider theme={theme}> + + <div className={classes.root}> + <Loading + show={pageLoaded} + color="rgba(255,255,255,.9)" + showSpinner={false} + /> + {/* <Button onClick={handleOpenPallete} className={classes.btnPicker}> + <span className={classes.btn}> + <Icon className={classes.icon}>palette</Icon> + Theme + </span> + </Button> */} + <TemplateSettings + open={open} + palette={newPalette} + changeTheme={handleChangeTheme} + selectedValue={color} + close={handleClose} + /> + {children} + </div> + </MuiThemeProvider> + ); +} + +ThemeWrapper.propTypes = { + classes: PropTypes.object.isRequired, + children: PropTypes.node.isRequired, + color: PropTypes.string.isRequired, + changeTheme: PropTypes.func.isRequired, + palette: PropTypes.object.isRequired, +}; + +const reducer = 'ui'; +const mapStateToProps = state => ({ + ...state, + color: state.getIn([reducer, 'theme']), + palette: state.getIn([reducer, 'palette']), +}); + +const dispatchToProps = dispatch => ({ + changeTheme: bindActionCreators(changeThemeAction, dispatch), +}); + +const ThemeWrapperMapped = connect( + mapStateToProps, + dispatchToProps +)(ThemeWrapper); + +export default withStyles(styles)(ThemeWrapperMapped); diff --git a/front/odiparpack/app/containers/App/index.js b/front/odiparpack/app/containers/App/index.js new file mode 100644 index 0000000..a7313fe --- /dev/null +++ b/front/odiparpack/app/containers/App/index.js @@ -0,0 +1,23 @@ +import React from 'react'; +import { Switch, Route } from 'react-router-dom'; +import NotFound from 'containers/Pages/Standalone/NotFoundDedicated'; +import Auth from './Auth'; +import Application from './Application'; +import LoginDedicated from '../Pages/Standalone/LoginDedicated'; +import ThemeWrapper from './ThemeWrapper'; +window.__MUI_USE_NEXT_TYPOGRAPHY_VARIANTS__ = true; + +function App() { + return ( + <ThemeWrapper> + <Switch> + <Route path="/" exact component={LoginDedicated} /> + <Route path="/app" component={Application} /> + <Route component={Auth} /> + <Route component={NotFound} /> + </Switch> + </ThemeWrapper> + ); +} + +export default App; diff --git a/front/odiparpack/app/containers/Charts/AreaCharts.js b/front/odiparpack/app/containers/Charts/AreaCharts.js new file mode 100644 index 0000000..0468c22 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/AreaCharts.js @@ -0,0 +1,63 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { + AreaSimple, + AreaStacked, + AreaPercent, + AreaNegativePositive, + AreaResponsive +} from './demos'; + +class AreaCharts extends React.Component { + render() { + const title = brand.name + ' - Chart'; + const description = brand.desc; + const docSrc = 'containers/Charts/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Simple Area Chart" overflowX desc=""> + <div> + <AreaSimple /> + <SourceReader componentName={docSrc + 'AreaSimple.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Stacked Area Chart" overflowX desc=""> + <div> + <AreaStacked /> + <SourceReader componentName={docSrc + 'AreaStacked.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Percent Area Chart" overflowX desc=""> + <div> + <AreaPercent /> + <SourceReader componentName={docSrc + 'AreaPercent.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Positive Negative Area Chart" overflowX desc=""> + <div> + <AreaNegativePositive /> + <SourceReader componentName={docSrc + 'AreaNegativePositive.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Responsive Area Chart" overflowX desc=""> + <div> + <AreaResponsive /> + <SourceReader componentName={docSrc + 'AreaResponsive.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default AreaCharts; diff --git a/front/odiparpack/app/containers/Charts/BarCharts.js b/front/odiparpack/app/containers/Charts/BarCharts.js new file mode 100644 index 0000000..4d24f78 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/BarCharts.js @@ -0,0 +1,77 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { + BarSimple, + BarStacked, + BarMix, + BarCustom, + BarPositiveNegative, + BarCustomLabel, + BarResponsive +} from './demos'; + +class BarCharts extends React.Component { + render() { + const title = brand.name + ' - Chart'; + const description = brand.desc; + const docSrc = 'containers/Charts/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Simple Bar Chart" desc="" overflowX> + <div> + <BarSimple /> + <SourceReader componentName={docSrc + 'BarSimple.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Stacked Bar Chart" desc="" overflowX> + <div> + <BarStacked /> + <SourceReader componentName={docSrc + 'BarStacked.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Simple Mixing Bar" desc="" overflowX> + <div> + <BarMix /> + <SourceReader componentName={docSrc + 'BarMix.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Custom Bar Shape" desc="" overflowX> + <div> + <BarCustom /> + <SourceReader componentName={docSrc + 'BarCustom.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Custom Label Bar Chart" desc="" overflowX> + <div> + <BarCustomLabel /> + <SourceReader componentName={docSrc + 'BarCustomLabel.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Positive Negative Bar Chart" desc="" overflowX> + <div> + <BarPositiveNegative /> + <SourceReader componentName={docSrc + 'BarPositiveNegative.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Responsive Bar Chart" desc="" overflowX> + <div> + <BarResponsive /> + <SourceReader componentName={docSrc + 'BarResponsive.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default BarCharts; diff --git a/front/odiparpack/app/containers/Charts/CompossedCharts.js b/front/odiparpack/app/containers/Charts/CompossedCharts.js new file mode 100644 index 0000000..08937c7 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/CompossedCharts.js @@ -0,0 +1,56 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { + CompossedLineBarArea, + CompossedSameData, + CompossedVertical, + CompossedResponsive +} from './demos'; + +class CompossedCharts extends React.Component { + render() { + const title = brand.name + ' - Chart'; + const description = brand.desc; + const docSrc = 'containers/Charts/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Compossed Line Bar & Area Chart" desc="" overflowX> + <div> + <CompossedLineBarArea /> + <SourceReader componentName={docSrc + 'CompossedLineBarArea.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Compossed Same Data Chart" desc="" overflowX> + <div> + <CompossedSameData /> + <SourceReader componentName={docSrc + 'CompossedSameData.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Compossed Vertical Chart" desc="" overflowX> + <div> + <CompossedVertical /> + <SourceReader componentName={docSrc + 'CompossedVertical.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Compossed Responsive Chart" desc="" overflowX> + <div> + <CompossedResponsive /> + <SourceReader componentName={docSrc + 'CompossedResponsive.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default CompossedCharts; diff --git a/front/odiparpack/app/containers/Charts/LineCharts.js b/front/odiparpack/app/containers/Charts/LineCharts.js new file mode 100644 index 0000000..9f34f91 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/LineCharts.js @@ -0,0 +1,70 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { + LineSimple, + LineVertical, + LineCustomDot, + LineCustomLabel, + LineResponsive +} from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class DateTime extends React.Component { + render() { + const title = brand.name + ' - Chart'; + const description = brand.desc; + const docSrc = 'containers/Charts/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Simple Line Chart" desc="" overflowX> + <div> + <LineSimple /> + <SourceReader componentName={docSrc + 'LineSimple.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Vertical Line Chart" desc="" overflowX> + <div> + <LineVertical /> + <SourceReader componentName={docSrc + 'LineVertical.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Custom Dot Line Chart" desc="" overflowX> + <div> + <LineCustomDot /> + <SourceReader componentName={docSrc + 'LineCustomDot.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Custom Label Line Chart" desc="" overflowX> + <div> + <LineCustomLabel /> + <SourceReader componentName={docSrc + 'LineCustomLabel.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Responsive Line Chart" desc="" overflowX> + <div> + <LineResponsive /> + <SourceReader componentName={docSrc + 'LineResponsive.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default withStyles(styles)(DateTime); diff --git a/front/odiparpack/app/containers/Charts/PieCharts.js b/front/odiparpack/app/containers/Charts/PieCharts.js new file mode 100644 index 0000000..b5609e2 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/PieCharts.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { + PieSimple, + PieCustomShape, + PieCustomLabel, +} from './demos'; + +class PieCharts extends React.Component { + render() { + const title = brand.name + ' - Chart'; + const description = brand.desc; + const docSrc = 'containers/Charts/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Simple Pie Chart" desc="" overflowX> + <div> + <PieSimple /> + <SourceReader componentName={docSrc + 'PieSimple.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Custom Shape Pie Chart" desc="" overflowX> + <div> + <PieCustomShape /> + <SourceReader componentName={docSrc + 'PieCustomShape.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Custom Label Pie Chart" desc="" overflowX> + <div> + <PieCustomLabel /> + <SourceReader componentName={docSrc + 'PieCustomLabel.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default PieCharts; diff --git a/front/odiparpack/app/containers/Charts/RadarCharts.js b/front/odiparpack/app/containers/Charts/RadarCharts.js new file mode 100644 index 0000000..23fff3e --- /dev/null +++ b/front/odiparpack/app/containers/Charts/RadarCharts.js @@ -0,0 +1,42 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { + RadarSimple, + DoubleRadar +} from './demos'; + +class RadarCharts extends React.Component { + render() { + const title = brand.name + ' - Chart'; + const description = brand.desc; + const docSrc = 'containers/Charts/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Simple Radar Chart" desc="" overflowX> + <div> + <RadarSimple /> + <SourceReader componentName={docSrc + 'RadarSimple.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Double Radar Chart" desc="" overflowX> + <div> + <DoubleRadar /> + <SourceReader componentName={docSrc + 'DoubleRadar.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default RadarCharts; diff --git a/front/odiparpack/app/containers/Charts/ResponsiveCharts.js b/front/odiparpack/app/containers/Charts/ResponsiveCharts.js new file mode 100644 index 0000000..ab8bb17 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/ResponsiveCharts.js @@ -0,0 +1,63 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { + AreaResponsive, + BarResponsive, + LineResponsive, + ScatterResponsive, + CompossedResponsive +} from './demos'; + +class ScatterCharts extends React.Component { + render() { + const title = brand.name + ' - Chart'; + const description = brand.desc; + const docSrc = 'containers/Charts/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Area Responsive Chart" desc="" overflowX> + <div> + <AreaResponsive /> + <SourceReader componentName={docSrc + 'AreaResponsive.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Bar Responsive Chart" desc="" overflowX> + <div> + <BarResponsive /> + <SourceReader componentName={docSrc + 'BarResponsive.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Line Responsive Chart" desc="" overflowX> + <div> + <LineResponsive /> + <SourceReader componentName={docSrc + 'LineResponsive.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Scatter Responsive Chart" desc="" overflowX> + <div> + <ScatterResponsive /> + <SourceReader componentName={docSrc + 'ScatterResponsive.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Compossed Responsive Chart" desc="" overflowX> + <div> + <CompossedResponsive /> + <SourceReader componentName={docSrc + 'CompossedResponsive.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default ScatterCharts; diff --git a/front/odiparpack/app/containers/Charts/ScatterCharts.js b/front/odiparpack/app/containers/Charts/ScatterCharts.js new file mode 100644 index 0000000..b680738 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/ScatterCharts.js @@ -0,0 +1,63 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { + ScatterSimple, + ScatterJoinLine, + ScatterMultiple, + ScatterCustom, + ScatterResponsive +} from './demos'; + +class ScatterCharts extends React.Component { + render() { + const title = brand.name + ' - Chart'; + const description = brand.desc; + const docSrc = 'containers/Charts/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Scatter Simple Chart" desc="" overflowX> + <div> + <ScatterSimple /> + <SourceReader componentName={docSrc + 'ScatterSimple.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Scatter Join Line Chart" desc="" overflowX> + <div> + <ScatterJoinLine /> + <SourceReader componentName={docSrc + 'ScatterJoinLine.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Scatter Multiple Chart" desc="" overflowX> + <div> + <ScatterMultiple /> + <SourceReader componentName={docSrc + 'ScatterMultiple.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Scatter Custom Icon Chart" desc="" overflowX> + <div> + <ScatterCustom /> + <SourceReader componentName={docSrc + 'ScatterCustom.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Scatter Responsive Chart" desc="" overflowX> + <div> + <ScatterResponsive /> + <SourceReader componentName={docSrc + 'ScatterResponsive.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default ScatterCharts; diff --git a/front/odiparpack/app/containers/Charts/demos/AreaNegativePositive.js b/front/odiparpack/app/containers/Charts/demos/AreaNegativePositive.js new file mode 100644 index 0000000..dd5793a --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/AreaNegativePositive.js @@ -0,0 +1,62 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip +} from 'recharts'; +import { data3 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.redTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +const gradientOffset = () => { + const dataMax = Math.max(...data3.map((i) => i.uv)); + const dataMin = Math.min(...data3.map((i) => i.uv)); + + if (dataMax <= 0) { + return 0; + } else if (dataMin >= 0) { + return 1; + } + return dataMax / (dataMax - dataMin); +}; + +const off = gradientOffset(); + +function AreaNegativePositive() { + return ( + <AreaChart + width={800} + height={450} + data={data3} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <XAxis dataKey="name" /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip /> + <defs> + <linearGradient id="splitColor" x1="0" y1="0" x2="0" y2="1"> + <stop offset={off} stopColor={color.secondary} stopOpacity={1} /> + <stop offset={off} stopColor={color.primary} stopOpacity={1} /> + </linearGradient> + </defs> + <Area type="monotone" dataKey="uv" stroke="#bcbcbc" fillOpacity="0.8" fill="url(#splitColor)" /> + </AreaChart> + ); +} + +export default AreaNegativePositive; diff --git a/front/odiparpack/app/containers/Charts/demos/AreaPercent.js b/front/odiparpack/app/containers/Charts/demos/AreaPercent.js new file mode 100644 index 0000000..32902e1 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/AreaPercent.js @@ -0,0 +1,79 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip +} from 'recharts'; +import { red } from '@material-ui/core/colors'; +import { data1 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.greenNatureTheme); +const color = ({ + primary: theme.palette.primary.main, + primarydark: theme.palette.primary.dark, + secondary: theme.palette.secondary.main, + secondarydark: theme.palette.secondary.dark, + third: red[500], + thirddark: red[900], +}); + +const toPercent = (decimal, fixed = 0) => ( + `${(decimal * 100).toFixed(fixed)}%` +); + +const getPercent = (value, total) => { + const ratio = total > 0 ? value / total : 0; + return toPercent(ratio, 2); +}; + +const renderTooltipContent = (o) => { + const { payload, label } = o; + const total = payload.reduce((result, entry) => (result + entry.value), 0); + + return ( + <div className="customized-tooltip-content"> + <p className="total">{`${label} (Total: ${total})`}</p> + <ul className="list"> + { + payload.map((entry, index) => ( + <li key={`item-${index.toString()}`} style={{ color: entry.color }}> + {`${entry.name}: ${entry.value}(${getPercent(entry.value, total)})`} + </li> + )) + } + </ul> + </div> + ); +}; + +function AreaPercent() { + return ( + <AreaChart + width={800} + height={450} + data={data1} + stackOffset="expand" + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <XAxis dataKey="name" /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip content={renderTooltipContent} /> + <Area type="monotone" dataKey="uv" stackId="1" stroke={color.primarydark} fillOpacity={0.8} fill={color.primary} /> + <Area type="monotone" dataKey="pv" stackId="1" stroke={color.secondary} fillOpacity={0.8} fill={color.secondarydark} /> + <Area type="monotone" dataKey="amt" stackId="1" stroke={color.third} fillOpacity={0.8} fill={color.thirddark} /> + </AreaChart> + ); +} + +export default AreaPercent; diff --git a/front/odiparpack/app/containers/Charts/demos/AreaResponsive.js b/front/odiparpack/app/containers/Charts/demos/AreaResponsive.js new file mode 100644 index 0000000..a1bc4c0 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/AreaResponsive.js @@ -0,0 +1,67 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { createMuiTheme, withStyles } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer +} from 'recharts'; +import { green } from '@material-ui/core/colors'; +import { data1 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.purpleTheme); +const color = ({ + primary: theme.palette.primary.main, + primarydark: theme.palette.primary.dark, + secondary: theme.palette.secondary.main, + secondarydark: theme.palette.secondary.dark, + third: green[500], + thirddark: green[900], +}); + +const styles = { + chartFluid: { + width: '100%', + height: 450 + } +}; + +function AreaResponsive(props) { + const { classes } = props; + return ( + <div className={classes.chartFluid}> + <ResponsiveContainer> + <AreaChart + width={800} + height={450} + data={data1} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <XAxis dataKey="name" /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip /> + <Area type="monotone" dataKey="uv" stackId="1" stroke={color.primarydark} fillOpacity="0.8" fill={color.primary} /> + <Area type="monotone" dataKey="pv" stackId="1" stroke={color.secondary} fillOpacity="0.8" fill={color.secondarydark} /> + <Area type="monotone" dataKey="amt" stackId="1" stroke={color.third} fillOpacity="0.8" fill={color.thirddark} /> + </AreaChart> + </ResponsiveContainer> + </div> + ); +} + +AreaResponsive.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(AreaResponsive); diff --git a/front/odiparpack/app/containers/Charts/demos/AreaSimple.js b/front/odiparpack/app/containers/Charts/demos/AreaSimple.js new file mode 100644 index 0000000..590d19c --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/AreaSimple.js @@ -0,0 +1,42 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip +} from 'recharts'; +import { data2 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.greenTheme); +const color = ({ + main: theme.palette.primary.main, + dark: theme.palette.primary.dark, +}); + +function AreaSimple() { + return ( + <AreaChart + width={800} + height={450} + data={data2} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <XAxis dataKey="name" /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip /> + <Area type="monotone" dataKey="uv" stroke={color.dark} fillOpacity="0.8" fill={color.main} /> + </AreaChart> + ); +} + +export default AreaSimple; diff --git a/front/odiparpack/app/containers/Charts/demos/AreaStacked.js b/front/odiparpack/app/containers/Charts/demos/AreaStacked.js new file mode 100644 index 0000000..82abd40 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/AreaStacked.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip +} from 'recharts'; +import { green } from '@material-ui/core/colors'; +import { data1 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.blueTheme); +const color = ({ + primary: theme.palette.primary.main, + primarydark: theme.palette.primary.dark, + secondary: theme.palette.secondary.main, + secondarydark: theme.palette.secondary.dark, + third: green[500], + thirddark: green[900], +}); + +function AreaStacked() { + return ( + <AreaChart + width={800} + height={450} + data={data1} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <XAxis dataKey="name" /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip /> + <Area type="monotone" dataKey="uv" stackId="1" stroke={color.primarydark} fillOpacity="0.8" fill={color.primary} /> + <Area type="monotone" dataKey="pv" stackId="1" stroke={color.secondary} fillOpacity="0.8" fill={color.secondarydark} /> + <Area type="monotone" dataKey="amt" stackId="1" stroke={color.third} fillOpacity="0.8" fill={color.thirddark} /> + </AreaChart> + ); +} + +export default AreaStacked; diff --git a/front/odiparpack/app/containers/Charts/demos/BarCustom.js b/front/odiparpack/app/containers/Charts/demos/BarCustom.js new file mode 100644 index 0000000..d9a572d --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/BarCustom.js @@ -0,0 +1,80 @@ +import React from 'react'; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Cell +} from 'recharts'; +import PropTypes from 'prop-types'; +import { purple, red, pink, indigo, blue, cyan, teal } from '@material-ui/core/colors'; +import { data2 } from './sampleData'; + + +const colors = [red[500], pink[500], purple[500], indigo[500], blue[500], cyan[500], teal[500]]; + +const getPath = (x, y, width, height) => ( + `M${x},${y + height} + C${x + (width / 3)},${y + height} ${x + (width / 2)},${y + (height / 3)} ${x + (width / 2)}, ${y} + C${x + (width / 2)},${y + (height / 3)} ${x + (2 * (width / 3))},${y + height} ${x + width}, ${y + height} + Z` +); + +const TriangleBar = props => { + const { + fill, + x, + y, + width, + height + } = props; + return <path d={getPath(x, y, width, height)} stroke="none" fillOpacity="0.8" fill={fill} />; +}; + +TriangleBar.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + fill: PropTypes.string, + width: PropTypes.number, + height: PropTypes.number, +}; + +TriangleBar.defaultProps = { + x: 0, + y: 0, + fill: '#9f9f9f', + width: 0, + height: 0, +}; + +function BarCustom() { + return ( + <BarChart + width={800} + height={450} + data={data2} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <XAxis dataKey="name" /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip /> + <Bar dataKey="female" fill="#8884d8" shape={<TriangleBar />} label={{ position: 'top' }}> + { + data2.map((entry, index) => ( + <Cell key={`cell-${index.toString()}`} fill={colors[index % 20]} /> + )) + } + </Bar> + </BarChart> + ); +} + +export default BarCustom; diff --git a/front/odiparpack/app/containers/Charts/demos/BarCustomLabel.js b/front/odiparpack/app/containers/Charts/demos/BarCustomLabel.js new file mode 100644 index 0000000..79968d5 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/BarCustomLabel.js @@ -0,0 +1,84 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + LabelList +} from 'recharts'; +import { data1 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.purpleTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +const renderCustomizedLabel = props => { + const { + x, + y, + width, + value, + } = props; + const radius = 10; + + return ( + <g> + <circle cx={x + (width / 2)} cy={y - radius} r={radius} fillOpacity="0.8" fill="#689F38" /> + <text x={x + (width / 2)} y={y - radius} fill="#fff" textAnchor="middle" dominantBaseline="middle"> + {value.split(' ')[1]} + </text> + </g> + ); +}; + +renderCustomizedLabel.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + width: PropTypes.number, + value: PropTypes.number, +}; + +renderCustomizedLabel.defaultProps = { + x: 0, + y: 0, + width: 0, + value: 0, +}; + +function BarCustomLabel() { + return ( + <BarChart + width={800} + height={450} + data={data1} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <XAxis dataKey="name" /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip /> + <Legend /> + <Bar dataKey="pv" fill={color.primary}> + <LabelList dataKey="name" content={renderCustomizedLabel} /> + </Bar> + <Bar dataKey="uv" fill={color.secondary}> + <LabelList dataKey="name" content={renderCustomizedLabel} /> + </Bar> + </BarChart> + ); +} + +export default BarCustomLabel; diff --git a/front/odiparpack/app/containers/Charts/demos/BarMix.js b/front/odiparpack/app/containers/Charts/demos/BarMix.js new file mode 100644 index 0000000..e2e0e4e --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/BarMix.js @@ -0,0 +1,48 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend +} from 'recharts'; +import { green } from '@material-ui/core/colors'; +import { data2 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.blueTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, + third: green[500] +}); + +function BarMix() { + return ( + <BarChart + width={800} + height={450} + data={data2} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <XAxis dataKey="name" /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip /> + <Legend /> + <Bar dataKey="female" stackId="a" fillOpacity="0.8" fill={color.primary} /> + <Bar dataKey="male" stackId="a" fillOpacity="0.8" fill={color.secondary} /> + <Bar dataKey="uv" fill={color.third} /> + </BarChart> + ); +} + +export default BarMix; diff --git a/front/odiparpack/app/containers/Charts/demos/BarPositiveNegative.js b/front/odiparpack/app/containers/Charts/demos/BarPositiveNegative.js new file mode 100644 index 0000000..c3d6fcf --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/BarPositiveNegative.js @@ -0,0 +1,47 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ReferenceLine +} from 'recharts'; +import { data3 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.greenTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +function BarPositiveNegative() { + return ( + <BarChart + width={800} + height={450} + data={data3} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <XAxis dataKey="name" /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip /> + <Legend /> + <ReferenceLine y={0} stroke="#000" /> + <Bar dataKey="pv" fillOpacity="0.8" fill={color.secondary} /> + <Bar dataKey="uv" fillOpacity="0.8" fill={color.primary} /> + </BarChart> + ); +} + +export default BarPositiveNegative; diff --git a/front/odiparpack/app/containers/Charts/demos/BarResponsive.js b/front/odiparpack/app/containers/Charts/demos/BarResponsive.js new file mode 100644 index 0000000..a2d3a48 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/BarResponsive.js @@ -0,0 +1,64 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { createMuiTheme, withStyles } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer +} from 'recharts'; +import { data1 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.greenNatureTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +const styles = { + chartFluid: { + width: '100%', + height: 450 + } +}; + +function BarResponsive(props) { + const { classes } = props; + return ( + <div className={classes.chartFluid}> + <ResponsiveContainer> + <BarChart + width={800} + height={450} + data={data1} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <XAxis dataKey="name" /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip /> + <Legend /> + <Bar dataKey="pv" fill={color.primary} /> + <Bar dataKey="uv" fill={color.secondary} /> + </BarChart> + </ResponsiveContainer> + </div> + ); +} + +BarResponsive.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(BarResponsive); + diff --git a/front/odiparpack/app/containers/Charts/demos/BarSimple.js b/front/odiparpack/app/containers/Charts/demos/BarSimple.js new file mode 100644 index 0000000..eb06ee6 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/BarSimple.js @@ -0,0 +1,45 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend +} from 'recharts'; +import { data1 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.redTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +function BarSimple() { + return ( + <BarChart + width={800} + height={450} + data={data1} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <XAxis dataKey="name" /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip /> + <Legend /> + <Bar dataKey="pv" fillOpacity="0.8" fill={color.primary} /> + <Bar dataKey="uv" fillOpacity="0.8" fill={color.secondary} /> + </BarChart> + ); +} + +export default BarSimple; diff --git a/front/odiparpack/app/containers/Charts/demos/BarStacked.js b/front/odiparpack/app/containers/Charts/demos/BarStacked.js new file mode 100644 index 0000000..e5dfb0a --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/BarStacked.js @@ -0,0 +1,45 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend +} from 'recharts'; +import { data1 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.orangeTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +function BarStacked() { + return ( + <BarChart + width={800} + height={450} + data={data1} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <XAxis dataKey="name" /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip /> + <Legend /> + <Bar dataKey="pv" stackId="a" fillOpacity="0.8" fill={color.secondary} /> + <Bar dataKey="uv" stackId="a" fillOpacity="0.8" fill={color.primary} /> + </BarChart> + ); +} + +export default BarStacked; diff --git a/front/odiparpack/app/containers/Charts/demos/CompossedLineBarArea.js b/front/odiparpack/app/containers/Charts/demos/CompossedLineBarArea.js new file mode 100644 index 0000000..9063062 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/CompossedLineBarArea.js @@ -0,0 +1,51 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + ComposedChart, + Line, + Area, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend +} from 'recharts'; +import { green } from '@material-ui/core/colors'; +import { data1 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.orangeTheme); +const color = ({ + main: theme.palette.primary.main, + maindark: theme.palette.primary.dark, + secondary: theme.palette.secondary.main, + third: green[500], +}); + +function CompossedLineBarArea() { + return ( + <ComposedChart + width={800} + height={450} + data={data1} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <CartesianGrid stroke="#f5f5f5" /> + <XAxis dataKey="name" /> + <YAxis /> + <Tooltip /> + <Legend /> + <Area type="monotone" dataKey="amt" fillOpacity="0.8" fill={color.main} stroke={color.maindark} /> + <Bar dataKey="pv" barSize={20} fillOpacity="0.8" fill={color.secondary} /> + <Line type="monotone" dataKey="uv" stroke={color.third} /> + </ComposedChart> + ); +} + +export default CompossedLineBarArea; diff --git a/front/odiparpack/app/containers/Charts/demos/CompossedResponsive.js b/front/odiparpack/app/containers/Charts/demos/CompossedResponsive.js new file mode 100644 index 0000000..ff06fb4 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/CompossedResponsive.js @@ -0,0 +1,69 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { createMuiTheme, withStyles } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + ComposedChart, + Line, + Area, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer +} from 'recharts'; +import { green } from '@material-ui/core/colors'; +import { data1 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.magentaTheme); +const color = ({ + main: theme.palette.primary.main, + maindark: theme.palette.primary.dark, + secondary: theme.palette.secondary.main, + third: green[500], +}); + +const styles = { + chartFluid: { + width: '100%', + height: 450 + } +}; + +function CompossedResponsive(props) { + const { classes } = props; + return ( + <div className={classes.chartFluid}> + <ResponsiveContainer> + <ComposedChart + width={800} + height={450} + data={data1} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <CartesianGrid stroke="#f5f5f5" /> + <XAxis dataKey="name" /> + <YAxis /> + <Tooltip /> + <Legend /> + <Area type="monotone" dataKey="amt" fillOpacity="0.8" fill={color.main} stroke={color.maindark} /> + <Bar dataKey="pv" barSize={20} fillOpacity="0.8" fill={color.secondary} /> + <Line type="monotone" dataKey="uv" stroke={color.third} /> + </ComposedChart> + </ResponsiveContainer> + </div> + ); +} + +CompossedResponsive.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CompossedResponsive); diff --git a/front/odiparpack/app/containers/Charts/demos/CompossedSameData.js b/front/odiparpack/app/containers/Charts/demos/CompossedSameData.js new file mode 100644 index 0000000..ca81de1 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/CompossedSameData.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + ComposedChart, + Line, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend +} from 'recharts'; +import { data1 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.blueTheme); +const color = ({ + main: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +function CompossedSameData() { + return ( + <ComposedChart + width={800} + height={450} + data={data1} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <CartesianGrid stroke="#f5f5f5" /> + <XAxis dataKey="name" /> + <YAxis /> + <Tooltip /> + <Legend /> + <Bar dataKey="uv" barSize={20} fillOpacity="0.8" fill={color.main} /> + <Line type="monotone" dataKey="uv" stroke={color.secondary} /> + </ComposedChart> + ); +} + +export default CompossedSameData; diff --git a/front/odiparpack/app/containers/Charts/demos/CompossedVertical.js b/front/odiparpack/app/containers/Charts/demos/CompossedVertical.js new file mode 100644 index 0000000..8e6d382 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/CompossedVertical.js @@ -0,0 +1,52 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + ComposedChart, + Line, + Area, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend +} from 'recharts'; +import { green } from '@material-ui/core/colors'; +import { data1 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.blueTheme); +const color = ({ + main: theme.palette.primary.main, + maindark: theme.palette.primary.dark, + secondary: theme.palette.secondary.main, + third: green[500], +}); + +function CompossedVertical() { + return ( + <ComposedChart + width={800} + height={450} + layout="vertical" + data={data1} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <CartesianGrid stroke="#f5f5f5" /> + <XAxis type="number" /> + <YAxis dataKey="name" type="category" /> + <Tooltip /> + <Legend /> + <Area dataKey="amt" fillOpacity="0.8" fill={color.main} stroke={color.maindark} /> + <Bar dataKey="pv" barSize={20} fillOpacity="0.8" fill={color.secondary} /> + <Line dataKey="uv" stroke={color.third} /> + </ComposedChart> + ); +} + +export default CompossedVertical; diff --git a/front/odiparpack/app/containers/Charts/demos/DoubleRadar.js b/front/odiparpack/app/containers/Charts/demos/DoubleRadar.js new file mode 100644 index 0000000..ba6774c --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/DoubleRadar.js @@ -0,0 +1,35 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + Radar, + RadarChart, + PolarGrid, + PolarAngleAxis, + PolarRadiusAxis, + Legend +} from 'recharts'; +import { data7 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.redTheme); +const color = ({ + main: theme.palette.primary.main, + maindark: theme.palette.primary.dark, + secondary: theme.palette.secondary.main, + secondarydark: theme.palette.secondary.dark, +}); + +function DoubleRadar() { + return ( + <RadarChart cx={300} cy={250} outerRadius={150} width={600} height={500} data={data7}> + <PolarGrid /> + <PolarAngleAxis dataKey="subject" /> + <PolarRadiusAxis angle={30} domain={[0, 150]} /> + <Radar name="Mike" dataKey="A" stroke={color.maindark} fill={color.main} fillOpacity={0.6} /> + <Radar name="Lily" dataKey="B" stroke={color.secondarydark} fill={color.secondary} fillOpacity={0.3} /> + <Legend /> + </RadarChart> + ); +} + +export default DoubleRadar; diff --git a/front/odiparpack/app/containers/Charts/demos/LineCustomDot.js b/front/odiparpack/app/containers/Charts/demos/LineCustomDot.js new file mode 100644 index 0000000..0b36702 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/LineCustomDot.js @@ -0,0 +1,78 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend +} from 'recharts'; +import { data1 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.orangeTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +const CustomizedDot = props => { + const { + cx, + cy, + value + } = props; + if (value > 2500) { + return ( + <svg x={cx - 10} y={cy - 10} width={20} height={20} fillOpacity="0.8" fill={color.primary} viewBox="0 0 1024 1024"> + <path d="M512 1009.984c-274.912 0-497.76-222.848-497.76-497.76s222.848-497.76 497.76-497.76c274.912 0 497.76 222.848 497.76 497.76s-222.848 497.76-497.76 497.76zM340.768 295.936c-39.488 0-71.52 32.8-71.52 73.248s32.032 73.248 71.52 73.248c39.488 0 71.52-32.8 71.52-73.248s-32.032-73.248-71.52-73.248zM686.176 296.704c-39.488 0-71.52 32.8-71.52 73.248s32.032 73.248 71.52 73.248c39.488 0 71.52-32.8 71.52-73.248s-32.032-73.248-71.52-73.248zM772.928 555.392c-18.752-8.864-40.928-0.576-49.632 18.528-40.224 88.576-120.256 143.552-208.832 143.552-85.952 0-164.864-52.64-205.952-137.376-9.184-18.912-31.648-26.592-50.08-17.28-18.464 9.408-21.216 21.472-15.936 32.64 52.8 111.424 155.232 186.784 269.76 186.784 117.984 0 217.12-70.944 269.76-186.784 8.672-19.136 9.568-31.2-9.12-40.096z" /> + </svg> + ); + } + return ( + <svg x={cx - 10} y={cy - 10} width={20} height={20} fillOpacity="0.8" fill={color.secondary} viewBox="0 0 1024 1024"> + <path d="M517.12 53.248q95.232 0 179.2 36.352t145.92 98.304 98.304 145.92 36.352 179.2-36.352 179.2-98.304 145.92-145.92 98.304-179.2 36.352-179.2-36.352-145.92-98.304-98.304-145.92-36.352-179.2 36.352-179.2 98.304-145.92 145.92-98.304 179.2-36.352zM663.552 261.12q-15.36 0-28.16 6.656t-23.04 18.432-15.872 27.648-5.632 33.28q0 35.84 21.504 61.44t51.2 25.6 51.2-25.6 21.504-61.44q0-17.408-5.632-33.28t-15.872-27.648-23.04-18.432-28.16-6.656zM373.76 261.12q-29.696 0-50.688 25.088t-20.992 60.928 20.992 61.44 50.688 25.6 50.176-25.6 20.48-61.44-20.48-60.928-50.176-25.088zM520.192 602.112q-51.2 0-97.28 9.728t-82.944 27.648-62.464 41.472-35.84 51.2q-1.024 1.024-1.024 2.048-1.024 3.072-1.024 8.704t2.56 11.776 7.168 11.264 12.8 6.144q25.6-27.648 62.464-50.176 31.744-19.456 79.36-35.328t114.176-15.872q67.584 0 116.736 15.872t81.92 35.328q37.888 22.528 63.488 50.176 17.408-5.12 19.968-18.944t0.512-18.944-3.072-7.168-1.024-3.072q-26.624-55.296-100.352-88.576t-176.128-33.28z" /> + </svg> + ); +}; + +CustomizedDot.propTypes = { + cx: PropTypes.number, + cy: PropTypes.number, + value: PropTypes.number, +}; + +CustomizedDot.defaultProps = { + cx: 0, + cy: 0, + value: 0, +}; + +function LineCustomDot() { + return ( + <LineChart + width={800} + height={450} + data={data1} + 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={color.primary} dot={<CustomizedDot />} /> + <Line type="monotone" dataKey="uv" stroke={color.secondary} /> + </LineChart> + ); +} + +export default LineCustomDot; diff --git a/front/odiparpack/app/containers/Charts/demos/LineCustomLabel.js b/front/odiparpack/app/containers/Charts/demos/LineCustomLabel.js new file mode 100644 index 0000000..32f4288 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/LineCustomLabel.js @@ -0,0 +1,74 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend +} from 'recharts'; +import { data1 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.redTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +const CustomizedLabel = props => { + const { + x, + y, + stroke, + value + } = props; + return ( + <text x={x} y={y} dy={-4} fill={stroke} fillOpacity="0.8" fontSize={10} textAnchor="middle"> + { value } + </text> + ); +}; + +CustomizedLabel.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + value: PropTypes.number, + stroke: PropTypes.string, +}; + +CustomizedLabel.defaultProps = { + x: 0, + y: 0, + value: 0, + stroke: '#000' +}; + +function LineCustomLabel() { + return ( + <LineChart + width={800} + height={450} + data={data1} + 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={color.primary} label={<CustomizedLabel stroke={color.primary} />} /> + <Line type="monotone" dataKey="uv" stroke={color.secondary} label={<CustomizedLabel stroke={color.secondary} />} /> + </LineChart> + ); +} + +export default LineCustomLabel; diff --git a/front/odiparpack/app/containers/Charts/demos/LineResponsive.js b/front/odiparpack/app/containers/Charts/demos/LineResponsive.js new file mode 100644 index 0000000..b27223f --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/LineResponsive.js @@ -0,0 +1,56 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer +} from 'recharts'; +import { data1 } from './sampleData'; + +const styles = { + chartFluid: { + width: '100%', + height: 450 + } +}; + +function LineResponsive(props) { + const { classes } = props; + return ( + <div className={classes.chartFluid}> + <ResponsiveContainer> + <LineChart + width={800} + height={450} + data={data1} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <XAxis dataKey="name" /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip /> + <Legend /> + <Line type="monotone" dataKey="pv" stroke="#8884d8" activeDot={{ r: 8 }} /> + <Line type="monotone" dataKey="uv" stroke="#82ca9d" /> + </LineChart> + </ResponsiveContainer> + </div> + ); +} + +LineResponsive.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(LineResponsive); diff --git a/front/odiparpack/app/containers/Charts/demos/LineSimple.js b/front/odiparpack/app/containers/Charts/demos/LineSimple.js new file mode 100644 index 0000000..54c1be5 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/LineSimple.js @@ -0,0 +1,37 @@ +import React from 'react'; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend +} from 'recharts'; +import { data1 } from './sampleData'; + +function LineSimple() { + return ( + <LineChart + width={800} + height={450} + data={data1} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <XAxis dataKey="name" /> + <YAxis /> + <CartesianGrid strokeDasharray="3 3" /> + <Tooltip /> + <Legend /> + <Line type="monotone" dataKey="pv" stroke="#8884d8" activeDot={{ r: 8 }} /> + <Line type="monotone" dataKey="uv" stroke="#82ca9d" /> + </LineChart> + ); +} + +export default LineSimple; diff --git a/front/odiparpack/app/containers/Charts/demos/LineVertical.js b/front/odiparpack/app/containers/Charts/demos/LineVertical.js new file mode 100644 index 0000000..28ffc07 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/LineVertical.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend +} from 'recharts'; +import { data1 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.cyanTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +function LineVertical() { + return ( + <LineChart + width={800} + height={450} + data={data1} + layout="vertical" + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <CartesianGrid strokeDasharray="3 3" /> + <XAxis type="number" /> + <YAxis dataKey="name" type="category" /> + <Tooltip /> + <Legend /> + <Line type="monotone" dataKey="pv" stroke={color.primary} activeDot={{ r: 8 }} /> + <Line type="monotone" dataKey="uv" stroke={color.secondary} /> + </LineChart> + ); +} + +export default LineVertical; diff --git a/front/odiparpack/app/containers/Charts/demos/PieCustomLabel.js b/front/odiparpack/app/containers/Charts/demos/PieCustomLabel.js new file mode 100644 index 0000000..1787ef3 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/PieCustomLabel.js @@ -0,0 +1,83 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + PieChart, + Pie, + Cell +} from 'recharts'; + +import { purple, red, pink, indigo, blue, cyan, teal } from '@material-ui/core/colors'; +import { data6 } from './sampleData'; +const colors = [red[500], pink[500], purple[500], indigo[500], blue[500], cyan[500], teal[500]]; + +const RADIAN = Math.PI / 180; +const renderCustomizedLabel = ({ + cx, + cy, + midAngle, + innerRadius, + outerRadius, + percent, +}) => { + const radius = innerRadius + ((outerRadius - innerRadius) * 0.5); + const x = cx + (radius * Math.cos(-midAngle * RADIAN)); + const y = cy + (radius * Math.sin(-midAngle * RADIAN)); + + return ( + <text x={x} y={y} fill="white" textAnchor={x > cx ? 'start' : 'end'} dominantBaseline="central"> + {`${(percent * 100).toFixed(0)}%`} + </text> + ); +}; + +renderCustomizedLabel.propTypes = { + cx: PropTypes.number, + cy: PropTypes.number, + midAngle: PropTypes.number, + innerRadius: PropTypes.number, + outerRadius: PropTypes.number, + percent: PropTypes.number, +}; + +renderCustomizedLabel.defaultProps = { + cx: 0, + cy: 0, + midAngle: 0, + innerRadius: 0, + outerRadius: 0, + percent: 0, +}; + +class PieCustomLabel extends React.Component { + render() { + return ( + <PieChart + width={800} + height={450} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <Pie + dataKey="value" + data={data6} + cx={300} + cy={200} + labelLine={false} + label={renderCustomizedLabel} + outerRadius={160} + fill="#8884d8" + > + { + data6.map((entry, index) => <Cell fill={colors[index % colors.length]} key={index.toString()} />) + } + </Pie> + </PieChart> + ); + } +} + +export default PieCustomLabel; diff --git a/front/odiparpack/app/containers/Charts/demos/PieCustomShape.js b/front/odiparpack/app/containers/Charts/demos/PieCustomShape.js new file mode 100644 index 0000000..ae58dba --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/PieCustomShape.js @@ -0,0 +1,143 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + PieChart, + Pie, + Sector +} from 'recharts'; +import { data6 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.greenTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +const renderActiveShape = props => { + const RADIAN = Math.PI / 180; + const { + cx, + cy, + midAngle, + innerRadius, + outerRadius, + startAngle, + endAngle, + fill, + payload, + percent, + value + } = props; + const sin = Math.sin(-RADIAN * midAngle); + const cos = Math.cos(-RADIAN * midAngle); + const sx = cx + ((outerRadius + 10) * cos); + const sy = cy + ((outerRadius + 10) * sin); + const mx = cx + ((outerRadius + 30) * cos); + const my = cy + ((outerRadius + 30) * sin); + const ex = mx + ((cos >= 0 ? 1 : -1) * 22); + const ey = my; + const 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> + ); +}; + +renderActiveShape.propTypes = { + cx: PropTypes.number, + cy: PropTypes.number, + midAngle: PropTypes.number, + innerRadius: PropTypes.number, + outerRadius: PropTypes.number, + startAngle: PropTypes.number, + endAngle: PropTypes.number, + fill: PropTypes.string, + payload: PropTypes.string, + percent: PropTypes.number, + value: PropTypes.number, +}; + +renderActiveShape.defaultProps = { + cx: 0, + cy: 0, + midAngle: 0, + innerRadius: 0, + outerRadius: 0, + startAngle: 0, + endAngle: 0, + fill: '#eee', + payload: '', + percent: 0, + value: 0, +}; + +class PieCustomShape extends React.Component { + state = { + activeIndex: 0 + }; + + onPieEnter(evt) { + const index = data6.findIndex(p => p.name === evt.name); + this.setState({ + activeIndex: index, + }); + } + + render() { + return ( + <PieChart + width={800} + height={400} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <Pie + dataKey="value" + activeIndex={this.state.activeIndex} + activeShape={renderActiveShape} + data={data6} + cx={300} + cy={200} + innerRadius={60} + outerRadius={100} + fill={color.secondary} + fillOpacity="0.8" + onMouseEnter={(event) => this.onPieEnter(event)} + /> + </PieChart> + ); + } +} + +export default PieCustomShape; diff --git a/front/odiparpack/app/containers/Charts/demos/PieSimple.js b/front/odiparpack/app/containers/Charts/demos/PieSimple.js new file mode 100644 index 0000000..f0a4f6e --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/PieSimple.js @@ -0,0 +1,36 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + PieChart, + Pie, + Tooltip +} from 'recharts'; +import { data4, data5 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.purpleTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +function PieSimple() { + return ( + <PieChart + width={800} + height={450} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <Pie isAnimationActive={false} dataKey="value" data={data4} cx={200} cy={200} outerRadius={80} fill={color.primary} label /> + <Pie data={data5} cx={500} dataKey="value" cy={200} innerRadius={40} outerRadius={80} fill={color.secondary} /> + <Tooltip /> + </PieChart> + ); +} + +export default PieSimple; diff --git a/front/odiparpack/app/containers/Charts/demos/RadarSimple.js b/front/odiparpack/app/containers/Charts/demos/RadarSimple.js new file mode 100644 index 0000000..f0e86cd --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/RadarSimple.js @@ -0,0 +1,30 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + Radar, + RadarChart, + PolarGrid, + PolarAngleAxis, + PolarRadiusAxis +} from 'recharts'; +import { data7 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.purpleTheme); +const color = ({ + main: theme.palette.primary.main, + dark: theme.palette.primary.dark, +}); + +function RadarSimple() { + return ( + <RadarChart cx={300} cy={250} outerRadius={150} width={600} height={500} data={data7}> + <PolarGrid /> + <PolarAngleAxis dataKey="subject" /> + <PolarRadiusAxis /> + <Radar name="Mike" dataKey="A" stroke={color.dark} fill={color.main} fillOpacity={0.8} /> + </RadarChart> + ); +} + +export default RadarSimple; diff --git a/front/odiparpack/app/containers/Charts/demos/ScatterCustom.js b/front/odiparpack/app/containers/Charts/demos/ScatterCustom.js new file mode 100644 index 0000000..9e61b27 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/ScatterCustom.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + ScatterChart, + Scatter, + XAxis, + YAxis, + ZAxis, + CartesianGrid, + Tooltip, + Legend +} from 'recharts'; +import { data8, data9 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.orangeTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +function ScatterCustom() { + return ( + <ScatterChart + width={800} + height={450} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <CartesianGrid /> + <XAxis dataKey="x" type="number" name="stature" unit="cm" /> + <YAxis dataKey="y" type="number" name="weight" unit="kg" /> + <ZAxis range={[100]} /> + <Tooltip cursor={{ strokeDasharray: '3 3' }} /> + <Legend /> + <Scatter name="A school" data={data8} fillOpacity="0.8" fill={color.primary} shape="star" /> + <Scatter name="B school" data={data9} fillOpacity="0.8" fill={color.secondary} shape="triangle" /> + </ScatterChart> + ); +} + +export default ScatterCustom; diff --git a/front/odiparpack/app/containers/Charts/demos/ScatterJoinLine.js b/front/odiparpack/app/containers/Charts/demos/ScatterJoinLine.js new file mode 100644 index 0000000..74c238e --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/ScatterJoinLine.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + ScatterChart, + Scatter, + XAxis, + YAxis, + ZAxis, + CartesianGrid, + Tooltip, + Legend +} from 'recharts'; +import { data8, data9 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.blueTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +function ScatterJoinLine() { + return ( + <ScatterChart + width={800} + height={450} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <CartesianGrid /> + <XAxis dataKey="x" type="number" name="stature" unit="cm" /> + <YAxis dataKey="y" type="number" name="weight" unit="kg" /> + <ZAxis range={[100]} /> + <Tooltip cursor={{ strokeDasharray: '3 3' }} /> + <Legend /> + <Scatter name="A school" data={data8} fillOpacity="0.8" fill={color.primary} line shape="cross" /> + <Scatter name="B school" data={data9} fillOpacity="0.8" fill={color.secondary} line shape="diamond" /> + </ScatterChart> + ); +} + +export default ScatterJoinLine; diff --git a/front/odiparpack/app/containers/Charts/demos/ScatterMultiple.js b/front/odiparpack/app/containers/Charts/demos/ScatterMultiple.js new file mode 100644 index 0000000..a870692 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/ScatterMultiple.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + ScatterChart, + Scatter, + XAxis, + YAxis, + ZAxis, + CartesianGrid, + Tooltip, + Legend +} from 'recharts'; +import { data8, data9 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.greenTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +function ScatterMultiple() { + return ( + <ScatterChart + width={800} + height={450} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <CartesianGrid /> + <XAxis dataKey="x" type="number" name="stature" unit="cm" /> + <YAxis dataKey="y" type="number" name="weight" unit="kg" /> + <ZAxis range={[100]} /> + <Tooltip cursor={{ strokeDasharray: '3 3' }} /> + <Legend /> + <Scatter name="A school" data={data8} fillOpacity="0.8" fill={color.primary} /> + <Scatter name="B school" data={data9} fillOpacity="0.8" fill={color.secondary} /> + </ScatterChart> + ); +} + +export default ScatterMultiple; diff --git a/front/odiparpack/app/containers/Charts/demos/ScatterResponsive.js b/front/odiparpack/app/containers/Charts/demos/ScatterResponsive.js new file mode 100644 index 0000000..791117d --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/ScatterResponsive.js @@ -0,0 +1,64 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { createMuiTheme, withStyles } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + ScatterChart, + Scatter, + XAxis, + YAxis, + ZAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer +} from 'recharts'; +import { data8, data9 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.skyBlueTheme); +const color = ({ + primary: theme.palette.primary.main, + secondary: theme.palette.secondary.main, +}); + +const styles = { + chartFluid: { + width: '100%', + height: 450 + } +}; + +function ScatterResponsive(props) { + const { classes } = props; + return ( + <div className={classes.chartFluid}> + <ResponsiveContainer width="100%" min-height="100%"> + <ScatterChart + width={800} + height={450} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <CartesianGrid /> + <XAxis dataKey="x" type="number" name="stature" unit="cm" /> + <YAxis dataKey="y" type="number" name="weight" unit="kg" /> + <ZAxis range={[100]} /> + <Tooltip cursor={{ strokeDasharray: '3 3' }} /> + <Legend /> + <Scatter name="A school" data={data8} fillOpacity="0.8" fill={color.primary} /> + <Scatter name="B school" data={data9} fillOpacity="0.8" fill={color.secondary} /> + </ScatterChart> + </ResponsiveContainer> + </div> + ); +} + +ScatterResponsive.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ScatterResponsive); diff --git a/front/odiparpack/app/containers/Charts/demos/ScatterSimple.js b/front/odiparpack/app/containers/Charts/demos/ScatterSimple.js new file mode 100644 index 0000000..4b8717a --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/ScatterSimple.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { createMuiTheme } from '@material-ui/core/styles'; +import ThemePallete from 'ba-api/themePalette'; +import { + ScatterChart, + Scatter, + XAxis, + YAxis, + CartesianGrid, + Tooltip, +} from 'recharts'; +import { data8 } from './sampleData'; + +const theme = createMuiTheme(ThemePallete.cyanTheme); +const color = ({ + primary: theme.palette.primary.main, +}); + +function ScatterSimple() { + return ( + <ScatterChart + width={800} + height={450} + margin={{ + top: 5, + right: 30, + left: 20, + bottom: 5 + }} + > + <CartesianGrid /> + <XAxis dataKey="x" type="number" name="stature" unit="cm" /> + <YAxis dataKey="y" type="number" name="weight" unit="kg" /> + <Scatter name="A school" data={data8} fill={color.primary} /> + <Tooltip cursor={{ strokeDasharray: '3 3' }} /> + </ScatterChart> + ); +} + +export default ScatterSimple; diff --git a/front/odiparpack/app/containers/Charts/demos/index.js b/front/odiparpack/app/containers/Charts/demos/index.js new file mode 100644 index 0000000..da6ac01 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/index.js @@ -0,0 +1,31 @@ +export LineSimple from './LineSimple'; +export LineVertical from './LineVertical'; +export LineCustomDot from './LineCustomDot'; +export LineCustomLabel from './LineCustomLabel'; +export LineResponsive from './LineResponsive'; +export BarSimple from './BarSimple'; +export BarMix from './BarMix'; +export BarStacked from './BarStacked'; +export BarCustom from './BarCustom'; +export BarPositiveNegative from './BarPositiveNegative'; +export BarCustomLabel from './BarCustomLabel'; +export BarResponsive from './BarResponsive'; +export AreaSimple from './AreaSimple'; +export AreaStacked from './AreaStacked'; +export AreaPercent from './AreaPercent'; +export AreaNegativePositive from './AreaNegativePositive'; +export AreaResponsive from './AreaResponsive'; +export PieSimple from './PieSimple'; +export PieCustomShape from './PieCustomShape'; +export PieCustomLabel from './PieCustomLabel'; +export RadarSimple from './RadarSimple'; +export DoubleRadar from './DoubleRadar'; +export ScatterSimple from './ScatterSimple'; +export ScatterJoinLine from './ScatterJoinLine'; +export ScatterMultiple from './ScatterMultiple'; +export ScatterCustom from './ScatterCustom'; +export ScatterResponsive from './ScatterResponsive'; +export CompossedLineBarArea from './CompossedLineBarArea'; +export CompossedSameData from './CompossedSameData'; +export CompossedVertical from './CompossedVertical'; +export CompossedResponsive from './CompossedResponsive'; diff --git a/front/odiparpack/app/containers/Charts/demos/sampleData.js b/front/odiparpack/app/containers/Charts/demos/sampleData.js new file mode 100644 index 0000000..4c6e524 --- /dev/null +++ b/front/odiparpack/app/containers/Charts/demos/sampleData.js @@ -0,0 +1,262 @@ +export const data1 = [ + { + 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 + }, +]; + +export const data2 = [ + { + name: 'Page A', + uv: 4000, + female: 2400, + male: 2400 + }, { + name: 'Page B', + uv: 3000, + female: 1398, + male: 2210 + }, { + name: 'Page C', + uv: 2000, + female: 9800, + male: 2290 + }, { + name: 'Page D', + uv: 2780, + female: 3908, + male: 2000 + }, { + name: 'Page E', + uv: 1890, + female: 4800, + male: 2181 + }, { + name: 'Page F', + uv: 2390, + female: 3800, + male: 2500 + }, { + name: 'Page G', + uv: 3490, + female: 4300, + male: 2100 + }, +]; + +export const data3 = [ + { + 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 + }, +]; + +export const data4 = [ + { + name: 'Group A', + value: 400 + }, { + name: 'Group B', + value: 300 + }, + { + name: 'Group C', + value: 300 + }, { + name: 'Group D', + value: 200 + }, + { + name: 'Group E', + value: 278 + }, { + name: 'Group F', + value: 189 + } +]; + +export const data5 = [ + { + name: 'Group A', + value: 2400 + }, { + name: 'Group B', + value: 4567 + }, + { + name: 'Group C', + value: 1398 + }, { + name: 'Group D', + value: 9800 + }, + { + name: 'Group E', + value: 3908 + }, { + name: 'Group F', + value: 4800 + } +]; + +export const data6 = [ + { + name: 'Group A', + value: 400 + }, { + name: 'Group B', + value: 300 + }, + { + name: 'Group C', + value: 300 + }, { + name: 'Group D', + value: 200 + } +]; + +export const data7 = [ + { + subject: 'Math', + A: 120, + B: 110, + fullMark: 150 + }, { + subject: 'Chinese', + A: 98, + B: 130, + fullMark: 150 + }, { + subject: 'English', + A: 86, + B: 130, + fullMark: 150 + }, { + subject: 'Geography', + A: 99, + B: 100, + fullMark: 150 + }, { + subject: 'Physics', + A: 85, + B: 90, + fullMark: 150 + }, { + subject: 'History', + A: 65, + B: 85, + fullMark: 150 + }, +]; + +export const data8 = [ + { + x: 10, + y: 30 + }, { + x: 30, + y: 200 + }, { + x: 45, + y: 100 + }, { + x: 50, + y: 400 + }, { + x: 70, + y: 150 + }, { + x: 100, + y: 250 + } +]; + +export const data9 = [ + { + x: 30, + y: 20 + }, { + x: 50, + y: 180 + }, { + x: 75, + y: 240 + }, { + x: 100, + y: 100 + }, { + x: 120, + y: 190 + } +]; diff --git a/front/odiparpack/app/containers/Dashboard/Dashboard.js b/front/odiparpack/app/containers/Dashboard/Dashboard.js new file mode 100644 index 0000000..94d3eb4 --- /dev/null +++ b/front/odiparpack/app/containers/Dashboard/Dashboard.js @@ -0,0 +1,99 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import brand from 'ba-api/brand'; +import { Helmet } from 'react-helmet'; +import { withStyles } from '@material-ui/core/styles'; +import imgApi from 'ba-api/images'; +import avatarApi from 'ba-api/avatars'; +import { + SliderWidget, + CounterGroupWidget, + BigChartWidget, + TableWidget, + TaskWidget, + ProfileCard, + ProfileWidget, + ProgressWidget, + GeneralCard, + Quote, + PlayerCard +} from 'ba-components'; +import { Grid, Divider } from '@material-ui/core'; +import styles from './dashboard-jss'; + + +class Dashboard extends PureComponent { + render() { + const title = brand.name + ' - Dashboard'; + const description = brand.desc; + const { classes } = this.props; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <Grid container spacing={3} className={classes.root}> + <Grid item md={6} sm={12} xs={12}> + <div className={classes.sliderWrap}> + <SliderWidget /> + </div> + </Grid> + <Grid item md={6} xs={12} className={classes.noPadding}> + <CounterGroupWidget /> + </Grid> + </Grid> + <Divider className={classes.dividerMini} /> + <Grid container spacing={3} className={classes.root}> + <Grid item xs={12}> + <BigChartWidget /> + </Grid> + </Grid> + <Grid container spacing={3} className={classes.root}> + <Grid item md={7} xs={12}> + <Divider className={classes.dividerMini} /> + <TableWidget /> + <Divider className={classes.divider} /> + <TaskWidget /> + <Divider className={classes.divider} /> + <PlayerCard + title="Live From Space" + artist="Mac Miller" + cover={imgApi[32]} + /> + </Grid> + <Grid item md={5}> + <Divider className={classes.dividerMini} /> + <ProfileCard + cover={imgApi[41]} + avatar={avatarApi[6]} + name="John Doe" + title="UX designer" + connection={10} + btnText="See Profile" + isVerified + /> + <Divider className={classes.divider} /> + <ProgressWidget /> + <Divider className={classes.divider} /> + <ProfileWidget /> + <Divider className={classes.divider} /> + <GeneralCard liked={1} shared={20} commented={15}> + <Quote align="left" content="Imagine all the people living life in peace. You may say I'm a dreamer, but I'm not the only one. I hope someday you'll join us, and the world will be as one." footnote="John Lennon" /> + </GeneralCard> + </Grid> + </Grid> + </div> + ); + } +} + +Dashboard.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Dashboard); diff --git a/front/odiparpack/app/containers/Dashboard/DashboardV2.js b/front/odiparpack/app/containers/Dashboard/DashboardV2.js new file mode 100644 index 0000000..027dad8 --- /dev/null +++ b/front/odiparpack/app/containers/Dashboard/DashboardV2.js @@ -0,0 +1,79 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import brand from 'ba-api/brand'; +import { Helmet } from 'react-helmet'; +import { withStyles } from '@material-ui/core/styles'; +import imgApi from 'ba-api/images'; +import avatarApi from 'ba-api/avatars'; +import { + CounterIconsWidget, + AreaChartWidget, + CarouselWidget, + PostCard, + AlbumWidget, + MapWidget +} from 'ba-components'; +import { Grid, Divider } from '@material-ui/core'; +import styles from './dashboard-jss'; + + +class DashboardV2 extends PureComponent { + render() { + const title = brand.name + ' - Dashboard Version 2'; + const description = brand.desc; + const { classes } = this.props; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <Grid container className={classes.root}> + <CounterIconsWidget /> + </Grid> + <Divider className={classes.divider} /> + <AreaChartWidget /> + <Divider className={classes.divider} /> + <Grid container spacing={3}> + <Grid item xs={12}> + <CarouselWidget /> + </Grid> + </Grid> + <Divider className={classes.divider} /> + <Grid container spacing={3} className={classes.root}> + <Grid item sm={6} xs={12}> + <PostCard + liked={1} + shared={20} + commented={15} + date="Sept, 25 2018" + content="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum." + image={imgApi[16]} + avatar={avatarApi[6]} + name="John Doe" + /> + </Grid> + <Grid item sm={6} xs={12}> + <AlbumWidget /> + </Grid> + </Grid> + <Divider className={classes.divider} /> + <Grid container spacing={3} className={classes.root}> + <Grid item xs={12}> + <MapWidget /> + </Grid> + </Grid> + </div> + ); + } +} + +DashboardV2.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(DashboardV2); diff --git a/front/odiparpack/app/containers/Dashboard/dashboard-jss.js b/front/odiparpack/app/containers/Dashboard/dashboard-jss.js new file mode 100644 index 0000000..adb9bc0 --- /dev/null +++ b/front/odiparpack/app/containers/Dashboard/dashboard-jss.js @@ -0,0 +1,30 @@ +const styles = theme => ({ + root: { + flexGrow: 1, + }, + divider: { + display: 'block', + margin: `${theme.spacing(2)}px 0`, + background: 'none' + }, + sliderWrap: { + position: 'relative', + display: 'block', + boxShadow: theme.shadows[1], + width: '100%', + borderRadius: 4 + }, + dividerMini: { + margin: `${theme.spacing(1.5)}px 0`, + background: 'none' + }, + noPadding: { + paddingTop: '0 !important', + paddingBottom: '0 !important', + [theme.breakpoints.up('sm')]: { + padding: '0 !important' + } + } +}); + +export default styles; diff --git a/front/odiparpack/app/containers/Forms/Autocomplete.js b/front/odiparpack/app/containers/Forms/Autocomplete.js new file mode 100644 index 0000000..23c6a13 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/Autocomplete.js @@ -0,0 +1,91 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Grid } from '@material-ui/core'; +import { + AutoSuggest, + TagSuggestions, + SelectSuggestions, + SelectSuggestionTags, + HighlightSuggest +} from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class Textbox extends React.Component { + render() { + const { classes } = this.props; + const title = brand.name + ' - Form'; + const description = brand.desc; + const docSrc = 'containers/Forms/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <div className={classes.root}> + <Grid container spacing={3}> + <Grid item md={12}> + <PapperBlock title="Highlight Suggestion" desc="In the following example, we demonstrate how to use react-autosuggest. It's also using autosuggest-highlight for the highlighting logic."> + <div> + <HighlightSuggest /> + <SourceReader componentName={docSrc + 'HighlightSuggest.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6}> + <PapperBlock title="Auto Suggestion" desc="The autocomplete is a normal text input enhanced by a panel of suggested options."> + <div> + <AutoSuggest /> + <SourceReader componentName={docSrc + 'AutoSuggest.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6}> + <PapperBlock title="Tag Suggestion" desc="In the following example, we demonstrate with tag input and how to use downshift. It mean press Shift + down to show autocomplete"> + <div> + <TagSuggestions /> + <SourceReader componentName={docSrc + 'TagSuggestions.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6}> + <PapperBlock title="Select Suggestion" desc="In the following example, we demonstrate how to use react-select."> + <div> + <SelectSuggestions /> + <SourceReader componentName={docSrc + 'SelectSuggestions.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6}> + <PapperBlock title="Select Tag Suggestion" desc="In the following example, we demonstrate how to combine tag input and react-select."> + <div> + <SelectSuggestionTags /> + <SourceReader componentName={docSrc + 'SelectSuggestionTags.js'} /> + </div> + </PapperBlock> + </Grid> + </Grid> + </div> + </div> + ); + } +} + +Textbox.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Textbox); diff --git a/front/odiparpack/app/containers/Forms/Buttons.js b/front/odiparpack/app/containers/Forms/Buttons.js new file mode 100644 index 0000000..0935ac2 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/Buttons.js @@ -0,0 +1,58 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { StandardButtons, FloatingButtons, CustomButtons, ComplexButtons } from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class Buttons extends React.Component { + render() { + const title = brand.name + ' - Form'; + const description = brand.desc; + const docSrc = 'containers/Forms/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Standard Buttons" desc="Buttons communicate the action that will occur when the user touches them."> + <div> + <StandardButtons /> + <SourceReader componentName={docSrc + 'StandardButtons.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Floating Action Buttons" desc="A floating action button represents the primary action in an application. Shaped like a circled icon floating above the UI, it has an ink wash upon focus and lifts upon selection."> + <div> + <FloatingButtons /> + <SourceReader componentName={docSrc + 'FloatingButtons.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Customized Buttons" desc=""> + <div> + <CustomButtons /> + <SourceReader componentName={docSrc + 'CustomButtons.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Complex Buttons" desc="The Flat Buttons, Raised Buttons, Floating Action Buttons and Icon Buttons are built on top of the same component: the ButtonBase. You can take advantage of this lower level component to build custom interactions."> + <div> + <ComplexButtons /> + <SourceReader componentName={docSrc + 'ComplexButtons.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default withStyles(styles)(Buttons); diff --git a/front/odiparpack/app/containers/Forms/CheckboxRadio.js b/front/odiparpack/app/containers/Forms/CheckboxRadio.js new file mode 100644 index 0000000..9833964 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/CheckboxRadio.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Checkboxes, RadioButton } from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class DateTime extends React.Component { + render() { + const title = brand.name + ' - Form'; + const description = brand.desc; + const docSrc = 'containers/Forms/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Checkbox" desc="Checkboxes allow the user to select multiple options from a set. If you have multiple options appearing in a list, you can preserve space by using checkboxes instead of on/off switches. If you have a single option, avoid using a checkbox and use an on/off switch instead."> + <div> + <Checkboxes /> + <SourceReader componentName={docSrc + 'Checkboxes.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Radio Button" desc="Radio buttons allow the user to select one option from a set. Use radio buttons for exclusive selection if you think that the user needs to see all available options side-by-side; otherwise, consider a dropdown, which uses less space than displaying all options."> + <div> + <RadioButton /> + <SourceReader componentName={docSrc + 'RadioButton.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default withStyles(styles)(DateTime); diff --git a/front/odiparpack/app/containers/Forms/DateTimePicker.js b/front/odiparpack/app/containers/Forms/DateTimePicker.js new file mode 100644 index 0000000..b079d2e --- /dev/null +++ b/front/odiparpack/app/containers/Forms/DateTimePicker.js @@ -0,0 +1,52 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { DateInput, TimeInput, DateTimeInput } from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class DateTime extends React.Component { + render() { + const title = brand.name + ' - Form'; + const description = brand.desc; + const docSrc = 'containers/Forms/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Date Picker" desc="Date pickers use a dialog window to select a single date. The selected day is indicated by a filled circle. The current day is indicated by a different color and type weight."> + <div> + <DateInput /> + <SourceReader componentName={docSrc + 'DateInput.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Time Picker" desc="Time pickers use a dialog to select a single time (in the hours:minutes format). The selected time is indicated by the filled circle at the end of the clock hand."> + <div> + <TimeInput /> + <SourceReader componentName={docSrc + 'TimeInput.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Date & Time Picker" desc="Its a combination of date & time picker and allows that uses the modal to select both date and time with one control."> + <div> + <DateTimeInput /> + <SourceReader componentName={docSrc + 'DateTimeInput.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default withStyles(styles)(DateTime); diff --git a/front/odiparpack/app/containers/Forms/Rating.js b/front/odiparpack/app/containers/Forms/Rating.js new file mode 100644 index 0000000..5d28bca --- /dev/null +++ b/front/odiparpack/app/containers/Forms/Rating.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { RatingNormal, RatingCustom } from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class DateTime extends React.Component { + render() { + const title = brand.name + ' - Form'; + const description = brand.desc; + const docSrc = 'containers/Forms/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Ratting" desc="Basic React component for star (or any other icon based) rating elements"> + <div> + <RatingNormal /> + <SourceReader componentName={docSrc + 'RatingNormal.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Ratting Custom" desc=""> + <div> + <RatingCustom /> + <SourceReader componentName={docSrc + 'RatingNormal.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default withStyles(styles)(DateTime); diff --git a/front/odiparpack/app/containers/Forms/ReduxForm.js b/front/odiparpack/app/containers/Forms/ReduxForm.js new file mode 100644 index 0000000..d8ef3a7 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/ReduxForm.js @@ -0,0 +1,51 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { ReduxFormDemo } from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class ReduxForm extends React.Component { + state = { + valueForm: [] + } + + showResult(values) { + setTimeout(() => { + this.setState({ valueForm: values }); + window.alert(`You submitted:\n\n${this.state.valueForm}`); + }, 500); // simulate server latency + } + + render() { + const title = brand.name + ' - Form'; + const description = brand.desc; + const docSrc = 'containers/Forms/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Redux Form" desc="This is a simple demonstration of how to connect all the standard material-ui form elements to redux-form."> + <div> + <ReduxFormDemo onSubmit={(values) => this.showResult(values)} /> + <SourceReader componentName={docSrc + 'ReduxFormDemo.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default withStyles(styles)(ReduxForm); diff --git a/front/odiparpack/app/containers/Forms/Selectbox.js b/front/odiparpack/app/containers/Forms/Selectbox.js new file mode 100644 index 0000000..4f0c090 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/Selectbox.js @@ -0,0 +1,58 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { SimpleSelectbox, NativeSelectbox, MultipleSelectbox, ControlledSelectbox } from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class Selectbox extends React.Component { + render() { + const title = brand.name + ' - Form'; + const description = brand.desc; + const docSrc = 'containers/Forms/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Simple Selectbox" desc="Menus are positioned over their emitting elements such that the currently selected menu item appears on top of the emitting element."> + <div> + <SimpleSelectbox /> + <SourceReader componentName={docSrc + 'SimpleSelectbox.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Native Selectbox" desc="As the user experience can be improved on mobile using the native select of the platform, we allow such pattern."> + <div> + <NativeSelectbox /> + <SourceReader componentName={docSrc + 'NativeSelectbox.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Multiple Selectbox" desc="The Select component can handle multiple selections. It's enabled with the multiple property."> + <div> + <MultipleSelectbox /> + <SourceReader componentName={docSrc + 'MultipleSelectbox.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Controlled open Select" desc=""> + <div> + <ControlledSelectbox /> + <SourceReader componentName={docSrc + 'ContorlledSelectbox.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default withStyles(styles)(Selectbox); diff --git a/front/odiparpack/app/containers/Forms/SliderRange.js b/front/odiparpack/app/containers/Forms/SliderRange.js new file mode 100644 index 0000000..779e251 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/SliderRange.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { SliderInput, RangeInput } from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class DateTime extends React.Component { + render() { + const title = brand.name + ' - Form'; + const description = brand.desc; + const docSrc = 'containers/Forms/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Slider Input" desc="React component for inputting numeric values within a range (range slider)"> + <div> + <SliderInput /> + <SourceReader componentName={docSrc + 'SliderInput.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Range Input" desc="InputRange is a React component allowing users to input numeric values within a specific range. It can accept a single value, or a range of values (min/max). By default, basic styles are applied, but can be overridden depending on your design requirements."> + <div> + <RangeInput /> + <SourceReader componentName={docSrc + 'RangeInput.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default withStyles(styles)(DateTime); diff --git a/front/odiparpack/app/containers/Forms/Switches.js b/front/odiparpack/app/containers/Forms/Switches.js new file mode 100644 index 0000000..988e0a3 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/Switches.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { SwitchesInput } from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class DateTime extends React.Component { + render() { + const title = brand.name + ' - Form'; + const description = brand.desc; + const docSrc = 'containers/Forms/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Switches" desc="On/off switches toggle the state of a single settings option. The option that the switch controls, as well as the state it’s in, should be made clear from the corresponding inline label."> + <div> + <SwitchesInput /> + <SourceReader componentName={docSrc + 'SwitchesInput.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default withStyles(styles)(DateTime); diff --git a/front/odiparpack/app/containers/Forms/TextEditor.js b/front/odiparpack/app/containers/Forms/TextEditor.js new file mode 100644 index 0000000..84a1a82 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/TextEditor.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Wysiwyg } from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class DateTime extends React.Component { + render() { + const title = brand.name + ' - Form'; + const description = brand.desc; + const docSrc = 'containers/Forms/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Text Editor" desc="A Wysiwyg Built on ReactJS and DraftJS. Editor can be simply imported and used as a React Component. Make sure to also include react-draft-wysiwyg.css from node_modules."> + <div> + <Wysiwyg /> + <SourceReader componentName={docSrc + 'Wysiwyg.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default withStyles(styles)(DateTime); diff --git a/front/odiparpack/app/containers/Forms/Textbox.js b/front/odiparpack/app/containers/Forms/Textbox.js new file mode 100644 index 0000000..3eff0ba --- /dev/null +++ b/front/odiparpack/app/containers/Forms/Textbox.js @@ -0,0 +1,58 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { TextFields, TextFieldsLayout, InputAdornments, FormattedInputs } from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class Textbox extends React.Component { + render() { + const title = brand.name + ' - Form'; + const description = brand.desc; + const docSrc = 'containers/Forms/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Textfield Components" desc="Text fields allow users to input text and usually appear in forms. Users may enter text, numbers, or mixed-format types of input."> + <div> + <TextFields /> + <SourceReader componentName={docSrc + 'TextFields.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Textfield Layout and Design" desc=""> + <div> + <TextFieldsLayout /> + <SourceReader componentName={docSrc + 'TextFieldsLayout.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Input with Additonal Icon" desc="Input allows the provision of InputAdornment. These can be used to add a prefix, a suffix or an action to an input."> + <div> + <InputAdornments /> + <SourceReader componentName={docSrc + 'InputAdornments.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Formatted inputs" desc="We demonstrate how you could be using third-party libraries to format your input. In the following demo, we are using react-text-mask and react-number-format libraries. "> + <div> + <FormattedInputs /> + <SourceReader componentName={docSrc + 'FormattedInputs.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default withStyles(styles)(Textbox); diff --git a/front/odiparpack/app/containers/Forms/Upload.js b/front/odiparpack/app/containers/Forms/Upload.js new file mode 100644 index 0000000..99ef3bf --- /dev/null +++ b/front/odiparpack/app/containers/Forms/Upload.js @@ -0,0 +1,52 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { UploadInputAll, UploadInputImg, UploadInputBtn } from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class DateTime extends React.Component { + render() { + const title = brand.name + ' - Form'; + const description = brand.desc; + const docSrc = 'containers/Forms/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Upload with Drop Zone" desc="Simple HTML5-compliant drag'n'drop zone for files built with React Drop Zone. The component containing a file upload (dropzone) area, images and files preview and some snazzy File Allowed/Not Allowed effects."> + <div> + <UploadInputAll /> + <SourceReader componentName={docSrc + 'UploadInputAll.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Upload only Images" desc="This example allowing users to upload images only"> + <div> + <UploadInputImg /> + <SourceReader componentName={docSrc + 'UploadInputImg.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Upload Button" desc="Trigger upload file via button with ref attribute"> + <div> + <UploadInputBtn /> + <SourceReader componentName={docSrc + 'UploadInputBtn.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default withStyles(styles)(DateTime); diff --git a/front/odiparpack/app/containers/Forms/demos/AutoSuggest.js b/front/odiparpack/app/containers/Forms/demos/AutoSuggest.js new file mode 100644 index 0000000..6d90ed3 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/AutoSuggest.js @@ -0,0 +1,184 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Downshift from 'downshift'; +import { withStyles } from '@material-ui/core/styles'; +import { TextField, Paper, MenuItem } from '@material-ui/core'; + +const suggestions = [ + { label: 'Afghanistan' }, + { label: 'Aland Islands' }, + { label: 'Albania' }, + { label: 'Algeria' }, + { label: 'American Samoa' }, + { label: 'Andorra' }, + { label: 'Angola' }, + { label: 'Anguilla' }, + { label: 'Antarctica' }, + { label: 'Antigua and Barbuda' }, + { label: 'Argentina' }, + { label: 'Armenia' }, + { label: 'Aruba' }, + { label: 'Australia' }, + { label: 'Austria' }, + { label: 'Azerbaijan' }, + { label: 'Bahamas' }, + { label: 'Bahrain' }, + { label: 'Bangladesh' }, + { label: 'Barbados' }, + { label: 'Belarus' }, + { label: 'Belgium' }, + { label: 'Belize' }, + { label: 'Benin' }, + { label: 'Bermuda' }, + { label: 'Bhutan' }, + { label: 'Bolivia, Plurinational State of' }, + { label: 'Bonaire, Sint Eustatius and Saba' }, + { label: 'Bosnia and Herzegovina' }, + { label: 'Botswana' }, + { label: 'Bouvet Island' }, + { label: 'Brazil' }, + { label: 'British Indian Ocean Territory' }, + { label: 'Brunei Darussalam' }, +]; + +function renderInput(inputProps) { + const { + InputProps, + classes, + ref, + ...other + } = inputProps; + + return ( + <TextField + InputProps={{ + inputRef: ref, + classes: { + root: classes.inputRoot, + }, + ...InputProps, + }} + {...other} + /> + ); +} + +function renderSuggestion({ + suggestion, + index, + itemProps, + highlightedIndex, + selectedItem +}) { + const isHighlighted = highlightedIndex === index; + const isSelected = (selectedItem || '').indexOf(suggestion.label) > -1; + + return ( + <MenuItem + {...itemProps} + key={suggestion.label} + selected={isHighlighted} + component="div" + style={{ + fontWeight: isSelected ? 500 : 400, + }} + > + {suggestion.label} + </MenuItem> + ); +} + +renderSuggestion.propTypes = { + highlightedIndex: PropTypes.number.isRequired, + index: PropTypes.number.isRequired, + itemProps: PropTypes.object.isRequired, + selectedItem: PropTypes.string.isRequired, + suggestion: PropTypes.shape({ label: PropTypes.string }).isRequired, +}; + +function getSuggestions(inputValue) { + let count = 0; + + return suggestions.filter(suggestion => { + const keep = (!inputValue || suggestion.label.toLowerCase().indexOf(inputValue.toLowerCase()) !== -1) + && count < 5; + + if (keep) { + count += 1; + } + + return keep; + }); +} + +const styles = theme => ({ + root: { + flexGrow: 1, + height: 100, + }, + container: { + flexGrow: 1, + position: 'relative', + }, + paper: { + position: 'absolute', + zIndex: 1, + marginTop: theme.spacing(1), + left: 0, + right: 0, + }, + chip: { + margin: `${theme.spacing(0.5)}px ${theme.spacing(0.25)}px`, + }, + inputRoot: { + flexWrap: 'wrap', + }, +}); + +function AutoSuggest(props) { + const { classes } = props; + + return ( + <div className={classes.root}> + <Downshift> + {({ + getInputProps, + getItemProps, + isOpen, + inputValue, + selectedItem, + highlightedIndex + }) => ( + <div className={classes.container}> + {renderInput({ + fullWidth: true, + classes, + InputProps: getInputProps({ + placeholder: 'Search a country (start with a)', + id: 'integration-downshift-simple', + }), + })} + {isOpen ? ( + <Paper className={classes.paper} square> + {getSuggestions(inputValue).map((suggestion, index) => renderSuggestion({ + suggestion, + index, + itemProps: getItemProps({ item: suggestion.label }), + highlightedIndex, + selectedItem, + }), + )} + </Paper> + ) : null} + </div> + )} + </Downshift> + </div> + ); +} + +AutoSuggest.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(AutoSuggest); diff --git a/front/odiparpack/app/containers/Forms/demos/Checkboxes.js b/front/odiparpack/app/containers/Forms/demos/Checkboxes.js new file mode 100644 index 0000000..4979e60 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/Checkboxes.js @@ -0,0 +1,242 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; +import CheckBoxIcon from '@material-ui/icons/CheckBox'; +import Favorite from '@material-ui/icons/Favorite'; +import FavoriteBorder from '@material-ui/icons/FavoriteBorder'; +import { green } from '@material-ui/core/colors'; + +import { + Checkbox, + Typography, + Grid, + FormControl, + FormLabel, + FormControlLabel, + FormHelperText, + FormGroup, +} from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + field: { + margin: `${theme.spacing(3)}px 5px`, + }, + root: { + color: green[600], + '&$checked': { + color: green[500], + }, + }, + checked: {}, + size: { + width: 40, + height: 40, + }, + sizeIcon: { + fontSize: 20, + }, +}); + +class Checkboxes extends PureComponent { + state = { + checkedA: true, + checkedB: true, + checkedF: true, + checkedG: true, + gilad: true, + jason: false, + antoine: true, + }; + + handleChange = name => event => { + this.setState({ [name]: event.target.checked }); + }; + + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="space-around" + direction="row" + spacing={3} + > + <Grid + item + md={3} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Basic usage</Typography> + <div> + <Checkbox + checked={this.state.checkedA} + onChange={this.handleChange('checkedA')} + value="checkedA" + /> + <Checkbox + checked={this.state.checkedB} + onChange={this.handleChange('checkedB')} + value="checkedB" + color="primary" + /> + <Checkbox value="checkedC" /> + <Checkbox disabled value="checkedD" /> + <Checkbox disabled checked value="checkedE" /> + <Checkbox + checked={this.state.checkedF} + onChange={this.handleChange('checkedF')} + value="checkedF" + indeterminate + /> + <Checkbox defaultChecked color="default" value="checkedG" /> + </div> + </Grid> + <Grid + item + md={5} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Checkbox with label</Typography> + <Typography className={classes.divider}>Checkbox can also be used with a label description thanks to the FormControlLabel component.</Typography> + <div> + <FormGroup row> + <FormControlLabel + control={( + <Checkbox + checked={this.state.checkedA} + onChange={this.handleChange('checkedA')} + value="checkedA" + /> + )} + label="Secondary" + /> + <FormControlLabel + control={( + <Checkbox + checked={this.state.checkedB} + onChange={this.handleChange('checkedB')} + value="checkedB" + color="primary" + /> + )} + label="Primary" + /> + <FormControlLabel control={<Checkbox value="checkedC" />} label="Uncontrolled" /> + <FormControlLabel disabled control={<Checkbox value="checkedD" />} label="Disabled" /> + <FormControlLabel + disabled + control={<Checkbox checked value="checkedE" />} + label="Disabled" + /> + <FormControlLabel + control={( + <Checkbox + checked={this.state.checkedF} + onChange={this.handleChange('checkedF')} + value="checkedF" + indeterminate + /> + )} + label="Indeterminate" + /> + <FormControlLabel + control={( + <Checkbox + checked={this.state.checkedG} + onChange={this.handleChange('checkedG')} + value="checkedG" + classes={{ + root: classes.root, + checked: classes.checked, + }} + /> + )} + label="Custom color" + /> + <FormControlLabel + control={ + <Checkbox icon={<FavoriteBorder />} checkedIcon={<Favorite />} value="checkedH" /> + } + label="Custom icon" + /> + <FormControlLabel + control={( + <Checkbox + className={classes.size} + icon={<CheckBoxOutlineBlankIcon className={classes.sizeIcon} />} + checkedIcon={<CheckBoxIcon className={classes.sizeIcon} />} + value="checkedI" + /> + )} + label="Custom size" + /> + </FormGroup> + </div> + </Grid> + <Grid + item + md={4} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Checkbox in Form Group</Typography> + <Typography className={classes.divider}>FormGroup is a helpful wrapper used to group selection controls components that provides an easier API.</Typography> + <div> + <FormControl component="fieldset"> + <FormLabel component="legend">Assign responsibility</FormLabel> + <FormGroup> + <FormControlLabel + control={( + <Checkbox + checked={this.state.gilad} + onChange={this.handleChange('gilad')} + value="gilad" + /> + )} + label="Gilad Gray" + /> + <FormControlLabel + control={( + <Checkbox + checked={this.state.jason} + onChange={this.handleChange('jason')} + value="jason" + /> + )} + label="Jason Killian" + /> + <FormControlLabel + control={( + <Checkbox + checked={this.state.antoine} + onChange={this.handleChange('antoine')} + value="antoine" + /> + )} + label="Antoine Llorca" + /> + </FormGroup> + <FormHelperText>Be careful</FormHelperText> + </FormControl> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +Checkboxes.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Checkboxes); diff --git a/front/odiparpack/app/containers/Forms/demos/ComplexButtons.js b/front/odiparpack/app/containers/Forms/demos/ComplexButtons.js new file mode 100644 index 0000000..dbeab99 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/ComplexButtons.js @@ -0,0 +1,154 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import imgApi from 'ba-api/images'; + +import { Typography, ButtonBase } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + margin: `${theme.spacing(3)}px 0`, + }, + root: { + display: 'flex', + flexWrap: 'wrap', + minWidth: 300, + width: '100%', + }, + image: { + position: 'relative', + height: 200, + [theme.breakpoints.down('xs')]: { + width: '100% !important', // Overrides inline-style + height: 100, + }, + '&:hover, &$focusVisible': { + zIndex: 1, + '& $imageBackdrop': { + opacity: 0.15, + }, + '& $imageMarked': { + opacity: 0, + }, + '& $imageTitle': { + border: '4px solid currentColor', + }, + }, + }, + focusVisible: {}, + imageButton: { + position: 'absolute', + left: 0, + right: 0, + top: 0, + bottom: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + color: theme.palette.common.white, + }, + imageSrc: { + position: 'absolute', + left: 0, + right: 0, + top: 0, + bottom: 0, + backgroundSize: 'cover', + backgroundPosition: 'center 40%', + }, + imageBackdrop: { + position: 'absolute', + left: 0, + right: 0, + top: 0, + bottom: 0, + backgroundColor: theme.palette.common.black, + opacity: 0.4, + transition: theme.transitions.create('opacity'), + }, + imageTitle: { + position: 'relative', + padding: `${theme.spacing(2)}px ${theme.spacing(4)}px ${theme.spacing(1) + 6}px`, + }, + imageMarked: { + height: 3, + width: 18, + backgroundColor: theme.palette.common.white, + position: 'absolute', + bottom: -2, + left: 'calc(50% - 9px)', + transition: theme.transitions.create('opacity'), + }, +}); + +const images = [ + { + url: imgApi[0], + title: '330x200', + width: '40%', + id: '0' + }, + { + url: imgApi[3], + title: '250x200', + width: '30%', + id: '1' + }, + { + url: imgApi[5], + title: '250x200', + width: '30%', + id: '2' + }, +]; + +class ComplexButtons extends PureComponent { + render() { + const { classes } = this.props; + return ( + <Fragment> + <div className={classes.root}> + {images.map(image => ( + <ButtonBase + focusRipple + key={image.id} + className={classes.image} + focusVisibleClassName={classes.focusVisible} + style={{ + width: image.width, + }} + > + <span + className={classes.imageSrc} + style={{ + backgroundImage: `url(${image.url})`, + }} + /> + <span className={classes.imageBackdrop} /> + <span className={classes.imageButton}> + <Typography + component="span" + variant="subtitle1" + color="inherit" + className={classes.imageTitle} + > + {image.title} + <span className={classes.imageMarked} /> + </Typography> + </span> + </ButtonBase> + ))} + </div> + </Fragment> + ); + } +} + +ComplexButtons.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ComplexButtons); diff --git a/front/odiparpack/app/containers/Forms/demos/ControlledSelectbox.js b/front/odiparpack/app/containers/Forms/demos/ControlledSelectbox.js new file mode 100644 index 0000000..8ba1163 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/ControlledSelectbox.js @@ -0,0 +1,200 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; + +import { + Button, + Typography, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Input, + InputLabel, + MenuItem, + FormControl, + Select, + Grid, +} from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + field: { + margin: `${theme.spacing(3)}px 5px`, + }, + container: { + display: 'flex', + flexWrap: 'wrap', + }, + formControl: { + margin: theme.spacing(1), + minWidth: 120, + }, + button: { + display: 'block', + marginTop: theme.spacing(2), + }, +}); + +class ControlledSelectbox extends PureComponent { + state = { + open: false, + openRemotely: false, + age: '', + }; + + handleChange = name => event => { + this.setState({ [name]: Number(event.target.value) }); + }; + + handleChangeControll = event => { + this.setState({ [event.target.name]: event.target.value }); + }; + + handleClickOpen = () => { + this.setState({ open: true }); + }; + + handleClickOpenRemot = () => { + this.setState({ openRemotely: true }); + }; + + handleClose = () => { + this.setState({ open: false }); + }; + + handleOpen = () => { + this.setState({ open: true }); + }; + + handleCloseRemot = () => { + this.setState({ openRemotely: false }); + }; + + handleOpenRemot = () => { + this.setState({ openRemotely: true }); + }; + + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="space-around" + direction="row" + spacing={3} + > + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>With a Dialog</Typography> + <Typography className={classes.divider}>While its not encouraged by the Material Design specification, you can use a select inside a dialog.</Typography> + <div> + <Button variant="contained" color="secondary" onClick={this.handleClickOpen}>Open select dialog</Button> + <Dialog + disableBackdropClick + disableEscapeKeyDown + open={this.state.open} + onClose={this.handleClose} + > + <DialogTitle>Fill the form</DialogTitle> + <DialogContent> + <form className={classes.container}> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="age-native-simple">Age</InputLabel> + <Select + native + value={this.state.age} + onChange={this.handleChange('age')} + input={<Input id="age-native-simple" />} + > + <option value="" /> + <option value={10}>Ten</option> + <option value={20}>Twenty</option> + <option value={30}>Thirty</option> + </Select> + </FormControl> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="age-simple">Age</InputLabel> + <Select + value={this.state.age} + onChange={this.handleChange('age')} + input={<Input id="age-simple" />} + > + <MenuItem value=""> + <em>None</em> + </MenuItem> + <MenuItem value={10}>Ten</MenuItem> + <MenuItem value={20}>Twenty</MenuItem> + <MenuItem value={30}>Thirty</MenuItem> + </Select> + </FormControl> + </form> + </DialogContent> + <DialogActions> + <Button onClick={this.handleClose} color="primary"> + Cancel + </Button> + <Button onClick={this.handleClose} color="primary"> + Ok + </Button> + </DialogActions> + </Dialog> + </div> + </Grid> + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Controlled open Select</Typography> + <div> + <form autoComplete="off"> + <Button variant="contained" color="secondary" className={classes.button} onClick={this.handleClickOpenRemot}> + Open the select + </Button> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="controlled-open-select">Age</InputLabel> + <Select + open={this.state.openRemotely} + onClose={this.handleCloseRemot} + onOpen={this.handleOpenRemot} + value={this.state.age} + onChange={this.handleChangeControll} + inputProps={{ + name: 'age', + id: 'controlled-open-select', + }} + > + <MenuItem value=""> + <em>None</em> + </MenuItem> + <MenuItem value={10}>Ten</MenuItem> + <MenuItem value={20}>Twenty</MenuItem> + <MenuItem value={30}>Thirty</MenuItem> + </Select> + </FormControl> + </form> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +ControlledSelectbox.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ControlledSelectbox); diff --git a/front/odiparpack/app/containers/Forms/demos/CustomButtons.js b/front/odiparpack/app/containers/Forms/demos/CustomButtons.js new file mode 100644 index 0000000..f1600a0 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/CustomButtons.js @@ -0,0 +1,257 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { withStyles, MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'; +import AddIcon from '@material-ui/icons/Add'; +import FileUpload from '@material-ui/icons/CloudUpload'; +import { Link } from 'react-router-dom'; + +import { purple, green } from '@material-ui/core/colors'; + +import { Typography, Grid, Button, Fab, IconButton } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + margin: `${theme.spacing(3)}px 0`, + }, + field: { + margin: `${theme.spacing(3)}px 0`, + }, + button: { + margin: theme.spacing(1), + }, + container: { + display: 'flex', + flexWrap: 'wrap', + }, + margin: { + margin: theme.spacing(1), + }, + cssRoot: { + color: theme.palette.getContrastText(purple[500]), + backgroundColor: purple[500], + '&:hover': { + backgroundColor: purple[700], + }, + }, + bootstrapRoot: { + boxShadow: 'none', + textTransform: 'none', + borderRadius: 4, + fontSize: 16, + padding: '6px 12px', + border: '1px solid', + backgroundColor: '#007bff', + borderColor: '#007bff', + '&:hover': { + backgroundColor: '#0069d9', + borderColor: '#0062cc', + }, + '&:active': { + boxShadow: 'none', + backgroundColor: '#0062cc', + borderColor: '#005cbf', + }, + '&:focus': { + boxShadow: '0 0 0 0.2rem rgba(0,123,255,.5)', + }, + }, + gradientBtn: { + background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)', + borderRadius: 3, + border: 0, + color: 'white', + height: 48, + padding: '0 30px', + boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .30)', + }, + label: { + textTransform: 'capitalize', + }, + inputUpload: { + display: 'none', + }, +}); + +const theme = createMuiTheme({ + palette: { + primary: green, + }, +}); + +const LinkBtn = React.forwardRef(function LinkBtn(props, ref) { // eslint-disable-line + return <Link to={props.to} {...props} innerRef={ref} />; // eslint-disable-line +}); + +class CustomButtons extends PureComponent { + render() { + const { classes } = this.props; + const MyLink = React.forwardRef((props, ref) => <Link innerRef={ref} to="/app/forms/reduxform" {...props} />); // eslint-disable-line + return ( + <Fragment> + <Grid + container + alignItems="center" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Sizes</Typography> + <Typography className={classes.divider}> + Fancy larger or smaller buttons? Use the size or the mini property. + </Typography> + <div> + <div> + <Button size="small" className={classes.button}> + Small + </Button> + <Button size="medium" className={classes.button}> + Medium + </Button> + <Button size="large" className={classes.button}> + Large + </Button> + </div> + <div> + <Button variant="contained" size="small" color="primary" className={classes.button}> + Small + </Button> + <Button variant="contained" size="medium" color="primary" className={classes.button}> + Medium + </Button> + <Button variant="contained" size="large" color="primary" className={classes.button}> + Large + </Button> + </div> + <div> + <Button variant="outlined" size="small" color="primary" className={classes.button}> + Small + </Button> + <Button variant="outlined" size="medium" color="primary" className={classes.button}> + Medium + </Button> + <Button variant="outlined" size="large" color="primary" className={classes.button}> + Large + </Button> + </div> + <div> + <Fab size="small" color="secondary" aria-label="add" className={classes.button}> + <AddIcon /> + </Fab> + <Fab color="secondary" aria-label="add" className={classes.button}> + <AddIcon /> + </Fab> + </div> + </div> + </Grid> + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Style</Typography> + <Typography className={classes.divider}> + Here is an example of how you can change the main color of a Button. + </Typography> + <div> + <Button + variant="contained" + color="primary" + className={classNames(classes.margin, classes.cssRoot)} + > + Custom CSS + </Button> + <MuiThemeProvider theme={theme}> + <Button variant="contained" color="primary" className={classes.margin}> + MuiThemeProvider + </Button> + </MuiThemeProvider> + <Button + variant="contained" + color="primary" + disableRipple + className={classNames(classes.margin, classes.bootstrapRoot)} + > + Bootstrap + </Button> + <Button + classes={{ + root: classNames(classes.gradientBtn, classes.margin), // class name, e.g. `classes-root-x` + label: classes.label, // class name, e.g. `classes-label-x` + }} + > + Gradient Style + </Button> + </div> + </Grid> + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Linked Button</Typography> + <Typography className={classes.divider}> + One common use case is to use the button to trigger a navigation to a new page. + </Typography> + <div> + <Button + variant="contained" + color="primary" + className={classNames(classes.margin, classes.cssRoot)} + component={LinkBtn} + to="/app/forms/datetimepicker" + > + Go To Date Time Picker + </Button> + <Button color="secondary" variant="contained" component={MyLink}> Go To Redux Form </Button> + </div> + </Grid> + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Upload Button</Typography> + <Typography className={classes.divider}> + This a sample to trigger input files from button + </Typography> + <div> + <input + accept="image/*" + className={classes.inputUpload} + id="raised-button-file" + multiple + type="file" + /> + <label htmlFor="raised-button-file"> + <Button variant="contained" component="span" id="raised-button-file" className={classes.button}> + Upload + </Button> + </label> + <input accept="image/*" className={classes.inputUpload} id="icon-button-file" type="file" /> + <label htmlFor="icon-button-file"> + <IconButton color="primary" id="uploadBtnIcon" className={classes.button} component="span"> + <FileUpload /> + </IconButton> + </label> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +CustomButtons.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CustomButtons); diff --git a/front/odiparpack/app/containers/Forms/demos/DateInput.js b/front/odiparpack/app/containers/Forms/demos/DateInput.js new file mode 100644 index 0000000..fc3397c --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/DateInput.js @@ -0,0 +1,193 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { DatePicker, KeyboardDatePicker, MuiPickersUtilsProvider } from '@material-ui/pickers'; +import MomentUtils from '@date-io/moment'; +import DateFnsUtils from '@date-io/date-fns'; +import { withStyles } from '@material-ui/core/styles'; + +import frLocale from 'date-fns/locale/fr'; +import ruLocale from 'date-fns/locale/ru'; +import enLocale from 'date-fns/locale/en-US'; + +import { MenuItem, Menu, Icon, IconButton, Typography, Grid } from '@material-ui/core'; + +const localeMap = { + en: enLocale, + fr: frLocale, + ru: ruLocale, +}; + +const styles = theme => ({ + demo: { + height: 240, + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + picker: { + margin: `${theme.spacing(3)}px 5px`, + } +}); + +class DateInput extends PureComponent { + state = { + selectedDate: new Date(), + anchorEl: null, + currentLocale: 'fr', + } + + handleDateChange = (date) => { + this.setState({ selectedDate: date }); + } + + handleMenuOpen = (event) => { + event.stopPropagation(); + this.setState({ anchorEl: event.currentTarget }); + } + + handleMenuClose = () => { + this.setState({ anchorEl: null }); + }; + + selectLocale = (selectedLocale) => { + this.setState({ + currentLocale: selectedLocale, + anchorEl: null, + }); + } + + render() { + const { selectedDate, currentLocale, anchorEl } = this.state; + const { classes } = this.props; + const locale = localeMap[currentLocale]; + return ( + <Fragment> + <Grid + container + alignItems="center" + justify="space-around" + direction="row" + > + <Grid + item + md={4} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Basic usage</Typography> + <div className={classes.picker}> + <MuiPickersUtilsProvider utils={MomentUtils}> + <DatePicker + label="Basic example" + value={selectedDate} + onChange={this.handleDateChange} + animateYearScrolling={false} + /> + </MuiPickersUtilsProvider> + </div> + + <div className={classes.picker}> + <MuiPickersUtilsProvider utils={MomentUtils}> + <DatePicker + label="Clearable" + clearable + disableFuture + maxDateMessage="Date must be less than today" + value={selectedDate} + onChange={this.handleDateChange} + animateYearScrolling={false} + /> + </MuiPickersUtilsProvider> + </div> + </Grid> + <Grid + item + md={4} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Keyboard Input</Typography> + <div className={classes.picker}> + <MuiPickersUtilsProvider utils={MomentUtils}> + <KeyboardDatePicker + clearable + label="Uncontrolled input" + value={selectedDate} + onChange={this.handleDateChange} + animateYearScrolling={false} + /> + </MuiPickersUtilsProvider> + </div> + + <div className={classes.picker}> + <MuiPickersUtilsProvider utils={MomentUtils}> + <KeyboardDatePicker + label="Masked input" + format="DD/MM/YYYY" + placeholder="10/10/2018" + mask={[/\d/, /\d/, '/', /\d/, /\d/, '/', /\d/, /\d/, /\d/, /\d/]} + value={selectedDate} + onChange={this.handleDateChange} + animateYearScrolling={false} + /> + </MuiPickersUtilsProvider> + </div> + </Grid> + <Grid + item + md={4} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Localization</Typography> + <div className={classes.picker}> + <div className={classes.divider}> + <MuiPickersUtilsProvider utils={DateFnsUtils} locale={localeMap[currentLocale]}> + <DatePicker + value={selectedDate} + onChange={this.handleDateChange} + InputProps={{ + endAdornment: ( + <IconButton + aria-label="Select locale" + aria-owns={anchorEl ? 'locale-menu' : null} + onClick={this.handleMenuOpen} + > + <Icon> more_vert </Icon> + </IconButton> + ), + }} + /> + </MuiPickersUtilsProvider> + </div> + + <Menu + id="locale-menu" + anchorEl={anchorEl} + open={Boolean(anchorEl)} + onClose={this.handleMenuClose} + > + { + Object.keys(localeMap).map(localeItem => ( + <MenuItem + key={localeItem} + selected={localeItem === locale} + onClick={() => this.selectLocale(localeItem)} + > + {localeItem} + </MenuItem> + )) + } + </Menu> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + + +DateInput.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(DateInput); diff --git a/front/odiparpack/app/containers/Forms/demos/DateTimeInput.js b/front/odiparpack/app/containers/Forms/demos/DateTimeInput.js new file mode 100644 index 0000000..e594013 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/DateTimeInput.js @@ -0,0 +1,123 @@ +import React, { Fragment, PureComponent } from 'react'; +import { DateTimePicker, KeyboardDateTimePicker, MuiPickersUtilsProvider } from '@material-ui/pickers'; +import MomentUtils from '@date-io/moment'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { IconButton, Icon, InputAdornment, Typography, Grid } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + margin: `${theme.spacing(3)}px 0`, + }, + picker: { + margin: `${theme.spacing(3)}px 5px`, + } +}); + +class DateTimeInput extends PureComponent { + state = { + selectedDate: new Date(), + } + + handleDateChange = (date) => { + this.setState({ selectedDate: date }); + } + + render() { + const { selectedDate } = this.state; + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="space-around" + direction="row" + spacing={3} + > + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Basic usage</Typography> + <div className={classes.picker}> + <MuiPickersUtilsProvider utils={MomentUtils}> + <DateTimePicker + value={selectedDate} + disablePast + onChange={this.handleDateChange} + label="DateTimePicker" + /> + </MuiPickersUtilsProvider> + </div> + + <div className={classes.picker}> + <MuiPickersUtilsProvider utils={MomentUtils}> + <DateTimePicker + autoOk + ampm={false} + disableFuture + value={selectedDate} + onChange={this.handleDateChange} + label="24h clock" + /> + </MuiPickersUtilsProvider> + </div> + </Grid> + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Customization</Typography> + <div className={classes.picker}> + <MuiPickersUtilsProvider utils={MomentUtils}> + <DateTimePicker + autoOk + ampm={false} + showTabs={false} + disableFuture + value={selectedDate} + onChange={this.handleDateChange} + helperText="Hardcoded helper text" + leftArrowIcon={<Icon> add_alarm </Icon>} + rightArrowIcon={<Icon> snooze </Icon>} + InputProps={{ + endAdornment: ( + <InputAdornment position="end"> + <IconButton className={classes.iconBtn}> + <Icon>add_alarm</Icon> + </IconButton> + </InputAdornment> + ), + }} + /> + </MuiPickersUtilsProvider> + </div> + <div className={classes.picker}> + <MuiPickersUtilsProvider utils={MomentUtils}> + <KeyboardDateTimePicker + label="Keyboard input" + value={selectedDate} + onChange={this.handleDateChange} + format="YYYY/MM/DD hh:mm A" + mask={[/\d/, /\d/, /\d/, /\d/, '/', /\d/, /\d/, '/', /\d/, /\d/, ' ', /\d/, /\d/, ':', /\d/, /\d/, ' ', /a|p/i, 'M']} + /> + </MuiPickersUtilsProvider> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +DateTimeInput.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(DateTimeInput); diff --git a/front/odiparpack/app/containers/Forms/demos/FloatingButtons.js b/front/odiparpack/app/containers/Forms/demos/FloatingButtons.js new file mode 100644 index 0000000..5fbfa1c --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/FloatingButtons.js @@ -0,0 +1,185 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import SwipeableViews from 'react-swipeable-views'; +import AddIcon from '@material-ui/icons/Add'; +import EditIcon from '@material-ui/icons/Create'; +import NavigationIcon from '@material-ui/icons/Navigation'; +import UpIcon from '@material-ui/icons/KeyboardArrowUp'; +import DeleteIcon from '@material-ui/icons/Delete'; +import { green } from '@material-ui/core/colors'; + +import { Typography, Grid, Fab, AppBar, Tabs, Tab, Zoom, Icon } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + button: { + margin: theme.spacing(1), + }, + root: { + backgroundColor: theme.palette.background.paper, + [theme.breakpoints.up('sm')]: { + width: 500, + }, + margin: '0 auto', + position: 'relative', + minHeight: 200, + }, + fab: { + position: 'absolute', + bottom: theme.spacing(2), + right: theme.spacing(2), + }, + fabGreen: { + color: theme.palette.common.white, + backgroundColor: green[500], + }, + extendedIcon: { + marginRight: theme.spacing(1), + }, +}); + +function TabContainer(props) { + const { children, dir } = props; + + return ( + <Typography component="div" dir={dir} style={{ padding: 8 * 3 }}> + {children} + </Typography> + ); +} + +TabContainer.propTypes = { + children: PropTypes.node.isRequired, + dir: PropTypes.string.isRequired, +}; + +class FloatingButtons extends PureComponent { + state = { + value: 0, + }; + + handleChange = (event, value) => { + this.setState({ value }); + }; + + handleChangeIndex = index => { + this.setState({ value: index }); + }; + + render() { + const { classes, theme } = this.props; + const transitionDuration = { + enter: theme.transitions.duration.enteringScreen, + exit: theme.transitions.duration.leavingScreen, + }; + const fabs = [ + { + color: 'primary', + className: classes.fab, + icon: <AddIcon />, + }, + { + color: 'secondary', + className: classes.fab, + icon: <EditIcon />, + }, + { + color: 'inherit', + className: classNames(classes.fab, classes.fabGreen), + icon: <UpIcon />, + }, + ]; + + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid item md={6}> + <Typography variant="button" className={classes.divider}>Floating Action Buttons</Typography> + <Typography className={classes.divider}> + A floating action button represents the primary action in an application. + </Typography> + <Fab color="primary" aria-label="add" className={classes.button}> + <AddIcon /> + </Fab> + <Fab color="secondary" aria-label="edit" className={classes.button}> + <Icon>edit_icon</Icon> + </Fab> + <Fab variant="extended" color="secondary" aria-label="Delete" className={classes.button}> + <NavigationIcon className={classes.extendedIcon} /> + Extended + </Fab> + <Fab disabled aria-label="delete" className={classes.button}> + <DeleteIcon /> + </Fab> + </Grid> + <Grid item md={6}> + <Typography variant="button" className={classes.divider}>Floating BUtton in Tab</Typography> + <Typography className={classes.divider}> + A floating action button that spans multiple lateral screens (such as tabbed screens) should briefly disappear, then reappear if its action changes. + </Typography> + <div className={classes.root}> + <AppBar position="static" color="default"> + <Tabs + value={this.state.value} + onChange={this.handleChange} + indicatorColor="primary" + textColor="primary" + variant="fullWidth" + > + <Tab label="Item One" /> + <Tab label="Item Two" /> + <Tab label="Item Three" /> + </Tabs> + </AppBar> + <SwipeableViews + axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'} + index={this.state.value} + onChangeIndex={this.handleChangeIndex} + > + <TabContainer dir={theme.direction}>Item One</TabContainer> + <TabContainer dir={theme.direction}>Item Two</TabContainer> + <TabContainer dir={theme.direction}>Item Three</TabContainer> + </SwipeableViews> + {fabs.map((fab, index) => ( + <Zoom + key={fab.color} + in={this.state.value === index} + timeout={transitionDuration} + style={{ + transitionDelay: this.state.value === index ? transitionDuration.exit : 0, + }} + unmountOnExit + > + <Fab className={fab.className} color={fab.color}> + {fab.icon} + </Fab> + </Zoom> + ))} + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +FloatingButtons.propTypes = { + classes: PropTypes.object.isRequired, + theme: PropTypes.object.isRequired, +}; + +export default withStyles(styles, { withTheme: true })(FloatingButtons); diff --git a/front/odiparpack/app/containers/Forms/demos/FormattedInputs.js b/front/odiparpack/app/containers/Forms/demos/FormattedInputs.js new file mode 100644 index 0000000..9fbe9cd --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/FormattedInputs.js @@ -0,0 +1,109 @@ +import React from 'react'; +import MaskedInput from 'react-text-mask'; +import NumberFormat from 'react-number-format'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Input, InputLabel, TextField, FormControl } from '@material-ui/core'; + +const styles = theme => ({ + container: { + display: 'flex', + flexWrap: 'wrap', + }, + formControl: { + margin: theme.spacing(3), + }, +}); + +function TextMaskCustom(props) { + const { inputRef, ...other } = props; + + return ( + <MaskedInput + {...other} + ref={ref => { + inputRef(ref ? ref.inputElement : null); + }} + mask={['(', /[1-9]/, /\d/, /\d/, ')', ' ', /\d/, /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/]} + placeholderChar={'\u2000'} + showMask + /> + ); +} + +TextMaskCustom.propTypes = { + inputRef: PropTypes.func.isRequired, +}; + +function NumberFormatCustom(props) { + const { inputRef, onChange, ...other } = props; + + return ( + <NumberFormat + {...other} + getInputRef={inputRef} + onValueChange={values => { + onChange({ + target: { + value: values.value, + }, + }); + }} + thousandSeparator + prefix="$" + /> + ); +} + +NumberFormatCustom.propTypes = { + inputRef: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, +}; + +class FormattedInputs extends React.Component { + state = { + textmask: '(1 ) - ', + numberformat: '1320', + }; + + handleChange = name => event => { + this.setState({ + [name]: event.target.value, + }); + }; + + render() { + const { classes } = this.props; + const { textmask, numberformat } = this.state; + + return ( + <div className={classes.container}> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="formatted-text-mask-input">react-text-mask</InputLabel> + <Input + value={textmask} + onChange={this.handleChange('textmask')} + id="formatted-text-mask-input" + inputComponent={TextMaskCustom} + /> + </FormControl> + <TextField + className={classes.formControl} + label="react-number-format" + value={numberformat} + onChange={this.handleChange('numberformat')} + id="formatted-numberformat-input" + InputProps={{ + inputComponent: NumberFormatCustom, + }} + /> + </div> + ); + } +} + +FormattedInputs.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(FormattedInputs); diff --git a/front/odiparpack/app/containers/Forms/demos/HighlightSuggest.js b/front/odiparpack/app/containers/Forms/demos/HighlightSuggest.js new file mode 100644 index 0000000..ee13fd7 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/HighlightSuggest.js @@ -0,0 +1,198 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Autosuggest from 'react-autosuggest'; +import match from 'autosuggest-highlight/match'; +import parse from 'autosuggest-highlight/parse'; +import { withStyles } from '@material-ui/core/styles'; + +import { TextField, Paper, MenuItem } from '@material-ui/core'; + +const suggestions = [ + { label: 'Afghanistan' }, + { label: 'Aland Islands' }, + { label: 'Albania' }, + { label: 'Algeria' }, + { label: 'American Samoa' }, + { label: 'Andorra' }, + { label: 'Angola' }, + { label: 'Anguilla' }, + { label: 'Antarctica' }, + { label: 'Antigua and Barbuda' }, + { label: 'Argentina' }, + { label: 'Armenia' }, + { label: 'Aruba' }, + { label: 'Australia' }, + { label: 'Austria' }, + { label: 'Azerbaijan' }, + { label: 'Bahamas' }, + { label: 'Bahrain' }, + { label: 'Bangladesh' }, + { label: 'Barbados' }, + { label: 'Belarus' }, + { label: 'Belgium' }, + { label: 'Belize' }, + { label: 'Benin' }, + { label: 'Bermuda' }, + { label: 'Bhutan' }, + { label: 'Bolivia, Plurinational State of' }, + { label: 'Bonaire, Sint Eustatius and Saba' }, + { label: 'Bosnia and Herzegovina' }, + { label: 'Botswana' }, + { label: 'Bouvet Island' }, + { label: 'Brazil' }, + { label: 'British Indian Ocean Territory' }, + { label: 'Brunei Darussalam' }, +]; + +function renderInput(inputProps) { + const { classes, ref, ...other } = inputProps; + + return ( + <TextField + fullWidth + InputProps={{ + inputRef: ref, + classes: { + input: classes.input, + }, + ...other, + }} + /> + ); +} + +function renderSuggestion(suggestion, { query, isHighlighted }) { + const matches = match(suggestion.label, query); + const parts = parse(suggestion.label, matches); + + return ( + <MenuItem selected={isHighlighted} component="div"> + <div> + {parts.map((part, index) => ( + part.highlight ? ( + <span key={String(index)} style={{ fontWeight: 300 }}> + {part.text} + </span> + ) : ( + <strong key={String(index)} style={{ fontWeight: 500 }}> + {part.text} + </strong> + ) + ))} + </div> + </MenuItem> + ); +} + +function renderSuggestionsContainer(options) { + const { containerProps, children } = options; + + return ( + <Paper {...containerProps} square> + {children} + </Paper> + ); +} + +function getSuggestionValue(suggestion) { + return suggestion.label; +} + +function getSuggestions(value) { + const inputValue = value.trim().toLowerCase(); + const inputLength = inputValue.length; + let count = 0; + + return inputLength === 0 + ? [] : suggestions.filter(suggestion => { + const keep = count < 5 && suggestion.label.toLowerCase().slice(0, inputLength) === inputValue; + + if (keep) { + count += 1; + } + + return keep; + }); +} + +const styles = theme => ({ + container: { + flexGrow: 1, + position: 'relative', + height: 250, + }, + suggestionsContainerOpen: { + position: 'absolute', + zIndex: 1, + marginTop: theme.spacing(1), + left: 0, + right: 0, + }, + suggestion: { + display: 'block', + }, + suggestionsList: { + margin: 0, + padding: 0, + listStyleType: 'none', + }, +}); + +class HighlightSuggest extends React.Component { + state = { + value: '', + suggestions: [], + }; + + handleSuggestionsFetchRequested = ({ value }) => { + this.setState({ + suggestions: getSuggestions(value), + }); + }; + + handleSuggestionsClearRequested = () => { + this.setState({ + suggestions: [], + }); + }; + + handleChange = (event, { newValue }) => { + this.setState({ + value: newValue, + }); + }; + + render() { + const { classes } = this.props; + + return ( + <Autosuggest + theme={{ + container: classes.container, + suggestionsContainerOpen: classes.suggestionsContainerOpen, + suggestionsList: classes.suggestionsList, + suggestion: classes.suggestion, + }} + renderInputComponent={renderInput} + suggestions={this.state.suggestions} + onSuggestionsFetchRequested={this.handleSuggestionsFetchRequested} + onSuggestionsClearRequested={this.handleSuggestionsClearRequested} + renderSuggestionsContainer={renderSuggestionsContainer} + getSuggestionValue={getSuggestionValue} + renderSuggestion={renderSuggestion} + inputProps={{ + classes, + placeholder: 'Search a country (start with a)', + value: this.state.value, + onChange: this.handleChange, + }} + /> + ); + } +} + +HighlightSuggest.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(HighlightSuggest); diff --git a/front/odiparpack/app/containers/Forms/demos/InputAdornments.js b/front/odiparpack/app/containers/Forms/demos/InputAdornments.js new file mode 100644 index 0000000..1d6307e --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/InputAdornments.js @@ -0,0 +1,221 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { withStyles } from '@material-ui/core/styles'; +import Visibility from '@material-ui/icons/Visibility'; +import VisibilityOff from '@material-ui/icons/VisibilityOff'; +import AccountCircle from '@material-ui/icons/AccountCircle'; + +import { + Typography, + Grid, + IconButton, + Input, + InputLabel, + InputAdornment, + FormControl, + FormHelperText, + TextField, + MenuItem, +} from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + root: { + display: 'flex', + flexWrap: 'wrap', + }, + margin: { + margin: theme.spacing(3), + }, + withoutLabel: { + marginTop: theme.spacing(3), + }, + textField: { + flexBasis: 200, + }, +}); + +const ranges = [ + { + value: '0-20', + label: '0 to 20', + }, + { + value: '21-50', + label: '21 to 50', + }, + { + value: '51-100', + label: '51 to 100', + }, +]; + +class InputAdornments extends PureComponent { + state = { + amount: '', + password: '', + weight: '', + weightRange: '', + showPassword: false, + }; + + handleChange = prop => event => { + this.setState({ [prop]: event.target.value }); + }; + + handleMouseDownPassword = event => { + event.preventDefault(); + }; + + handleClickShowPassword = () => { + this.setState({ showPassword: !this.state.showPassword }); + }; + + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={3} + > + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Input Adornments</Typography> + <Typography className={classes.divider}>TextField is composed of smaller components that you can leverage directly to significantly customize your form inputs.</Typography> + <div className={classes.root}> + <TextField + label="With normal TextField" + id="simple-start-adornment" + className={classNames(classes.margin, classes.textField)} + InputProps={{ + startAdornment: <InputAdornment position="start">Kg</InputAdornment>, + }} + /> + <TextField + select + label="With Select" + className={classNames(classes.margin, classes.textField)} + value={this.state.weightRange} + onChange={this.handleChange('weightRange')} + InputProps={{ + startAdornment: <InputAdornment position="start">Kg</InputAdornment>, + }} + > + {ranges.map(option => ( + <MenuItem key={option.value} value={option.value}> + {option.label} + </MenuItem> + ))} + </TextField> + <FormControl fullWidth className={classes.margin}> + <InputLabel htmlFor="adornment-amount">Amount</InputLabel> + <Input + id="adornment-amount" + value={this.state.amount} + onChange={this.handleChange('amount')} + startAdornment={<InputAdornment position="start">$</InputAdornment>} + /> + </FormControl> + <FormControl + className={classNames(classes.margin, classes.withoutLabel, classes.textField)} + aria-describedby="weight-helper-text" + > + <Input + id="adornment-weight" + value={this.state.weight} + onChange={this.handleChange('weight')} + endAdornment={<InputAdornment position="end">Kg</InputAdornment>} + inputProps={{ + 'aria-label': 'Weight', + }} + /> + <FormHelperText id="weight-helper-text">Weight</FormHelperText> + </FormControl> + <FormControl className={classNames(classes.margin, classes.textField)}> + <InputLabel htmlFor="adornment-password">Password</InputLabel> + <Input + id="adornment-password" + type={this.state.showPassword ? 'text' : 'password'} + value={this.state.password} + onChange={this.handleChange('password')} + endAdornment={( + <InputAdornment position="end"> + <IconButton + aria-label="Toggle password visibility" + onClick={this.handleClickShowPassword} + onMouseDown={this.handleMouseDownPassword} + > + {this.state.showPassword ? <VisibilityOff /> : <Visibility />} + </IconButton> + </InputAdornment> + )} + /> + </FormControl> + </div> + </Grid> + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>With icon</Typography> + <Typography className={classes.divider}>Icons can be specified as prepended or appended.</Typography> + <FormControl className={classes.margin}> + <InputLabel htmlFor="input-with-icon-adornment">With a start adornment</InputLabel> + <Input + id="input-with-icon-adornment" + startAdornment={( + <InputAdornment position="start"> + <AccountCircle /> + </InputAdornment> + )} + /> + </FormControl> + <TextField + className={classes.margin} + id="input-with-icon-textfield" + label="TextField" + InputProps={{ + startAdornment: ( + <InputAdornment position="start"> + <AccountCircle /> + </InputAdornment> + ), + }} + /> + <div className={classes.margin}> + <Grid container spacing={1} alignItems="flex-end"> + <Grid item> + <AccountCircle /> + </Grid> + <Grid item> + <TextField id="input-with-icon-grid" label="With a grid" /> + </Grid> + </Grid> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +InputAdornments.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(InputAdornments); diff --git a/front/odiparpack/app/containers/Forms/demos/MultipleSelectbox.js b/front/odiparpack/app/containers/Forms/demos/MultipleSelectbox.js new file mode 100644 index 0000000..6609147 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/MultipleSelectbox.js @@ -0,0 +1,165 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; + +import { withStyles } from '@material-ui/core/styles'; + +import { + Input, + InputLabel, + MenuItem, + FormControl, + ListItemText, + Select, + Checkbox, + Chip, +} from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + margin: `${theme.spacing(3)}px 0`, + }, + field: { + margin: `${theme.spacing(3)}px 0`, + }, + root: { + display: 'flex', + flexWrap: 'wrap', + }, + formControl: { + margin: theme.spacing(1), + minWidth: 120, + maxWidth: 300, + }, + chips: { + display: 'flex', + flexWrap: 'wrap', + }, + chip: { + margin: theme.spacing(0.25), + }, +}); + +const ITEM_HEIGHT = 48; +const ITEM_PADDING_TOP = 8; +const MenuProps = { + PaperProps: { + style: { + maxHeight: (ITEM_HEIGHT * 4.5) + ITEM_PADDING_TOP, + width: 250, + }, + }, +}; + +const names = [ + 'Oliver Hansen', + 'Van Henry', + 'April Tucker', + 'Ralph Hubbard', + 'Omar Alexander', + 'Carlos Abbott', + 'Miriam Wagner', + 'Bradley Wilkerson', + 'Virginia Andrews', + 'Kelly Snyder', +]; + +class MultipleSelectbox extends PureComponent { + state = { + name: [], + }; + + handleChange = event => { + this.setState({ name: event.target.value }); + }; + + render() { + const { classes, theme } = this.props; + return ( + <Fragment> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="select-multiple">Name</InputLabel> + <Select + multiple + value={this.state.name} + onChange={this.handleChange} + input={<Input id="select-multiple" />} + MenuProps={MenuProps} + > + {names.map(name => ( + <MenuItem + key={name} + value={name} + style={{ + fontWeight: + this.state.name.indexOf(name) === -1 + ? theme.typography.fontWeightRegular + : theme.typography.fontWeightMedium, + }} + > + {name} + </MenuItem> + ))} + </Select> + </FormControl> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="select-multiple-checkbox">Tag</InputLabel> + <Select + multiple + value={this.state.name} + onChange={this.handleChange} + input={<Input id="select-multiple-checkbox" />} + renderValue={selected => selected.join(', ')} + MenuProps={MenuProps} + > + {names.map(name => ( + <MenuItem key={name} value={name}> + <Checkbox checked={this.state.name.indexOf(name) > -1} /> + <ListItemText primary={name} /> + </MenuItem> + ))} + </Select> + </FormControl> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="select-multiple-chip">Chip</InputLabel> + <Select + multiple + value={this.state.name} + onChange={this.handleChange} + input={<Input id="select-multiple-chip" />} + renderValue={selected => ( + <div className={classes.chips}> + {selected.map(value => <Chip key={value} label={value} className={classes.chip} />)} + </div> + )} + MenuProps={MenuProps} + > + {names.map(name => ( + <MenuItem + key={name} + value={name} + style={{ + fontWeight: + this.state.name.indexOf(name) === -1 + ? theme.typography.fontWeightRegular + : theme.typography.fontWeightMedium, + }} + > + {name} + </MenuItem> + ))} + </Select> + </FormControl> + </Fragment> + ); + } +} + +MultipleSelectbox.propTypes = { + classes: PropTypes.object.isRequired, + theme: PropTypes.object.isRequired, +}; + +export default withStyles(styles, { withTheme: true })(MultipleSelectbox); diff --git a/front/odiparpack/app/containers/Forms/demos/NativeSelectbox.js b/front/odiparpack/app/containers/Forms/demos/NativeSelectbox.js new file mode 100644 index 0000000..a0e3c4d --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/NativeSelectbox.js @@ -0,0 +1,153 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; + +import { withStyles } from '@material-ui/core/styles'; + +import { Input, InputLabel, FormControl, FormHelperText, Select } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + field: { + margin: `${theme.spacing(3)}px 5px`, + }, + root: { + display: 'flex', + flexWrap: 'wrap', + }, + formControl: { + margin: theme.spacing(1), + minWidth: 120, + }, + selectEmpty: { + marginTop: theme.spacing(2), + }, +}); + +class NativeSelectbox extends PureComponent { + state = { + age: '', + name: 'hai', + }; + + handleChange = name => event => { + this.setState({ [name]: event.target.value }); + }; + + render() { + const { classes } = this.props; + return ( + <Fragment> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="age-native-simple">Age</InputLabel> + <Select + native + value={this.state.age} + onChange={this.handleChange('age')} + inputProps={{ + id: 'age-native-simple', + }} + > + <option value="" /> + <option value={10}>Ten</option> + <option value={20}>Twenty</option> + <option value={30}>Thirty</option> + </Select> + </FormControl> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="age-native-helper">Age</InputLabel> + <Select + native + value={this.state.age} + onChange={this.handleChange('age')} + input={<Input id="age-native-helper" />} + > + <option value="" /> + <option value={10}>Ten</option> + <option value={20}>Twenty</option> + <option value={30}>Thirty</option> + </Select> + <FormHelperText>Some important helper text</FormHelperText> + </FormControl> + <FormControl className={classes.formControl}> + <Select + native + value={this.state.age} + onChange={this.handleChange('age')} + className={classes.selectEmpty} + > + <option value="">None</option> + <option value={10}>Ten</option> + <option value={20}>Twenty</option> + <option value={30}>Thirty</option> + </Select> + <FormHelperText>Without label</FormHelperText> + </FormControl> + <FormControl className={classes.formControl} disabled> + <InputLabel htmlFor="name-native-disabled">Name</InputLabel> + <Select + native + value={this.state.name} + onChange={this.handleChange('name')} + input={<Input id="name-native-disabled" />} + > + <option value="" /> + <optgroup label="Author"> + <option value="hai">Hai</option> + </optgroup> + <optgroup label="Contributors"> + <option value="olivier">Olivier</option> + <option value="kevin">Kevin</option> + </optgroup> + </Select> + <FormHelperText>Disabled</FormHelperText> + </FormControl> + <FormControl className={classes.formControl} error> + <InputLabel htmlFor="name-native-error">Name</InputLabel> + <Select + native + value={this.state.name} + onChange={this.handleChange('name')} + input={<Input id="name-native-error" />} + > + <option value="" /> + <optgroup label="Author"> + <option value="hai">Hai</option> + </optgroup> + <optgroup label="Contributors"> + <option value="olivier">Olivier</option> + <option value="kevin">Kevin</option> + </optgroup> + </Select> + <FormHelperText>Error</FormHelperText> + </FormControl> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="name-input">Name</InputLabel> + <Input id="name-input" /> + <FormHelperText>Alignment with an input</FormHelperText> + </FormControl> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="uncontrolled-native">Name</InputLabel> + <Select native defaultValue={30} input={<Input id="uncontrolled-native" />}> + <option value="" /> + <option value={10}>Ten</option> + <option value={20}>Twenty</option> + <option value={30}>Thirty</option> + </Select> + <FormHelperText>Uncontrolled</FormHelperText> + </FormControl> + </Fragment> + ); + } +} + +NativeSelectbox.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(NativeSelectbox); diff --git a/front/odiparpack/app/containers/Forms/demos/RadioButton.js b/front/odiparpack/app/containers/Forms/demos/RadioButton.js new file mode 100644 index 0000000..c378d96 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/RadioButton.js @@ -0,0 +1,212 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import RadioButtonUncheckedIcon from '@material-ui/icons/RadioButtonUnchecked'; +import RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonChecked'; +import { green } from '@material-ui/core/colors'; + +import { + Radio, + RadioGroup, + Typography, + Grid, + FormControl, + FormLabel, + FormControlLabel, + FormHelperText, +} from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + field: { + margin: `${theme.spacing(3)}px 5px`, + }, + root: { + color: green[600], + '&$checked': { + color: green[500], + }, + }, + formControl: { + margin: theme.spacing(3), + }, + group: { + margin: `${theme.spacing(1)}px 0`, + }, + checked: {}, + size: { + width: 40, + height: 40, + }, + sizeIcon: { + fontSize: 20, + }, +}); + +class RadioButton extends PureComponent { + state = { + value: 'female', + selectedValue: 'a', + }; + + handleChange = event => { + this.setState({ value: event.target.value }); + }; + + handleChangeOther = event => { + this.setState({ selectedValue: event.target.value }); + }; + + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="space-around" + direction="row" + spacing={3} + > + <Grid + item + md={7} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Basic usage</Typography> + <Typography className={classes.divider}>Radio buttons should have the most commonly used option selected by default.</Typography> + <div> + <Grid + container + alignItems="flex-start" + justify="space-around" + direction="row" + spacing={3} + > + <Grid + item + md={6} + className={classes.demo} + > + <FormControl component="fieldset" required className={classes.formControl}> + <FormLabel component="legend">Gender</FormLabel> + <RadioGroup + aria-label="gender" + name="gender1" + className={classes.group} + value={this.state.value} + onChange={this.handleChange} + > + <FormControlLabel value="female" control={<Radio />} label="Female" /> + <FormControlLabel value="male" control={<Radio />} label="Male" /> + <FormControlLabel value="other" control={<Radio />} label="Other" /> + <FormControlLabel + value="disabled" + disabled + control={<Radio />} + label="(Disabled option)" + /> + </RadioGroup> + </FormControl> + </Grid> + <Grid + item + md={6} + className={classes.demo} + > + <FormControl component="fieldset" required error className={classes.formControl}> + <FormLabel component="legend">Gender</FormLabel> + <RadioGroup + aria-label="gender" + name="gender2" + className={classes.group} + value={this.state.value} + onChange={this.handleChange} + > + <FormControlLabel value="male" control={<Radio color="primary" />} label="Male" /> + <FormControlLabel value="female" control={<Radio color="primary" />} label="Female" /> + <FormControlLabel value="other" control={<Radio color="primary" />} label="Other" /> + <FormControlLabel + value="disabled" + disabled + control={<Radio />} + label="(Disabled option)" + /> + </RadioGroup> + <FormHelperText>You can display an error</FormHelperText> + </FormControl> + </Grid> + </Grid> + </div> + </Grid> + <Grid + item + md={5} + className={classes.demo} + > + <div> + <Typography variant="button" className={classes.divider}>Radio without label</Typography> + <Typography className={classes.divider}>Radio can also be used standalone, without the wrapper.</Typography> + <Radio + checked={this.state.selectedValue === 'a'} + onChange={this.handleChangeOther} + value="a" + name="radio-button-demo" + aria-label="A" + /> + <Radio + checked={this.state.selectedValue === 'b'} + onChange={this.handleChangeOther} + value="b" + name="radio-button-demo" + aria-label="B" + /> + <Radio + checked={this.state.selectedValue === 'c'} + onChange={this.handleChangeOther} + value="c" + name="radio-button-demo" + aria-label="C" + classes={{ + root: classes.root, + checked: classes.checked, + }} + /> + <Radio + checked={this.state.selectedValue === 'd'} + onChange={this.handleChangeOther} + value="d" + color="default" + name="radio-button-demo" + aria-label="D" + /> + <Radio + checked={this.state.selectedValue === 'e'} + onChange={this.handleChangeOther} + value="e" + color="default" + name="radio-button-demo" + aria-label="E" + className={classes.size} + icon={<RadioButtonUncheckedIcon className={classes.sizeIcon} />} + checkedIcon={<RadioButtonCheckedIcon className={classes.sizeIcon} />} + /> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +RadioButton.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(RadioButton); diff --git a/front/odiparpack/app/containers/Forms/demos/RangeInput.js b/front/odiparpack/app/containers/Forms/demos/RangeInput.js new file mode 100644 index 0000000..247050e --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/RangeInput.js @@ -0,0 +1,92 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import InputRange from 'react-input-range'; +import { withStyles } from '@material-ui/core/styles'; +import 'ba-styles/vendors/react-input-range/react-input-range.css'; + +import { FormControl, Typography, Grid } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + inputRange: { + width: 300, + margin: `${theme.spacing(3)}px 5px`, + } +}); + +class RangeInput extends PureComponent { + state = { + valueRange: { + min: 3, + max: 7, + }, + valueRangeLabel: { + min: 5, + max: 10, + }, + } + + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="space-around" + direction="row" + spacing={2} + > + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Range Input Basic</Typography> + <FormControl className={classes.formControl}> + <div className={classes.inputRange}> + <InputRange + draggableTrack + maxValue={20} + minValue={0} + onChange={value => this.setState({ valueRange: value })} + value={this.state.valueRange} + /> + </div> + </FormControl> + </Grid> + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Range with Label</Typography> + <FormControl className={classes.formControl}> + <div className={classes.inputRange}> + <InputRange + maxValue={20} + minValue={0} + formatLabel={value => `${value} kg`} + value={this.state.valueRangeLabel} + onChange={value => this.setState({ valueRangeLabel: value })} + /> + </div> + </FormControl> + </Grid> + </Grid> + </Fragment> + ); + } +} + +RangeInput.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(RangeInput); diff --git a/front/odiparpack/app/containers/Forms/demos/RatingCustom.js b/front/odiparpack/app/containers/Forms/demos/RatingCustom.js new file mode 100644 index 0000000..002548b --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/RatingCustom.js @@ -0,0 +1,176 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { AddCircle, AddCircleOutline, Remove, ThumbUp } from '@material-ui/icons'; +import { Rating } from 'ba-components'; + +import { green, red, indigo as blue } from '@material-ui/core/colors'; + +import { FormControl, Typography, Grid, Chip } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + field: { + margin: `${theme.spacing(3)}px 0`, + }, + chip: { + margin: theme.spacing(1), + fontWeight: 'bold', + color: '#FFF', + background: red[300] + }, + blue: { + color: blue[300] + }, + green: { + color: green[500] + }, + red: { + color: red[300] + }, + small: { + '& button': { + width: 72, + height: 72, + padding: 16 + }, + '& svg': { + width: 36, + height: 36 + } + }, + medium: { + '& button': { + width: 96, + height: 96, + padding: 24 + }, + '& svg': { + width: 48, + height: 48 + } + }, + large: { + '& button': { + width: 120, + height: 120, + padding: 30 + }, + '& svg': { + width: 60, + height: 60 + } + } +}); + +class RatingCustom extends PureComponent { + state = { + rating: 3 + } + + handleChange = value => { + this.setState({ rating: value }); + } + + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="space-around" + direction="row" + spacing={2} + > + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Ratting Custom Icon</Typography> + <FormControl className={classes.formControl}> + <Rating + value={this.state.rating} + max={5} + onChange={(value) => this.handleChange(value)} + iconFilled={<ThumbUp className={classes.blue} />} + iconHovered={<ThumbUp className={classes.blue} />} + iconNormal={<Remove className={classes.red} />} + /> + </FormControl> + </Grid> + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Show Counter</Typography> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + > + <FormControl className={classes.formControl}> + <Rating + value={this.state.rating} + max={5} + onChange={(value) => this.handleChange(value)} + iconFilled={<AddCircle className={classes.green} />} + iconHovered={<AddCircleOutline className={classes.green} />} + iconNormal={<AddCircleOutline className={classes.green} />} + tooltipRenderer={(index) => <span>{ index }</span>} + tooltipPosition="bottom-center" + /> + </FormControl> + <Chip label={this.state.rating} className={classes.chip} /> + </Grid> + </Grid> + <Grid + item + md={12} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Ratting Custom Size</Typography> + <FormControl className={classes.formControl}> + <div className={classes.small}> + <Rating + value={this.state.rating} + max={5} + onChange={(value) => this.handleChange(value)} + /> + </div> + <div className={classes.medium}> + <Rating + value={this.state.rating} + max={5} + onChange={(value) => this.handleChange(value)} + /> + </div> + <div className={classes.large}> + <Rating + value={this.state.rating} + max={5} + onChange={(value) => this.handleChange(value)} + /> + </div> + </FormControl> + </Grid> + </Grid> + </Fragment> + ); + } +} + +RatingCustom.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(RatingCustom); diff --git a/front/odiparpack/app/containers/Forms/demos/RatingNormal.js b/front/odiparpack/app/containers/Forms/demos/RatingNormal.js new file mode 100644 index 0000000..acfb50d --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/RatingNormal.js @@ -0,0 +1,92 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Rating } from 'ba-components'; + +import { FormControl, Typography, Grid } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + margin: `${theme.spacing(3)}px 0`, + }, + field: { + margin: `${theme.spacing(3)}px 5px`, + }, +}); + +class RatingCustom extends PureComponent { + state = { + rating: 3 + } + + handleChange = value => { + this.setState({ rating: value }); + } + + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="space-around" + direction="row" + spacing={2} + > + <Grid + item + md={4} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Ratting Normal</Typography> + <FormControl className={classes.formControl}> + <Rating + value={this.state.rating} + max={5} + onChange={(value) => this.handleChange(value)} + /> + </FormControl> + </Grid> + <Grid + item + md={4} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Read Only</Typography> + <FormControl className={classes.formControl}> + <Rating + value={2} + max={5} + readOnly + /> + </FormControl> + </Grid> + <Grid + item + md={4} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Disabled</Typography> + <FormControl className={classes.formControl}> + <Rating + value={4} + max={5} + disabled + /> + </FormControl> + </Grid> + </Grid> + </Fragment> + ); + } +} + +RatingCustom.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(RatingCustom); diff --git a/front/odiparpack/app/containers/Forms/demos/ReduxFormDemo.js b/front/odiparpack/app/containers/Forms/demos/ReduxFormDemo.js new file mode 100644 index 0000000..32b5d3d --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/ReduxFormDemo.js @@ -0,0 +1,229 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Field, reduxForm } from 'redux-form/immutable'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { + CheckboxRedux, + SelectRedux, + TextFieldRedux, + SwitchRedux +} from 'ba-components/Forms/ReduxFormMUI'; +import { initAction, clearAction } from 'ba-actions/ReduxFormActions'; + +import { + Paper, + MenuItem, + InputLabel, + Grid, + Radio, + RadioGroup, + FormControl, + FormLabel, + FormControlLabel, + Typography, + Button, +} from '@material-ui/core'; + +const renderRadioGroup = ({ input, ...rest }) => ( + <RadioGroup + {...input} + {...rest} + valueselected={input.value} + onChange={(event, value) => input.onChange(value)} + /> +); + +// validation functions +const required = value => (value == null ? 'Required' : undefined); +const email = value => ( + value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) + ? 'Invalid email' + : undefined +); + +const styles = theme => ({ + root: { + flexGrow: 1, + padding: 30 + }, + field: { + width: '100%', + marginBottom: 20 + }, + fieldBasic: { + width: '100%', + marginBottom: 20, + marginTop: 10 + }, + inlineWrap: { + display: 'flex', + flexDirection: 'row' + }, + buttonInit: { + margin: theme.spacing(4), + textAlign: 'center' + }, +}); + +const initData = { + text: 'Sample Text', + email: '[email protected]', + radio: 'option1', + selection: 'option1', + onof: true, + checkbox: true, + textarea: 'This is default text' +}; + +class ReduxFormDemo extends Component { + render() { + const trueBool = true; + const { + classes, + handleSubmit, + pristine, + reset, + submitting, + init, + clear + } = this.props; + return ( + <div> + <Grid container spacing={3} alignItems="flex-start" direction="row" justify="center"> + <Grid item xs={12} md={6}> + <Paper className={classes.root}> + <Typography variant="h5" component="h3"> + Simple Form Example + </Typography> + <Typography component="p"> + The delay between when you click (Submit) and when the alert dialog pops up is intentional, to simulate server latency. + </Typography> + <div className={classes.buttonInit}> + <Button onClick={() => init(initData)} color="secondary" type="button"> + Load Sample Data + </Button> + <Button onClick={() => clear()} type="button"> + Clear Data + </Button> + </div> + <form onSubmit={handleSubmit}> + <div> + <Field + name="text" + component={TextFieldRedux} + placeholder="Text Field" + label="Text Field" + validate={required} + required + ref={this.saveRef} + className={classes.field} + /> + </div> + <div> + <Field + name="email" + component={TextFieldRedux} + placeholder="Email Field" + label="Email" + required + validate={[required, email]} + className={classes.field} + /> + </div> + <div className={classes.fieldBasic}> + <FormLabel component="label">Choose One Option</FormLabel> + <Field name="radio" className={classes.inlineWrap} component={renderRadioGroup}> + <FormControlLabel value="option1" control={<Radio />} label="Option 1" /> + <FormControlLabel value="option2" control={<Radio />} label="Option 2" /> + </Field> + </div> + <div> + <FormControl className={classes.field}> + <InputLabel htmlFor="selection">Selection</InputLabel> + <Field + name="selection" + component={SelectRedux} + placeholder="Selection" + autoWidth={trueBool} + > + <MenuItem value="option1">Option One</MenuItem> + <MenuItem value="option2">Option Two</MenuItem> + <MenuItem value="option3">Option Three</MenuItem> + </Field> + </FormControl> + </div> + <div className={classes.fieldBasic}> + <FormLabel component="label">Toggle Input</FormLabel> + <div className={classes.inlineWrap}> + <FormControlLabel control={<Field name="onof" component={SwitchRedux} />} label="On/OF Switch" /> + <FormControlLabel control={<Field name="checkbox" component={CheckboxRedux} />} label="Checkbox" /> + </div> + </div> + <div className={classes.field}> + <Field + name="textarea" + className={classes.field} + component={TextFieldRedux} + placeholder="Textarea" + label="Textarea" + multiline={trueBool} + rows={4} + /> + </div> + <div> + <Button variant="contained" color="secondary" type="submit" disabled={submitting}> + Submit + </Button> + <Button + type="button" + disabled={pristine || submitting} + onClick={reset} + > + Reset + </Button> + </div> + </form> + </Paper> + </Grid> + </Grid> + </div> + ); + } +} + +renderRadioGroup.propTypes = { + input: PropTypes.object.isRequired, +}; + +ReduxFormDemo.propTypes = { + classes: PropTypes.object.isRequired, + handleSubmit: PropTypes.func.isRequired, + reset: PropTypes.func.isRequired, + pristine: PropTypes.bool.isRequired, + submitting: PropTypes.bool.isRequired, + init: PropTypes.func.isRequired, + clear: PropTypes.func.isRequired, +}; + +const mapDispatchToProps = dispatch => ({ + init: bindActionCreators(initAction, dispatch), + clear: () => dispatch(clearAction), +}); + +const ReduxFormMapped = reduxForm({ + form: 'immutableExample', + enableReinitialize: true, +})(ReduxFormDemo); + +const reducer = 'initval'; +const FormInit = connect( + state => ({ + force: state, + initialValues: state.getIn([reducer, 'formValues']) + }), + mapDispatchToProps, +)(ReduxFormMapped); + +export default withStyles(styles)(FormInit); diff --git a/front/odiparpack/app/containers/Forms/demos/SelectSuggestionTags.js b/front/odiparpack/app/containers/Forms/demos/SelectSuggestionTags.js new file mode 100644 index 0000000..125fd23 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/SelectSuggestionTags.js @@ -0,0 +1,379 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import Select from 'react-select'; +import { emphasize, makeStyles, useTheme } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import NoSsr from '@material-ui/core/NoSsr'; +import TextField from '@material-ui/core/TextField'; +import Paper from '@material-ui/core/Paper'; +import Chip from '@material-ui/core/Chip'; +import MenuItem from '@material-ui/core/MenuItem'; +import CancelIcon from '@material-ui/icons/Cancel'; + +const suggestions = [ + { label: 'Afghanistan' }, + { label: 'Aland Islands' }, + { label: 'Albania' }, + { label: 'Algeria' }, + { label: 'American Samoa' }, + { label: 'Andorra' }, + { label: 'Angola' }, + { label: 'Anguilla' }, + { label: 'Antarctica' }, + { label: 'Antigua and Barbuda' }, + { label: 'Argentina' }, + { label: 'Armenia' }, + { label: 'Aruba' }, + { label: 'Australia' }, + { label: 'Austria' }, + { label: 'Azerbaijan' }, + { label: 'Bahamas' }, + { label: 'Bahrain' }, + { label: 'Bangladesh' }, + { label: 'Barbados' }, + { label: 'Belarus' }, + { label: 'Belgium' }, + { label: 'Belize' }, + { label: 'Benin' }, + { label: 'Bermuda' }, + { label: 'Bhutan' }, + { label: 'Bolivia, Plurinational State of' }, + { label: 'Bonaire, Sint Eustatius and Saba' }, + { label: 'Bosnia and Herzegovina' }, + { label: 'Botswana' }, + { label: 'Bouvet Island' }, + { label: 'Brazil' }, + { label: 'British Indian Ocean Territory' }, + { label: 'Brunei Darussalam' }, +].map(suggestion => ({ + value: suggestion.label, + label: suggestion.label, +})); + +const useStyles = makeStyles(theme => ({ + root: { + flexGrow: 1, + height: 250, + }, + input: { + display: 'flex', + padding: 0, + height: 'auto', + }, + valueContainer: { + display: 'flex', + flexWrap: 'wrap', + flex: 1, + alignItems: 'center', + overflow: 'hidden', + }, + chip: { + margin: theme.spacing(0.5, 0.25), + }, + chipFocused: { + backgroundColor: emphasize( + theme.palette.type === 'light' ? theme.palette.grey[300] : theme.palette.grey[700], + 0.08, + ), + }, + noOptionsMessage: { + padding: theme.spacing(1, 2), + }, + singleValue: { + fontSize: 16, + }, + placeholder: { + position: 'absolute', + left: 8, + bottom: 6, + fontSize: 16, + }, + paper: { + position: 'absolute', + zIndex: 1, + marginTop: theme.spacing(1), + left: 0, + right: 0, + }, +})); + +function NoOptionsMessage(props) { + const { selectProps, innerProps, children } = props; + return ( + <Typography + color="textSecondary" + className={selectProps.classes.noOptionsMessage} + {...innerProps} + > + {children} + </Typography> + ); +} + +NoOptionsMessage.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + selectProps: PropTypes.object.isRequired, +}; + +NoOptionsMessage.defaultProps = { + children: null, + innerProps: null +}; + +function inputComponent({ inputRef, ...props }) { + return <div ref={inputRef} {...props} />; +} + +inputComponent.propTypes = { + inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), +}; + +inputComponent.defaultProps = { + inputRef: undefined +}; + +function Control(props) { + const { + children, + innerProps, + innerRef, + selectProps: { classes, TextFieldProps }, + } = props; + + return ( + <TextField + fullWidth + InputProps={{ + inputComponent, + inputProps: { + className: classes.input, + ref: innerRef, + children, + ...innerProps, + }, + }} + {...TextFieldProps} + /> + ); +} + +Control.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + selectProps: PropTypes.object.isRequired, +}; + +Control.defaultProps = { + children: null, + innerProps: null, + innerRef: undefined +}; + +function Option(props) { + const { + innerRef, + isFocused, + isSelected, + innerProps, + children + } = props; + return ( + <MenuItem + ref={innerRef} + selected={isFocused} + component="div" + style={{ + fontWeight: isSelected ? 500 : 400, + }} + {...innerProps} + > + {children} + </MenuItem> + ); +} + +Option.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + isFocused: PropTypes.bool, + isSelected: PropTypes.bool, +}; + +Option.defaultProps = { + children: null, + innerProps: null, + innerRef: undefined, + isFocused: false, + isSelected: false +}; + +function Placeholder(props) { + const { selectProps, innerProps, children } = props; + return ( + <Typography + color="textSecondary" + className={selectProps.classes.placeholder} + {...innerProps} + > + {children} + </Typography> + ); +} + +Placeholder.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + selectProps: PropTypes.object.isRequired, +}; + +Placeholder.defaultProps = { + children: null, + innerProps: null, +}; + +function SingleValue(props) { + const { selectProps, children, innerProps } = props; + return ( + <Typography className={selectProps.classes.singleValue} {...innerProps}> + {children} + </Typography> + ); +} + +SingleValue.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + selectProps: PropTypes.object.isRequired, +}; + +SingleValue.defaultProps = { + children: null, + innerProps: null, +}; + +function ValueContainer(props) { + const { selectProps, children } = props; + return <div className={selectProps.classes.valueContainer}>{children}</div>; +} + +ValueContainer.propTypes = { + children: PropTypes.node, + selectProps: PropTypes.object.isRequired, +}; + +ValueContainer.defaultProps = { + children: null, +}; + +function MultiValue(props) { + const { + children, + selectProps, + removeProps, + isFocused + } = props; + return ( + <Chip + tabIndex={-1} + label={children} + className={classNames(selectProps.classes.chip, { + [selectProps.classes.chipFocused]: isFocused, + })} + onDelete={removeProps.onClick} + deleteIcon={<CancelIcon {...removeProps} />} + /> + ); +} + +MultiValue.propTypes = { + children: PropTypes.node, + isFocused: PropTypes.bool, + removeProps: PropTypes.object.isRequired, + selectProps: PropTypes.object.isRequired, +}; + +MultiValue.defaultProps = { + children: null, + isFocused: false, +}; + +function Menu(props) { + const { selectProps, innerProps, children } = props; + return ( + <Paper square className={selectProps.classes.paper} {...innerProps}> + {children} + </Paper> + ); +} + +Menu.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + selectProps: PropTypes.object, +}; + +Menu.defaultProps = { + children: null, + innerProps: null, + selectProps: null, +}; + +const components = { + Control, + Menu, + MultiValue, + NoOptionsMessage, + Option, + Placeholder, + SingleValue, + ValueContainer, +}; + +export default function SelectSuggestions() { + const classes = useStyles(); + const theme = useTheme(); + const [multi, setMulti] = React.useState(null); + + function handleChangeMulti(value) { + setMulti(value); + } + + const selectStyles = { + input: base => ({ + ...base, + color: theme.palette.text.primary, + '& input': { + font: 'inherit', + }, + }), + }; + + return ( + <div className={classes.root}> + <NoSsr> + <Select + classes={classes} + styles={selectStyles} + inputId="react-select-multiple" + TextFieldProps={{ + label: 'Countries', + InputLabelProps: { + htmlFor: 'react-select-multiple', + shrink: true, + }, + placeholder: 'Select multiple countries', + }} + options={suggestions} + components={components} + value={multi} + onChange={handleChangeMulti} + isMulti + /> + </NoSsr> + </div> + ); +} diff --git a/front/odiparpack/app/containers/Forms/demos/SelectSuggestions.js b/front/odiparpack/app/containers/Forms/demos/SelectSuggestions.js new file mode 100644 index 0000000..2986aff --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/SelectSuggestions.js @@ -0,0 +1,378 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import Select from 'react-select'; +import { emphasize, makeStyles, useTheme } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import NoSsr from '@material-ui/core/NoSsr'; +import TextField from '@material-ui/core/TextField'; +import Paper from '@material-ui/core/Paper'; +import Chip from '@material-ui/core/Chip'; +import MenuItem from '@material-ui/core/MenuItem'; +import CancelIcon from '@material-ui/icons/Cancel'; + +const suggestions = [ + { label: 'Afghanistan' }, + { label: 'Aland Islands' }, + { label: 'Albania' }, + { label: 'Algeria' }, + { label: 'American Samoa' }, + { label: 'Andorra' }, + { label: 'Angola' }, + { label: 'Anguilla' }, + { label: 'Antarctica' }, + { label: 'Antigua and Barbuda' }, + { label: 'Argentina' }, + { label: 'Armenia' }, + { label: 'Aruba' }, + { label: 'Australia' }, + { label: 'Austria' }, + { label: 'Azerbaijan' }, + { label: 'Bahamas' }, + { label: 'Bahrain' }, + { label: 'Bangladesh' }, + { label: 'Barbados' }, + { label: 'Belarus' }, + { label: 'Belgium' }, + { label: 'Belize' }, + { label: 'Benin' }, + { label: 'Bermuda' }, + { label: 'Bhutan' }, + { label: 'Bolivia, Plurinational State of' }, + { label: 'Bonaire, Sint Eustatius and Saba' }, + { label: 'Bosnia and Herzegovina' }, + { label: 'Botswana' }, + { label: 'Bouvet Island' }, + { label: 'Brazil' }, + { label: 'British Indian Ocean Territory' }, + { label: 'Brunei Darussalam' }, +].map(suggestion => ({ + value: suggestion.label, + label: suggestion.label, +})); + +const useStyles = makeStyles(theme => ({ + root: { + flexGrow: 1, + height: 250, + }, + input: { + display: 'flex', + padding: 0, + height: 'auto', + }, + valueContainer: { + display: 'flex', + flexWrap: 'wrap', + flex: 1, + alignItems: 'center', + overflow: 'hidden', + }, + chip: { + margin: theme.spacing(0.5, 0.25), + }, + chipFocused: { + backgroundColor: emphasize( + theme.palette.type === 'light' ? theme.palette.grey[300] : theme.palette.grey[700], + 0.08, + ), + }, + noOptionsMessage: { + padding: theme.spacing(1, 2), + }, + singleValue: { + fontSize: 16, + }, + placeholder: { + position: 'absolute', + left: 8, + bottom: 6, + fontSize: 16, + }, + paper: { + position: 'absolute', + zIndex: 1, + marginTop: theme.spacing(1), + left: 0, + right: 0, + }, +})); + +function NoOptionsMessage(props) { + const { selectProps, innerProps, children } = props; + return ( + <Typography + color="textSecondary" + className={selectProps.classes.noOptionsMessage} + {...innerProps} + > + {children} + </Typography> + ); +} + +NoOptionsMessage.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + selectProps: PropTypes.object.isRequired, +}; + +NoOptionsMessage.defaultProps = { + children: null, + innerProps: null +}; + +function inputComponent({ inputRef, ...props }) { + return <div ref={inputRef} {...props} />; +} + +inputComponent.propTypes = { + inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), +}; + +inputComponent.defaultProps = { + inputRef: undefined +}; + +function Control(props) { + const { + children, + innerProps, + innerRef, + selectProps: { classes, TextFieldProps }, + } = props; + + return ( + <TextField + fullWidth + InputProps={{ + inputComponent, + inputProps: { + className: classes.input, + ref: innerRef, + children, + ...innerProps, + }, + }} + {...TextFieldProps} + /> + ); +} + +Control.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + selectProps: PropTypes.object.isRequired, +}; + +Control.defaultProps = { + children: null, + innerProps: null, + innerRef: undefined +}; + +function Option(props) { + const { + innerRef, + isFocused, + isSelected, + innerProps, + children + } = props; + return ( + <MenuItem + ref={innerRef} + selected={isFocused} + component="div" + style={{ + fontWeight: isSelected ? 500 : 400, + }} + {...innerProps} + > + {children} + </MenuItem> + ); +} + +Option.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + isFocused: PropTypes.bool, + isSelected: PropTypes.bool, +}; + +Option.defaultProps = { + children: null, + innerProps: null, + innerRef: undefined, + isFocused: false, + isSelected: false +}; + +function Placeholder(props) { + const { selectProps, innerProps, children } = props; + return ( + <Typography + color="textSecondary" + className={selectProps.classes.placeholder} + {...innerProps} + > + {children} + </Typography> + ); +} + +Placeholder.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + selectProps: PropTypes.object.isRequired, +}; + +Placeholder.defaultProps = { + children: null, + innerProps: null, +}; + +function SingleValue(props) { + const { selectProps, children, innerProps } = props; + return ( + <Typography className={selectProps.classes.singleValue} {...innerProps}> + {children} + </Typography> + ); +} + +SingleValue.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + selectProps: PropTypes.object.isRequired, +}; + +SingleValue.defaultProps = { + children: null, + innerProps: null, +}; + +function ValueContainer(props) { + const { selectProps, children } = props; + return <div className={selectProps.classes.valueContainer}>{children}</div>; +} + +ValueContainer.propTypes = { + children: PropTypes.node, + selectProps: PropTypes.object.isRequired, +}; + +ValueContainer.defaultProps = { + children: null, +}; + +function MultiValue(props) { + const { + children, + selectProps, + removeProps, + isFocused + } = props; + return ( + <Chip + tabIndex={-1} + label={children} + className={classNames(selectProps.classes.chip, { + [selectProps.classes.chipFocused]: isFocused, + })} + onDelete={removeProps.onClick} + deleteIcon={<CancelIcon {...removeProps} />} + /> + ); +} + +MultiValue.propTypes = { + children: PropTypes.node, + isFocused: PropTypes.bool, + removeProps: PropTypes.object.isRequired, + selectProps: PropTypes.object.isRequired, +}; + +MultiValue.defaultProps = { + children: null, + isFocused: false, +}; + +function Menu(props) { + const { selectProps, innerProps, children } = props; + return ( + <Paper square className={selectProps.classes.paper} {...innerProps}> + {children} + </Paper> + ); +} + +Menu.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + selectProps: PropTypes.object, +}; + +Menu.defaultProps = { + children: null, + innerProps: null, + selectProps: null, +}; + +const components = { + Control, + Menu, + MultiValue, + NoOptionsMessage, + Option, + Placeholder, + SingleValue, + ValueContainer, +}; + +export default function SelectSuggestions() { + const classes = useStyles(); + const theme = useTheme(); + const [single, setSingle] = React.useState(null); + + function handleChangeSingle(value) { + setSingle(value); + } + + const selectStyles = { + input: base => ({ + ...base, + color: theme.palette.text.primary, + '& input': { + font: 'inherit', + }, + }), + }; + + return ( + <div className={classes.root}> + <NoSsr> + <Select + classes={classes} + styles={selectStyles} + inputId="react-select-single" + TextFieldProps={{ + label: 'Country', + InputLabelProps: { + htmlFor: 'react-select-single', + shrink: true, + }, + placeholder: 'Search a country (start with a)', + }} + options={suggestions} + components={components} + value={single} + onChange={handleChangeSingle} + /> + </NoSsr> + </div> + ); +} diff --git a/front/odiparpack/app/containers/Forms/demos/SimpleSelectbox.js b/front/odiparpack/app/containers/Forms/demos/SimpleSelectbox.js new file mode 100644 index 0000000..eb7bd0a --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/SimpleSelectbox.js @@ -0,0 +1,180 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; + +import { withStyles } from '@material-ui/core/styles'; + +import { Input, InputLabel, MenuItem, FormControl, FormHelperText, Select } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + field: { + margin: `${theme.spacing(3)}px 5px`, + }, + root: { + display: 'flex', + flexWrap: 'wrap', + }, + formControl: { + margin: theme.spacing(1), + minWidth: 120, + }, + selectEmpty: { + marginTop: theme.spacing(2), + }, +}); + +class SimpleSelectbox extends PureComponent { + state = { + age: '', + name: 'hai', + }; + + handleChange = event => { + this.setState({ [event.target.name]: event.target.value }); + }; + + render() { + const { classes } = this.props; + return ( + <Fragment> + <form className={classes.root} autoComplete="off"> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="age-simple2">Age</InputLabel> + <Select + value={this.state.age} + onChange={this.handleChange} + inputProps={{ + name: 'age', + id: 'age-simple2', + }} + > + <MenuItem value=""> + <em>None</em> + </MenuItem> + <MenuItem value={10}>Ten</MenuItem> + <MenuItem value={20}>Twenty</MenuItem> + <MenuItem value={30}>Thirty</MenuItem> + </Select> + </FormControl> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="age-helper">Age</InputLabel> + <Select + value={this.state.age} + onChange={this.handleChange} + input={<Input name="age" id="age-helper" />} + > + <MenuItem value=""> + <em>None</em> + </MenuItem> + <MenuItem value={10}>Ten</MenuItem> + <MenuItem value={20}>Twenty</MenuItem> + <MenuItem value={30}>Thirty</MenuItem> + </Select> + <FormHelperText>Some important helper text</FormHelperText> + </FormControl> + <FormControl className={classes.formControl}> + <Select + value={this.state.age} + onChange={this.handleChange} + displayEmpty + name="age" + className={classes.selectEmpty} + > + <MenuItem value=""> + <em>None</em> + </MenuItem> + <MenuItem value={10}>Ten</MenuItem> + <MenuItem value={20}>Twenty</MenuItem> + <MenuItem value={30}>Thirty</MenuItem> + </Select> + <FormHelperText>Without label</FormHelperText> + </FormControl> + <FormControl className={classes.formControl} disabled> + <InputLabel htmlFor="name-disabled">Name</InputLabel> + <Select + value={this.state.name} + onChange={this.handleChange} + input={<Input name="name" id="name-disabled" />} + > + <MenuItem value=""> + <em>None</em> + </MenuItem> + <MenuItem value="hai">Hai</MenuItem> + <MenuItem value="olivier">Olivier</MenuItem> + <MenuItem value="kevin">Kevin</MenuItem> + </Select> + <FormHelperText>Disabled</FormHelperText> + </FormControl> + <FormControl className={classes.formControl} error> + <InputLabel htmlFor="name-error">Name</InputLabel> + <Select + value={this.state.name} + onChange={this.handleChange} + name="name" + renderValue={value => `⚠️ - ${value}`} + input={<Input id="name-error" />} + > + <MenuItem value=""> + <em>None</em> + </MenuItem> + <MenuItem value="hai">Hai</MenuItem> + <MenuItem value="olivier">Olivier</MenuItem> + <MenuItem value="kevin">Kevin</MenuItem> + </Select> + <FormHelperText>Error</FormHelperText> + </FormControl> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="name-input">Name</InputLabel> + <Input id="name-input" /> + <FormHelperText>Alignment with an input</FormHelperText> + </FormControl> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="name-readonly">Name</InputLabel> + <Select + value={this.state.name} + onChange={this.handleChange} + input={<Input name="name" id="name-readonly" readOnly />} + > + <MenuItem value=""> + <em>None</em> + </MenuItem> + <MenuItem value="hai">Hai</MenuItem> + <MenuItem value="olivier">Olivier</MenuItem> + <MenuItem value="kevin">Kevin</MenuItem> + </Select> + <FormHelperText>Read only</FormHelperText> + </FormControl> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="age-simple3">Age</InputLabel> + <Select + value={this.state.age} + onChange={this.handleChange} + input={<Input name="age" id="age-simple3" />} + autoWidth + > + <MenuItem value=""> + <em>None</em> + </MenuItem> + <MenuItem value={10}>Ten</MenuItem> + <MenuItem value={20}>Twenty</MenuItem> + <MenuItem value={30}>Thirty</MenuItem> + </Select> + <FormHelperText>Auto width</FormHelperText> + </FormControl> + </form> + </Fragment> + ); + } +} + +SimpleSelectbox.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(SimpleSelectbox); diff --git a/front/odiparpack/app/containers/Forms/demos/SliderInput.js b/front/odiparpack/app/containers/Forms/demos/SliderInput.js new file mode 100644 index 0000000..d0513ed --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/SliderInput.js @@ -0,0 +1,104 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import InputRange from 'react-input-range'; +import { withStyles } from '@material-ui/core/styles'; +import 'ba-styles/vendors/react-input-range/react-input-range.css'; + +import { FormControl, Typography, Grid } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + inputRange: { + width: 200, + margin: `${theme.spacing(3)}px 5px`, + } +}); + +class SliderInput extends PureComponent { + state = { + value: 10, + valueDisabled: 5, + valueDecimal: 16, + } + + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="space-around" + direction="row" + spacing={2} + > + <Grid + item + md={4} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Slider Input Basic</Typography> + <FormControl className={classes.formControl}> + <div className={classes.inputRange}> + <InputRange + maxValue={20} + minValue={0} + value={this.state.value} + onChange={value => this.setState({ value })} + /> + </div> + </FormControl> + </Grid> + <Grid + item + md={4} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Slider Input Disabled</Typography> + <FormControl className={classes.formControl}> + <div className={classes.inputRange}> + <InputRange + maxValue={20} + minValue={0} + disabled + value={this.state.valueDisabled} + onChange={value => this.setState({ valueDisabled: value })} + /> + </div> + </FormControl> + </Grid> + <Grid + item + md={4} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Formated Value</Typography> + <FormControl className={classes.formControl}> + <div className={classes.inputRange}> + <InputRange + maxValue={20} + minValue={0} + formatLabel={value => value.toFixed(2)} + value={this.state.valueDecimal} + onChange={value => this.setState({ valueDecimal: value })} + /> + </div> + </FormControl> + </Grid> + </Grid> + </Fragment> + ); + } +} + +SliderInput.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(SliderInput); diff --git a/front/odiparpack/app/containers/Forms/demos/StandardButtons.js b/front/odiparpack/app/containers/Forms/demos/StandardButtons.js new file mode 100644 index 0000000..d88c004 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/StandardButtons.js @@ -0,0 +1,223 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { withStyles } from '@material-ui/core/styles'; +import DeleteIcon from '@material-ui/icons/Delete'; +import AddShoppingCartIcon from '@material-ui/icons/AddShoppingCart'; +import PhotoCamera from '@material-ui/icons/PhotoCamera'; +import FileUpload from '@material-ui/icons/CloudUpload'; +import KeyboardVoice from '@material-ui/icons/KeyboardVoice'; +import Save from '@material-ui/icons/Save'; + +import { Button, Typography, Grid, Icon, IconButton } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + field: { + margin: `${theme.spacing(3)}px 5px`, + }, + button: { + margin: theme.spacing(1), + }, + inputUpload: { + display: 'none', + }, + leftIcon: { + marginRight: theme.spacing(1), + }, + rightIcon: { + marginLeft: theme.spacing(1), + }, + iconSmall: { + fontSize: 20, + }, +}); + +function doSomething(event) { + alert(event.currentTarget.getAttribute('data-something')); +} + +class StandardButtons extends PureComponent { + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="center" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Flat Button</Typography> + <Typography className={classes.divider}> + Flat buttons are text-only buttons. They may be used in dialogs, toolbars, or inline. They do not lift, but fill with color on press. + </Typography> + <Button className={classes.button}>Default</Button> + <Button color="primary" className={classes.button}> + Primary + </Button> + <Button color="secondary" className={classes.button}> + Secondary + </Button> + <Button disabled className={classes.button}> + Disabled + </Button> + <Button href="#flat-buttons" className={classes.button}> + Link + </Button> + <Button disabled href="/" className={classes.button}> + Link disabled + </Button> + <Button className={classes.button} onClick={doSomething} data-something="here I am"> + Does something + </Button> + </Grid> + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Raised Button</Typography> + <Typography className={classes.divider}> + Raised buttons are rectangular-shaped buttons. They may be used inline. They lift and display ink reactions on press. + </Typography> + <Button variant="contained" className={classes.button}> + Default + </Button> + <Button variant="contained" color="primary" className={classes.button}> + Primary + </Button> + <Button variant="contained" color="secondary" className={classes.button}> + Secondary + </Button> + <Button variant="contained" color="secondary" disabled className={classes.button}> + Disabled + </Button> + <input + accept="image/*" + className={classes.inputUpload} + id="raised-button-file" + multiple + type="file" + /> + <label htmlFor="raised-button-file"> + <Button variant="contained" component="span" className={classes.button}> + Upload + </Button> + </label> + </Grid> + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Outline Button</Typography> + <Button variant="outlined" className={classes.button}> + Default + </Button> + <Button variant="outlined" color="primary" className={classes.button}> + Primary + </Button> + <Button variant="outlined" color="secondary" className={classes.button}> + Secondary + </Button> + <Button variant="outlined" disabled className={classes.button}> + Disabled + </Button> + <Button variant="outlined" href="#outlined-buttons" className={classes.button}> + Link + </Button> + <input + accept="image/*" + className={classes.inputUpload} + id="outlined-button-file" + multiple + type="file" + /> + <label htmlFor="outlined-button-file"> + <Button variant="outlined" component="span" className={classes.button}> + Upload + </Button> + </label> + </Grid> + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Icon Button</Typography> + <Typography className={classes.divider}> + Icon buttons are commonly found in app bars and toolbars. + </Typography> + <IconButton className={classes.button} aria-label="Delete"> + <DeleteIcon /> + </IconButton> + <IconButton className={classes.button} aria-label="Delete" disabled color="primary"> + <DeleteIcon /> + </IconButton> + <IconButton color="secondary" className={classes.button} aria-label="Add an alarm"> + <Icon>alarm</Icon> + </IconButton> + <IconButton color="primary" className={classes.button} aria-label="Add to shopping cart"> + <AddShoppingCartIcon /> + </IconButton> + <input accept="image/*" className={classes.inputUpload} id="icon-button-file" type="file" /> + <label htmlFor="icon-button-file"> + <IconButton color="primary" className={classes.button} component="span"> + <PhotoCamera /> + </IconButton> + </label> + </Grid> + <Grid + item + md={12} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Icon Raised Button</Typography> + <Typography className={classes.divider}> + Icon buttons are commonly found in app bars and toolbars. + </Typography> + <Button className={classes.button} variant="contained" color="secondary"> + Delete + <DeleteIcon className={classes.rightIcon} /> + </Button> + <Button className={classes.button} variant="contained" color="primary"> + Send + <Icon className={classes.rightIcon}>send</Icon> + </Button> + <Button className={classes.button} variant="contained" color="default"> + Upload + <FileUpload className={classes.rightIcon} /> + </Button> + <Button className={classes.button} variant="contained" disabled color="secondary"> + <KeyboardVoice className={classes.leftIcon} /> + Talk + </Button> + <Button className={classes.button} variant="contained" size="small"> + <Save className={classNames(classes.leftIcon, classes.iconSmall)} /> + Save + </Button> + </Grid> + </Grid> + </Fragment> + ); + } +} + +StandardButtons.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(StandardButtons); diff --git a/front/odiparpack/app/containers/Forms/demos/SwitchesInput.js b/front/odiparpack/app/containers/Forms/demos/SwitchesInput.js new file mode 100644 index 0000000..3c10803 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/SwitchesInput.js @@ -0,0 +1,210 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { green } from '@material-ui/core/colors'; + +import { + Switch, + Typography, + Grid, + FormControl, + FormLabel, + FormControlLabel, + FormGroup, + FormHelperText, +} from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + field: { + margin: `${theme.spacing(3)}px 5px`, + }, + formControl: { + margin: theme.spacing(3), + }, + group: { + margin: `${theme.spacing(1)}px 0`, + }, + switchBase: { + color: green[50], + '&$checked': { + color: green[500], + '& + $bar': { + backgroundColor: green[500], + }, + }, + }, + bar: {}, + checked: {}, + size: { + width: 40, + height: 40, + }, + sizeIcon: { + fontSize: 20, + }, +}); + +class RadioButton extends PureComponent { + state = { + checkedA: true, + checkedB: true, + checkedF: true, + gilad: true, + jason: false, + antoine: true, + }; + + handleChange = name => event => { + this.setState({ [name]: event.target.checked }); + }; + + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="space-around" + direction="row" + spacing={3} + > + <Grid + item + md={3} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Basic usage</Typography> + <div> + <Switch + checked={this.state.checkedA} + onChange={this.handleChange('checkedA')} + value="checkedA" + /> + <Switch + checked={this.state.checkedB} + onChange={this.handleChange('checkedB')} + value="checkedB" + color="primary" + /> + <Switch value="checkedC" /> + <Switch disabled value="checkedD" /> + <Switch disabled checked value="checkedE" /> + <Switch defaultChecked value="checkedF" color="default" /> + </div> + </Grid> + <Grid + item + md={4} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Switch with label</Typography> + <Typography className={classes.divider}>Switch can also be used with a label description thanks to the FormControlLabel component.</Typography> + <div> + <FormGroup row> + <FormControlLabel + control={( + <Switch + checked={this.state.checkedA} + onChange={this.handleChange('checkedA')} + value="checkedA" + /> + )} + label="Secondary" + /> + <FormControlLabel + control={( + <Switch + checked={this.state.checkedB} + onChange={this.handleChange('checkedB')} + value="checkedB" + color="primary" + /> + )} + label="Primary" + /> + <FormControlLabel control={<Switch value="checkedC" />} label="Uncontrolled" /> + <FormControlLabel disabled control={<Switch value="checkedD" />} label="Disabled" /> + <FormControlLabel disabled control={<Switch checked value="checkedE" />} label="Disabled" /> + <FormControlLabel + control={( + <Switch + checked={this.state.checkedF} + onChange={this.handleChange('checkedF')} + value="checkedF" + classes={{ + switchBase: classes.switchBase, + checked: classes.checked, + track: classes.bar, + }} + /> + )} + label="Custom color" + /> + </FormGroup> + </div> + </Grid> + <Grid + item + md={5} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Switch in Form Group</Typography> + <Typography className={classes.divider}>FormGroup is a helpful wrapper used to group selection controls components that provides an easier API. However, we encourage you to use a Checkbox instead.</Typography> + <div> + <FormControl component="fieldset"> + <FormLabel component="legend">Assign responsibility</FormLabel> + <FormGroup> + <FormControlLabel + control={( + <Switch + checked={this.state.gilad} + onChange={this.handleChange('gilad')} + value="gilad" + /> + )} + label="Gilad Gray" + /> + <FormControlLabel + control={( + <Switch + checked={this.state.jason} + onChange={this.handleChange('jason')} + value="jason" + /> + )} + label="Jason Killian" + /> + <FormControlLabel + control={( + <Switch + checked={this.state.antoine} + onChange={this.handleChange('antoine')} + value="antoine" + /> + )} + label="Antoine Llorca" + /> + </FormGroup> + <FormHelperText>Be careful</FormHelperText> + </FormControl> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +RadioButton.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(RadioButton); diff --git a/front/odiparpack/app/containers/Forms/demos/TagSuggestions.js b/front/odiparpack/app/containers/Forms/demos/TagSuggestions.js new file mode 100644 index 0000000..1648fc3 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/TagSuggestions.js @@ -0,0 +1,249 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import keycode from 'keycode'; +import Downshift from 'downshift'; +import { withStyles } from '@material-ui/core/styles'; +import { TextField, Paper, MenuItem, Chip } from '@material-ui/core'; + +const suggestions = [ + { label: 'Afghanistan' }, + { label: 'Aland Islands' }, + { label: 'Albania' }, + { label: 'Algeria' }, + { label: 'American Samoa' }, + { label: 'Andorra' }, + { label: 'Angola' }, + { label: 'Anguilla' }, + { label: 'Antarctica' }, + { label: 'Antigua and Barbuda' }, + { label: 'Argentina' }, + { label: 'Armenia' }, + { label: 'Aruba' }, + { label: 'Australia' }, + { label: 'Austria' }, + { label: 'Azerbaijan' }, + { label: 'Bahamas' }, + { label: 'Bahrain' }, + { label: 'Bangladesh' }, + { label: 'Barbados' }, + { label: 'Belarus' }, + { label: 'Belgium' }, + { label: 'Belize' }, + { label: 'Benin' }, + { label: 'Bermuda' }, + { label: 'Bhutan' }, + { label: 'Bolivia, Plurinational State of' }, + { label: 'Bonaire, Sint Eustatius and Saba' }, + { label: 'Bosnia and Herzegovina' }, + { label: 'Botswana' }, + { label: 'Bouvet Island' }, + { label: 'Brazil' }, + { label: 'British Indian Ocean Territory' }, + { label: 'Brunei Darussalam' }, +]; + +function renderInput(inputProps) { + const { + InputProps, + classes, + ref, + ...other + } = inputProps; + + return ( + <TextField + InputProps={{ + inputRef: ref, + classes: { + root: classes.inputRoot, + }, + ...InputProps, + }} + {...other} + /> + ); +} + +function renderSuggestion({ + suggestion, + index, + itemProps, + highlightedIndex, + selectedItem +}) { + const isHighlighted = highlightedIndex === index; + const isSelected = (selectedItem || '').indexOf(suggestion.label) > -1; + + return ( + <MenuItem + {...itemProps} + key={suggestion.label} + selected={isHighlighted} + component="div" + style={{ + fontWeight: isSelected ? 500 : 400, + }} + > + {suggestion.label} + </MenuItem> + ); +} + +renderSuggestion.propTypes = { + highlightedIndex: PropTypes.number.isRequired, + index: PropTypes.number.isRequired, + itemProps: PropTypes.object.isRequired, + selectedItem: PropTypes.string.isRequired, + suggestion: PropTypes.shape({ label: PropTypes.string }).isRequired, +}; + +function getSuggestions(inputValue) { + let count = 0; + + return suggestions.filter(suggestion => { + const keep = (!inputValue || suggestion.label.toLowerCase().indexOf(inputValue.toLowerCase()) !== -1) + && count < 5; + + if (keep) { + count += 1; + } + + return keep; + }); +} + +class DownshiftMultiple extends React.Component { + state = { + inputValue: '', + selectedItem: [], + }; + + handleKeyDown = event => { + const { inputValue, selectedItem } = this.state; + if (selectedItem.length && !inputValue.length && keycode(event) === 'backspace') { + this.setState({ + selectedItem: selectedItem.slice(0, selectedItem.length - 1), + }); + } + }; + + handleInputChange = event => { + this.setState({ inputValue: event.target.value }); + }; + + handleChange = item => { + let { selectedItem } = this.state; + + if (selectedItem.indexOf(item) === -1) { + selectedItem = [...selectedItem, item]; + } + + this.setState({ + inputValue: '', + selectedItem, + }); + }; + + handleDelete = item => () => { + const selectedItem = [...this.state.selectedItem]; + selectedItem.splice(selectedItem.indexOf(item), 1); + + this.setState({ selectedItem }); + }; + + render() { + const { classes } = this.props; + const { inputValue, selectedItem } = this.state; + + return ( + <Downshift inputValue={inputValue} onChange={this.handleChange} selectedItem={selectedItem}> + {({ + getInputProps, + getItemProps, + isOpen, + inputValue: inputValue2, + selectedItem: selectedItem2, + highlightedIndex, + }) => ( + <div className={classes.container}> + {renderInput({ + fullWidth: true, + classes, + InputProps: getInputProps({ + startAdornment: selectedItem.map(item => ( + <Chip + key={item} + tabIndex={-1} + label={item} + className={classes.chip} + onDelete={this.handleDelete(item)} + /> + )), + onChange: this.handleInputChange, + onKeyDown: this.handleKeyDown, + placeholder: 'Select multiple countries', + id: 'integration-downshift-multiple', + }), + })} + {isOpen ? ( + <Paper className={classes.paper} square> + {getSuggestions(inputValue2).map((suggestion, index) => renderSuggestion({ + suggestion, + index, + itemProps: getItemProps({ item: suggestion.label }), + highlightedIndex, + selectedItem: selectedItem2, + }), + )} + </Paper> + ) : null} + </div> + )} + </Downshift> + ); + } +} + +DownshiftMultiple.propTypes = { + classes: PropTypes.object.isRequired, +}; + +const styles = theme => ({ + root: { + flexGrow: 1, + height: 100, + }, + container: { + flexGrow: 1, + position: 'relative', + }, + paper: { + position: 'absolute', + zIndex: 1, + marginTop: theme.spacing(1), + left: 0, + right: 0, + }, + chip: { + margin: `${theme.spacing(0.5)}px ${theme.spacing(0.25)}px`, + }, + inputRoot: { + flexWrap: 'wrap', + }, +}); + +function TagSuggestions(props) { + const { classes } = props; + + return ( + <div className={classes.root}> + <DownshiftMultiple classes={classes} /> + </div> + ); +} + +TagSuggestions.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(TagSuggestions); diff --git a/front/odiparpack/app/containers/Forms/demos/TextFields.js b/front/odiparpack/app/containers/Forms/demos/TextFields.js new file mode 100644 index 0000000..b408284 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/TextFields.js @@ -0,0 +1,124 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Typography, Grid, Input, InputLabel, FormControl, FormHelperText } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + input: { + margin: theme.spacing(3), + }, + container: { + display: 'flex', + flexWrap: 'wrap', + }, + formControl: { + margin: theme.spacing(3), + }, +}); + +class TextFields extends PureComponent { + state = { + name: 'Composed TextField', + }; + + handleChange = event => { + this.setState({ name: event.target.value }); + }; + + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={3} + > + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Textfield Components</Typography> + <Typography className={classes.divider}>TextField is composed of smaller components that you can leverage directly to significantly customize your form inputs.</Typography> + <div className={classes.container}> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="name-simple">Name</InputLabel> + <Input id="name-simple" value={this.state.name} onChange={this.handleChange} /> + </FormControl> + <FormControl className={classes.formControl} aria-describedby="name-helper-text"> + <InputLabel htmlFor="name-helper">Name</InputLabel> + <Input id="name-helper" value={this.state.name} onChange={this.handleChange} /> + <FormHelperText id="name-helper-text">Some important helper text</FormHelperText> + </FormControl> + <FormControl className={classes.formControl} disabled> + <InputLabel htmlFor="name-disabled">Name</InputLabel> + <Input id="name-disabled" value={this.state.name} onChange={this.handleChange} /> + <FormHelperText>Disabled</FormHelperText> + </FormControl> + <FormControl className={classes.formControl} error aria-describedby="name-error-text"> + <InputLabel htmlFor="name-error">Name</InputLabel> + <Input id="name-error" value={this.state.name} onChange={this.handleChange} /> + <FormHelperText id="name-error-text">Error</FormHelperText> + </FormControl> + </div> + </Grid> + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Input State</Typography> + <div className={classes.container}> + <Input + defaultValue="Hello world" + className={classes.input} + inputProps={{ + 'aria-label': 'Description', + }} + /> + <Input + placeholder="Placeholder" + className={classes.input} + inputProps={{ + 'aria-label': 'Description', + }} + /> + <Input + value="Disabled" + className={classes.input} + disabled + inputProps={{ + 'aria-label': 'Description', + }} + /> + <Input + defaultValue="Error" + className={classes.input} + error + inputProps={{ + 'aria-label': 'Description', + }} + /> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +TextFields.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(TextFields); diff --git a/front/odiparpack/app/containers/Forms/demos/TextFieldsLayout.js b/front/odiparpack/app/containers/Forms/demos/TextFieldsLayout.js new file mode 100644 index 0000000..27809f4 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/TextFieldsLayout.js @@ -0,0 +1,174 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles, MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'; +import { purple, green } from '@material-ui/core/colors'; + +import { Typography, Grid, Input, InputLabel, TextField, FormControl } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + lineHeight: '24px' + }, + input: { + margin: theme.spacing(3), + }, + margin: { + margin: theme.spacing(1), + }, + container: { + display: 'flex', + flexWrap: 'wrap', + }, + textField: { + margin: `${theme.spacing(2)}px ${theme.spacing(1)}px`, + width: 200, + }, + cssLabel: { + '&$cssFocused': { + color: purple[500], + }, + }, + cssFocused: {}, + cssUnderline: { + '&:after': { + backgroundColor: purple[500], + }, + }, + bootstrapRoot: { + padding: 0, + 'label + &': { + marginTop: theme.spacing(3), + }, + }, + bootstrapInput: { + borderRadius: 4, + backgroundColor: theme.palette.common.white, + border: '1px solid #ced4da', + fontSize: 16, + padding: '10px 12px', + width: 'calc(100% - 24px)', + transition: theme.transitions.create(['border-color', 'box-shadow']), + '&:focus': { + borderColor: '#80bdff', + boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)', + }, + }, + bootstrapFormLabel: { + fontSize: 18, + }, +}); + +const theme = createMuiTheme({ + palette: { + primary: green, + }, +}); + +class TextFields extends PureComponent { + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={3} + > + <Grid + item + md={12} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Layout</Typography> + <Typography className={classes.divider}>TextField, FormControl allow the specification of margin to alter the vertical spacing of inputs. Using none (default) will not apply margins to the FormControl, whereas dense and normal will as well as alter other styles to meet the specification.</Typography> + <div className={classes.container}> + <TextField + label="None" + id="margin-none" + defaultValue="Default Value" + className={classes.textField} + helperText="Some important text" + /> + <TextField + label="Dense" + id="margin-dense" + defaultValue="Default Value" + className={classes.textField} + helperText="Some important text" + margin="dense" + /> + <TextField + label="Normal" + id="margin-normal" + defaultValue="Default Value" + className={classes.textField} + helperText="Some important text" + margin="normal" + /> + </div> + </Grid> + <Grid + item + md={12} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Customized Designs</Typography> + <Typography className={classes.divider}>Here is an example of how you can change the main color of an Input.</Typography> + <div className={classes.container}> + <FormControl className={classes.margin}> + <InputLabel + htmlFor="custom-css-input" + > + Custom CSS + </InputLabel> + <Input + classes={{ + underline: classes.cssUnderline, + }} + id="custom-css-input" + /> + </FormControl> + <MuiThemeProvider theme={theme}> + <TextField + className={classes.margin} + label="MuiThemeProvider" + id="mui-theme-provider-input" + /> + </MuiThemeProvider> + <TextField + className={classes.divider} + defaultValue="react-bootstrap" + label="Bootstrap" + id="bootstrap-input" + InputProps={{ + disableUnderline: true, + classes: { + root: classes.bootstrapRoot, + input: classes.bootstrapInput, + }, + }} + InputLabelProps={{ + shrink: true, + className: classes.bootstrapFormLabel, + }} + /> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +TextFields.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(TextFields); diff --git a/front/odiparpack/app/containers/Forms/demos/TimeInput.js b/front/odiparpack/app/containers/Forms/demos/TimeInput.js new file mode 100644 index 0000000..d8ed35b --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/TimeInput.js @@ -0,0 +1,124 @@ +import React, { Fragment, PureComponent } from 'react'; +import { TimePicker, KeyboardDatePicker, MuiPickersUtilsProvider } from '@material-ui/pickers'; +import MomentUtils from '@date-io/moment'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Typography, Grid, InputAdornment, Icon, IconButton } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + picker: { + margin: `${theme.spacing(3)}px 5px`, + } +}); + +class TimeInput extends PureComponent { + state = { + selectedDate: new Date(), + } + + handleDateChange = (date) => { + this.setState({ selectedDate: date }); + } + + render() { + const { selectedDate } = this.state; + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="space-around" + direction="row" + spacing={3} + > + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Basic usage</Typography> + <Typography className={classes.divider}> + A time picker should adjusts to a user’s preferred time setting, i.e. the 12-hour or 24-hour format. + </Typography> + <div className={classes.picker}> + <MuiPickersUtilsProvider utils={MomentUtils}> + <div className="picker"> + <TimePicker + label="12 hours" + value={selectedDate} + onChange={this.handleDateChange} + /> + </div> + </MuiPickersUtilsProvider> + </div> + + <div className={classes.picker}> + <MuiPickersUtilsProvider utils={MomentUtils}> + <TimePicker + clearable + ampm={false} + label="24 hours" + value={selectedDate} + onChange={this.handleDateChange} + /> + </MuiPickersUtilsProvider> + </div> + </Grid> + <Grid + item + md={6} + className={classes.demo} + > + <Typography variant="button" className={classes.divider}>Keyboard Input</Typography> + <div className={classes.picker}> + <MuiPickersUtilsProvider utils={MomentUtils}> + <KeyboardDatePicker + label="Masked timepicker" + mask={[/\d/, /\d/, ':', /\d/, /\d/, ' ', /a|p/i, 'M']} + placeholder="08:00 AM" + value={selectedDate} + onChange={this.handleDateChange} + /> + </MuiPickersUtilsProvider> + </div> + <Typography variant="button" className={classes.divider}>Custom Icon</Typography> + <div className={classes.picker}> + <MuiPickersUtilsProvider utils={MomentUtils}> + <TimePicker + label="Masked timepicker" + mask={[/\d/, /\d/, ':', /\d/, /\d/, ' ', /a|p/i, 'M']} + placeholder="08:00 AM" + value={selectedDate} + onChange={this.handleDateChange} + InputProps={{ + endAdornment: ( + <InputAdornment position="end"> + <IconButton> + <Icon>access_time</Icon> + </IconButton> + </InputAdornment> + ), + }} + /> + </MuiPickersUtilsProvider> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +TimeInput.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(TimeInput); diff --git a/front/odiparpack/app/containers/Forms/demos/UploadInputAll.js b/front/odiparpack/app/containers/Forms/demos/UploadInputAll.js new file mode 100644 index 0000000..22ae742 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/UploadInputAll.js @@ -0,0 +1,31 @@ +import React, { Fragment } from 'react'; +import { MaterialDropZone } from 'ba-components'; + +class UploadInputAll extends React.Component { + constructor(props) { + super(props); + + this.state = { + files: [], + }; + } + + render() { + const { files } = this.state; + return ( + <Fragment> + <div> + <MaterialDropZone + files={files} + showPreviews + maxSize={5000000} + filesLimit={5} + text="Drag and drop file(s) here or click" + /> + </div> + </Fragment> + ); + } +} + +export default UploadInputAll; diff --git a/front/odiparpack/app/containers/Forms/demos/UploadInputBtn.js b/front/odiparpack/app/containers/Forms/demos/UploadInputBtn.js new file mode 100644 index 0000000..959ee85 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/UploadInputBtn.js @@ -0,0 +1,32 @@ +import React, { Fragment } from 'react'; +import { MaterialDropZone } from 'ba-components'; + +class UploadInputBtn extends React.Component { + constructor(props) { + super(props); + + this.state = { + files: [], + }; + } + + render() { + const { files } = this.state; + return ( + <Fragment> + <div> + <MaterialDropZone + files={files} + showPreviews + maxSize={5000000} + filesLimit={5} + text="Drag and drop file(s) here or click button bellow" + showButton + /> + </div> + </Fragment> + ); + } +} + +export default UploadInputBtn; diff --git a/front/odiparpack/app/containers/Forms/demos/UploadInputImg.js b/front/odiparpack/app/containers/Forms/demos/UploadInputImg.js new file mode 100644 index 0000000..3600f37 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/UploadInputImg.js @@ -0,0 +1,32 @@ +import React, { Fragment } from 'react'; +import { MaterialDropZone } from 'ba-components'; + +class UploadInputImg extends React.Component { + constructor(props) { + super(props); + + this.state = { + files: [], + }; + } + + render() { + const { files } = this.state; + return ( + <Fragment> + <div> + <MaterialDropZone + acceptedFiles={['image/jpeg', 'image/png', 'image/bmp']} + files={files} + showPreviews + maxSize={5000000} + filesLimit={5} + text="Drag and drop image(s) here or click" + /> + </div> + </Fragment> + ); + } +} + +export default UploadInputImg; diff --git a/front/odiparpack/app/containers/Forms/demos/Wysiwyg.js b/front/odiparpack/app/containers/Forms/demos/Wysiwyg.js new file mode 100644 index 0000000..61c64a8 --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/Wysiwyg.js @@ -0,0 +1,91 @@ +import React, { Fragment, PureComponent } from 'react'; +import { convertFromRaw, EditorState, convertToRaw } from 'draft-js'; +import { Editor } from 'react-draft-wysiwyg'; +import draftToHtml from 'draftjs-to-html'; +import draftToMarkdown from 'draftjs-to-markdown'; +import EditorStyle from 'ba-styles/TextEditor.scss'; +import 'ba-styles/vendors/react-draft-wysiwyg/react-draft-wysiwyg.css'; + +import { Grid, Typography } from '@material-ui/core'; + +const content = { + blocks: [{ + key: '637gr', + text: 'Lorem ipsum dolor sit amet 😀', + type: 'unstyled', + depth: 0, + inlineStyleRanges: [], + entityRanges: [], + data: {} + }], + entityMap: {} +}; + +class Wysiwyg extends PureComponent { + constructor(props) { + super(props); + const contentBlock = convertFromRaw(content); + if (contentBlock) { + const editorState = EditorState.createWithContent(contentBlock); + this.state = { + editorState, + }; + } + } + + onEditorStateChange = editorState => { + this.setState({ + editorState, + }); + }; + + render() { + const { editorState } = this.state; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="space-around" + direction="row" + spacing={3} + > + <Grid item xs={12}> + <Editor + editorState={editorState} + editorClassName={EditorStyle.TextEditor} + toolbarClassName={EditorStyle.ToolbarEditor} + onEditorStateChange={this.onEditorStateChange} + /> + </Grid> + <Grid item md={4} xs={12}> + <Typography variant="button">JSON Result :</Typography> + <textarea + className={EditorStyle.textPreview} + disabled + value={JSON.stringify(editorState, null, 4)} + /> + </Grid> + <Grid item md={4} xs={12}> + <Typography variant="button">HTML Result :</Typography> + <textarea + className={EditorStyle.textPreview} + disabled + value={draftToHtml(convertToRaw(editorState.getCurrentContent()))} + /> + </Grid> + <Grid item md={4} xs={12}> + <Typography variant="button">Markdown Result :</Typography> + <textarea + className={EditorStyle.textPreview} + disabled + value={editorState && draftToMarkdown(convertToRaw(editorState.getCurrentContent()))} + /> + </Grid> + </Grid> + </Fragment> + ); + } +} + +export default Wysiwyg; diff --git a/front/odiparpack/app/containers/Forms/demos/index.js b/front/odiparpack/app/containers/Forms/demos/index.js new file mode 100644 index 0000000..8ae709e --- /dev/null +++ b/front/odiparpack/app/containers/Forms/demos/index.js @@ -0,0 +1,44 @@ +// Redux Form +export ReduxFormDemo from './ReduxFormDemo'; +// Date Time Picker +export DateInput from './DateInput'; +export TimeInput from './TimeInput'; +export DateTimeInput from './DateTimeInput'; +// Checkbox and Radio +export Checkboxes from './Checkboxes'; +export RadioButton from './RadioButton'; +// Switches +export SwitchesInput from './SwitchesInput'; +// Selectbox +export SimpleSelectbox from './SimpleSelectbox'; +export NativeSelectbox from './NativeSelectbox'; +export MultipleSelectbox from './MultipleSelectbox'; +export ControlledSelectbox from './ControlledSelectbox'; +// Ratting +export RatingNormal from './RatingNormal'; +export RatingCustom from './RatingCustom'; +// Slide Range +export SliderInput from './SliderInput'; +export RangeInput from './RangeInput'; +// Buttons +export StandardButtons from './StandardButtons'; +export FloatingButtons from './FloatingButtons'; +export CustomButtons from './CustomButtons'; +export ComplexButtons from './ComplexButtons'; +// Textfield +export TextFields from './TextFields'; +export TextFieldsLayout from './TextFieldsLayout'; +export InputAdornments from './InputAdornments'; +export FormattedInputs from './FormattedInputs'; +// Autocomplete +export AutoSuggest from './AutoSuggest'; +export TagSuggestions from './TagSuggestions'; +export SelectSuggestions from './SelectSuggestions'; +export SelectSuggestionTags from './SelectSuggestionTags'; +export HighlightSuggest from './HighlightSuggest'; +// Text Editor +export Wysiwyg from './Wysiwyg'; +// Uploader +export UploadInputAll from './UploadInputAll'; +export UploadInputImg from './UploadInputImg'; +export UploadInputBtn from './UploadInputBtn'; diff --git a/front/odiparpack/app/containers/LanguageProvider/actions.js b/front/odiparpack/app/containers/LanguageProvider/actions.js new file mode 100644 index 0000000..129dda4 --- /dev/null +++ b/front/odiparpack/app/containers/LanguageProvider/actions.js @@ -0,0 +1,14 @@ +/* + * + * LanguageProvider actions + * + */ + +import { CHANGE_LOCALE } from './constants'; + +export default function changeLocale(languageLocale) { + return { + type: CHANGE_LOCALE, + locale: languageLocale, + }; +} diff --git a/front/odiparpack/app/containers/LanguageProvider/constants.js b/front/odiparpack/app/containers/LanguageProvider/constants.js new file mode 100644 index 0000000..365ac6d --- /dev/null +++ b/front/odiparpack/app/containers/LanguageProvider/constants.js @@ -0,0 +1,8 @@ +/* + * + * LanguageProvider constants + * + */ + +const CHANGE_LOCALE = 'CHANGE_LOCALE'; +export default CHANGE_LOCALE; diff --git a/front/odiparpack/app/containers/LanguageProvider/index.js b/front/odiparpack/app/containers/LanguageProvider/index.js new file mode 100644 index 0000000..666240d --- /dev/null +++ b/front/odiparpack/app/containers/LanguageProvider/index.js @@ -0,0 +1,51 @@ +/* + * + * LanguageProvider + * + * this component connects the redux state language locale to the + * IntlProvider component and i18n messages (loaded from `app/translations`) + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { IntlProvider } from 'react-intl'; + +import { makeSelectLocale } from './selectors'; + +export class LanguageProvider extends React.PureComponent { + // eslint-disable-line react/prefer-stateless-function + render() { + return ( + <IntlProvider + locale={this.props.locale} + key={this.props.locale} + messages={this.props.messages[this.props.locale]} + > + {React.Children.only(this.props.children)} + </IntlProvider> + ); + } +} + +LanguageProvider.propTypes = { + locale: PropTypes.string.isRequired, + messages: PropTypes.object.isRequired, + children: PropTypes.element.isRequired, +}; + +const mapStateToProps = createSelector(makeSelectLocale(), locale => ({ + locale, +})); + +function mapDispatchToProps(dispatch) { + return { + dispatch, + }; +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(LanguageProvider); diff --git a/front/odiparpack/app/containers/LanguageProvider/reducer.js b/front/odiparpack/app/containers/LanguageProvider/reducer.js new file mode 100644 index 0000000..089c426 --- /dev/null +++ b/front/odiparpack/app/containers/LanguageProvider/reducer.js @@ -0,0 +1,25 @@ +/* + * + * LanguageProvider reducer + * + */ + +import { fromJS } from 'immutable'; + +import CHANGE_LOCALE from './constants'; +import { DEFAULT_LOCALE } from '../../i18n'; // eslint-disable-line + +export const initialState = fromJS({ + locale: DEFAULT_LOCALE, +}); + +function languageProviderReducer(state = initialState, action) { + switch (action.type) { + case CHANGE_LOCALE: + return state.set('locale', action.locale); + default: + return state; + } +} + +export default languageProviderReducer; diff --git a/front/odiparpack/app/containers/LanguageProvider/selectors.js b/front/odiparpack/app/containers/LanguageProvider/selectors.js new file mode 100644 index 0000000..3ef15a2 --- /dev/null +++ b/front/odiparpack/app/containers/LanguageProvider/selectors.js @@ -0,0 +1,16 @@ +import { createSelector } from 'reselect'; +import { initialState } from './reducer'; + +/** + * Direct selector to the languageToggle state domain + */ +const selectLanguage = state => state.get('language', initialState); + +/** + * Select the language locale + */ + +const makeSelectLocale = () => + createSelector(selectLanguage, languageState => languageState.get('locale')); + +export { selectLanguage, makeSelectLocale }; diff --git a/front/odiparpack/app/containers/Layouts/AppLayout.js b/front/odiparpack/app/containers/Layouts/AppLayout.js new file mode 100644 index 0000000..c0e96d0 --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/AppLayout.js @@ -0,0 +1,45 @@ +import React, { Component } from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { SidebarLayout, SidebarLayoutRight, FullHeader } from './demos'; + +class AppLayout extends Component { + render() { + const title = brand.name + ' - Layout'; + const description = brand.desc; + const docSrc = 'containers/Layouts/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="App Layout with Sidebars" desc="The Drawer slides in from the side. It is a common pattern found in Google apps and follows the keylines and metrics for lists."> + <div> + <SidebarLayout /> + <SourceReader componentName={docSrc + 'SidebarLayout.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Right Sidebar Mode" desc="The Drawer slides in from the side. It is a common pattern found in Google apps and follows the keylines and metrics for lists."> + <div> + <SidebarLayoutRight /> + <SourceReader componentName={docSrc + 'SidebarLayoutRight.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Full Header Mode" desc="Apps focused on productivity that require balance across the screen."> + <div> + <FullHeader /> + <SourceReader componentName={docSrc + 'FullHeader.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default AppLayout; diff --git a/front/odiparpack/app/containers/Layouts/Grid.js b/front/odiparpack/app/containers/Layouts/Grid.js new file mode 100644 index 0000000..6a1057d --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/Grid.js @@ -0,0 +1,96 @@ +import React, { Component } from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Grid } from '@material-ui/core'; +import { + GridLayout, + FullWidth, + Centered, + Interactive, + AutoLayout, + Limitation +} from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class GridPage extends Component { + render() { + const { classes } = this.props; + const title = brand.name + ' - Layout'; + const description = brand.desc; + const docSrc = 'containers/Layouts/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Grid Spacing" desc="The responsive grid focuses on consistent spacing widths, rather than column width. Material design margins and columns follow an 8dp square baseline grid. Spacing can be 8, 16, 24, or 40dp wide."> + <div> + <GridLayout /> + <SourceReader componentName={docSrc + 'GridLayout.js'} /> + </div> + </PapperBlock> + + <PapperBlock title="Full-width" desc="Full-width grids: use fluid columns and breakpoints to determine when a layout needs to change."> + <div> + <FullWidth /> + <SourceReader componentName={docSrc + 'FullWidth.js'} /> + </div> + </PapperBlock> + + <PapperBlock title="Centered Grid" desc="Centered grids: use fixed columns and re-flow the layout when all columns (plus a defined margin) no longer fit on the screen."> + <div> + <Centered /> + <SourceReader componentName={docSrc + 'Centered.js'} /> + </div> + </PapperBlock> + + <PapperBlock title="Interactive" desc="Below is an interactive demo that lets you explore the visual results of the different settings:"> + <div> + <Interactive /> + <SourceReader componentName={docSrc + 'Interactive.js'} /> + </div> + </PapperBlock> + + <div className={classes.root}> + <Grid container spacing={3}> + <Grid item md={6} xs={12}> + <PapperBlock title="Auto Layout" desc="The Auto-layout makes the items equitably share the available space. That also means you can set the width of one item and the others will automatically resize around it."> + <div> + <AutoLayout /> + <SourceReader componentName={docSrc + 'AutoLayout.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6} xs={12}> + <PapperBlock title="Limitations" overflowX desc="There is one limitation with the negative margin we use to implement the spacing between items."> + <div> + <Limitation /> + <SourceReader componentName={docSrc + 'Limitation.js'} /> + </div> + </PapperBlock> + </Grid> + </Grid> + </div> + </div> + ); + } +} + +GridPage.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(GridPage); diff --git a/front/odiparpack/app/containers/Layouts/Responsive.js b/front/odiparpack/app/containers/Layouts/Responsive.js new file mode 100644 index 0000000..b5d6db2 --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/Responsive.js @@ -0,0 +1,54 @@ +import React, { Component } from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import Markdown from 'react-markdown'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Breakpoint, BreakpointGrid, MediaQueries, WIthWIdth } from './demos'; +import breakpointsTable from './demos/breakpoint.md'; + +class Responsive extends Component { + render() { + const title = brand.name + ' - Layout'; + const description = brand.desc; + const docSrc = 'containers/Layouts/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Media Queries" desc="CSS media queries is the idiomatic approach to make your UI responsive.. We provide some CSS-in-JS helpers to do so. In the following demo, we change the background color (red, blue & green) based on the screen width."> + <div> + <MediaQueries /> + <SourceReader componentName={docSrc + 'MediaQueries.js'} /> + </div> + </PapperBlock> + <PapperBlock title="With Width" desc="Sometimes, using CSS isn't enough. You might want to change the React rendering tree based on the breakpoint value, in JavaScript. We provide a withWidth() higher-order component for this use case. In the following demo, we change the rendered DOM element (em, u, del & span) based on the screen width."> + <div> + <WIthWIdth /> + <SourceReader componentName={docSrc + 'WIthWIdth.js'} /> + </div> + </PapperBlock> + <PapperBlock overflowX title="Hidden" desc="Hidden works with a range of breakpoints e.g. xsUp or mdDown, or one or more breakpoints e.g. only='sm' or only={['md', 'xl']}. Ranges and individual breakpoints can be used simultaneously to achieve very customized behavior. The ranges are inclusive of the specified breakpoints."> + <div> + <Markdown source={breakpointsTable} /> + <Breakpoint /> + <SourceReader componentName={docSrc + 'Breakpoint.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Integration with Grid" desc="It is quite common to alter Grid at different responsive breakpoints, and in many cases, you want to hide some of those elements."> + <div> + <BreakpointGrid /> + <SourceReader componentName={docSrc + 'BreakpointGrid.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default Responsive; diff --git a/front/odiparpack/app/containers/Layouts/demos/AutoLayout.js b/front/odiparpack/app/containers/Layouts/demos/AutoLayout.js new file mode 100644 index 0000000..864d17e --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/AutoLayout.js @@ -0,0 +1,85 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Typography, Paper, Divider, Grid } from '@material-ui/core'; + +const styles = theme => ({ + container: { + display: 'grid', + gridTemplateColumns: 'repeat(12, 1fr)', + gridGap: `${theme.spacing(3)}px`, + }, + paper: { + padding: theme.spacing(1), + textAlign: 'center', + color: theme.palette.text.secondary, + whiteSpace: 'nowrap', + marginBottom: theme.spacing(1), + backgroundColor: theme.palette.secondary.light, + }, + divider: { + margin: `${theme.spacing(2)}px 0`, + }, +}); + +function CSSGrid(props) { + const { classes } = props; + + return ( + <div> + <Typography variant="subtitle1" gutterBottom> + Material-UI Grid: + </Typography> + <Grid container spacing={3}> + <Grid item xs={3}> + <Paper className={classes.paper}>xs=3</Paper> + </Grid> + <Grid item xs={3}> + <Paper className={classes.paper}>xs=3</Paper> + </Grid> + <Grid item xs={3}> + <Paper className={classes.paper}>xs=3</Paper> + </Grid> + <Grid item xs={3}> + <Paper className={classes.paper}>xs=3</Paper> + </Grid> + <Grid item xs={8}> + <Paper className={classes.paper}>xs=8</Paper> + </Grid> + <Grid item xs={4}> + <Paper className={classes.paper}>xs=4</Paper> + </Grid> + </Grid> + <Divider className={classes.divider} /> + <Typography variant="subtitle1" gutterBottom> + CSS Grid Layout: + </Typography> + <div className={classes.container}> + <div style={{ gridColumnEnd: 'span 3' }}> + <Paper className={classes.paper}>xs=3</Paper> + </div> + <div style={{ gridColumnEnd: 'span 3' }}> + <Paper className={classes.paper}>xs=3</Paper> + </div> + <div style={{ gridColumnEnd: 'span 3' }}> + <Paper className={classes.paper}>xs=3</Paper> + </div> + <div style={{ gridColumnEnd: 'span 3' }}> + <Paper className={classes.paper}>xs=3</Paper> + </div> + <div style={{ gridColumnEnd: 'span 8' }}> + <Paper className={classes.paper}>xs=8</Paper> + </div> + <div style={{ gridColumnEnd: 'span 4' }}> + <Paper className={classes.paper}>xs=4</Paper> + </div> + </div> + </div> + ); +} + +CSSGrid.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CSSGrid); diff --git a/front/odiparpack/app/containers/Layouts/demos/Breakpoint.js b/front/odiparpack/app/containers/Layouts/demos/Breakpoint.js new file mode 100644 index 0000000..be557b4 --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/Breakpoint.js @@ -0,0 +1,115 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import compose from 'recompose/compose'; +import { withStyles } from '@material-ui/core/styles'; +import { Paper, Hidden, Divider, withWidth, Typography } from '@material-ui/core'; + +const styles = theme => ({ + root: { + flexGrow: 1, + }, + container: { + display: 'flex', + }, + paper: { + padding: theme.spacing(2), + textAlign: 'center', + color: theme.palette.text.secondary, + flex: '1 0 auto', + margin: theme.spacing(1), + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + } +}); + +function Breakpoint(props) { + const { classes } = props; + + return ( + <div className={classes.root}> + {/* Breakpoin up block */} + <Typography variant="h5">Breakpoint up</Typography> + <Typography gutterBottom noWrap> + Using any breakpoint up property, the given children will be hidden at or above the breakpoint. + </Typography> + <div className={classes.container}> + <Hidden xsUp> + <Paper className={classes.paper}>xsUp</Paper> + </Hidden> + <Hidden smUp> + <Paper className={classes.paper}>smUp</Paper> + </Hidden> + <Hidden mdUp> + <Paper className={classes.paper}>mdUp</Paper> + </Hidden> + <Hidden lgUp> + <Paper className={classes.paper}>lgUp</Paper> + </Hidden> + <Hidden xlUp> + <Paper className={classes.paper}>xlUp</Paper> + </Hidden> + </div> + <Typography variant="caption" gutterBottom align="center"> +Current width: + {props.width} + </Typography> + <Divider className={classes.divider} /> + {/* Breakpoin down block */} + <Typography variant="h5">Breakpoint down</Typography> + <Typography gutterBottom noWrap> + Using any breakpoint down property, the given children will be hidden at or below the breakpoint. + </Typography> + <div className={classes.container}> + <Hidden xsDown> + <Paper className={classes.paper}>xsDown</Paper> + </Hidden> + <Hidden smDown> + <Paper className={classes.paper}>smDown</Paper> + </Hidden> + <Hidden mdDown> + <Paper className={classes.paper}>mdDown</Paper> + </Hidden> + <Hidden lgDown> + <Paper className={classes.paper}>lgDown</Paper> + </Hidden> + <Hidden xlDown> + <Paper className={classes.paper}>xlDown</Paper> + </Hidden> + </div> + <Typography variant="caption" gutterBottom align="center"> +Current width: + {props.width} + </Typography> + <Divider className={classes.divider} /> + {/* Breakpoin only block */} + <Typography variant="h5">Breakpoint only</Typography> + <Typography gutterBottom noWrap> + Using the breakpoint only property, the given children will be hidden at the specified breakpoint(s). + </Typography> + <div className={classes.container}> + <Hidden only="lg"> + <Paper className={classes.paper}>Hidden on lg</Paper> + </Hidden> + <Hidden only="sm"> + <Paper className={classes.paper}>Hidden on sm</Paper> + </Hidden> + <Hidden only={['sm', 'lg']}> + <Paper className={classes.paper}>Hidden on sm and lg</Paper> + </Hidden> + </div> + <Typography variant="caption" gutterBottom align="center"> +Current width: + {props.width} + </Typography> + </div> + ); +} + +Breakpoint.propTypes = { + classes: PropTypes.object.isRequired, + width: PropTypes.string.isRequired, +}; + +export default compose(withStyles(styles), withWidth())(Breakpoint); diff --git a/front/odiparpack/app/containers/Layouts/demos/BreakpointGrid.js b/front/odiparpack/app/containers/Layouts/demos/BreakpointGrid.js new file mode 100644 index 0000000..cd94ef9 --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/BreakpointGrid.js @@ -0,0 +1,60 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import compose from 'recompose/compose'; +import { withStyles } from '@material-ui/core/styles'; +import { Paper, Grid, withWidth, Typography } from '@material-ui/core'; + +const styles = theme => ({ + root: { + flexGrow: 1, + }, + container: { + display: 'flex', + }, + paper: { + padding: theme.spacing(2), + textAlign: 'center', + color: theme.palette.text.secondary, + flex: '1 0 auto', + margin: theme.spacing(1), + }, +}); + +function GridIntegration(props) { + const { classes } = props; + + return ( + <div className={classes.root}> + <div className={classes.container}> + <Grid container spacing={3}> + <Grid item xs hidden={{ xsUp: true }}> + <Paper className={classes.paper}>xsUp</Paper> + </Grid> + <Grid item xs hidden={{ smUp: true }}> + <Paper className={classes.paper}>smUp</Paper> + </Grid> + <Grid item xs hidden={{ mdUp: true }}> + <Paper className={classes.paper}>mdUp</Paper> + </Grid> + <Grid item xs hidden={{ lgUp: true }}> + <Paper className={classes.paper}>lgUp</Paper> + </Grid> + <Grid item xs hidden={{ xlUp: true }}> + <Paper className={classes.paper}>xlUp</Paper> + </Grid> + </Grid> + </div> + <Typography variant="caption" align="center"> +Current width: + {props.width} + </Typography> + </div> + ); +} + +GridIntegration.propTypes = { + classes: PropTypes.object.isRequired, + width: PropTypes.string.isRequired, +}; + +export default compose(withStyles(styles), withWidth())(GridIntegration); diff --git a/front/odiparpack/app/containers/Layouts/demos/Centered.js b/front/odiparpack/app/containers/Layouts/demos/Centered.js new file mode 100644 index 0000000..cdd1c10 --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/Centered.js @@ -0,0 +1,54 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Paper, Grid } from '@material-ui/core'; + +const styles = theme => ({ + root: { + flexGrow: 1, + }, + paper: { + padding: theme.spacing(2), + textAlign: 'center', + color: theme.palette.text.secondary, + backgroundColor: theme.palette.secondary.light, + }, +}); + +function CenteredGrid(props) { + const { classes } = props; + + return ( + <div className={classes.root}> + <Grid container spacing={3}> + <Grid item xs={12}> + <Paper className={classes.paper}>xs=12</Paper> + </Grid> + <Grid item xs={6}> + <Paper className={classes.paper}>xs=6</Paper> + </Grid> + <Grid item xs={6}> + <Paper className={classes.paper}>xs=6</Paper> + </Grid> + <Grid item xs={3}> + <Paper className={classes.paper}>xs=3</Paper> + </Grid> + <Grid item xs={3}> + <Paper className={classes.paper}>xs=3</Paper> + </Grid> + <Grid item xs={3}> + <Paper className={classes.paper}>xs=3</Paper> + </Grid> + <Grid item xs={3}> + <Paper className={classes.paper}>xs=3</Paper> + </Grid> + </Grid> + </div> + ); +} + +CenteredGrid.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CenteredGrid); diff --git a/front/odiparpack/app/containers/Layouts/demos/FullHeader.js b/front/odiparpack/app/containers/Layouts/demos/FullHeader.js new file mode 100644 index 0000000..d5ef402 --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/FullHeader.js @@ -0,0 +1,153 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import MenuIcon from '@material-ui/icons/Menu'; +import { Drawer, AppBar, Toolbar, List, Typography, Divider, IconButton, Hidden } from '@material-ui/core'; +import { mailFolderListItems, otherMailFolderListItems } from './menuData'; + + +const drawerWidth = 240; + +const styles = theme => ({ + root: { + flexGrow: 1, + height: 430, + zIndex: 1, + overflow: 'hidden', + position: 'relative', + display: 'flex', + }, + appBar: { + zIndex: theme.zIndex.drawer + 1, + }, + menuButton: { + marginLeft: 0, + marginRight: 36, + }, + drawerPaper: { + width: drawerWidth, + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + [theme.breakpoints.up('md')]: { + position: 'relative', + }, + }, + drawerPaperClose: { + overflowX: 'hidden', + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + width: 70, + [theme.breakpoints.up('sm')]: { + width: 70, + }, + }, + toolbar: { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + padding: '0 8px', + ...theme.mixins.toolbar, + }, + content: { + flexGrow: 1, + backgroundColor: theme.palette.background.default, + padding: theme.spacing(3), + }, +}); + +class FullHeader extends React.Component { + state = { + open: true, + }; + + componentDidMount() { + window.addEventListener('resize', this.resize.bind(this)); + this.resize(); + } + + resize() { + this.setState({ open: window.innerWidth >= 760 }); + } + + handleDrawerToggle = () => { + this.setState({ open: !this.state.open }); + }; + + render() { + const { classes } = this.props; + const { open } = this.state; + const drawer = ( + <div> + <div className={classes.toolbar} /> + <Divider /> + <List>{mailFolderListItems}</List> + <Divider /> + <List>{otherMailFolderListItems}</List> + </div> + ); + return ( + <div className={classes.root}> + <AppBar + position="absolute" + className={classes.appBar} + > + <Toolbar> + <IconButton + color="inherit" + aria-label="open drawer" + onClick={this.handleDrawerToggle} + className={classNames(classes.menuButton)} + > + <MenuIcon /> + </IconButton> + <Typography variant="h6" color="inherit" noWrap> + Clipped under the app bar + </Typography> + </Toolbar> + </AppBar> + <Hidden mdUp> + <Drawer + variant="temporary" + anchor="left" + open={open} + onClose={this.handleDrawerToggle} + classes={{ + paper: classes.drawerPaper, + }} + ModalProps={{ + keepMounted: true, // Better open performance on mobile. + }} + > + {drawer} + </Drawer> + </Hidden> + <Hidden smDown implementation="css"> + <Drawer + variant="permanent" + classes={{ + paper: classNames(classes.drawerPaper, !open && classes.drawerPaperClose), + }} + open={open} + > + {drawer} + </Drawer> + </Hidden> + <main className={classes.content}> + <div className={classes.toolbar} /> + <Typography noWrap>Your App Content</Typography> + </main> + </div> + ); + } +} + +FullHeader.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles, { withTheme: true })(FullHeader); diff --git a/front/odiparpack/app/containers/Layouts/demos/FullWidth.js b/front/odiparpack/app/containers/Layouts/demos/FullWidth.js new file mode 100644 index 0000000..fa7dbce --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/FullWidth.js @@ -0,0 +1,54 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Paper, Grid } from '@material-ui/core'; + +const styles = theme => ({ + root: { + flexGrow: 1, + }, + paper: { + padding: theme.spacing(2), + textAlign: 'center', + color: theme.palette.text.secondary, + backgroundColor: theme.palette.secondary.light + }, +}); + +function FullWidthGrid(props) { + const { classes } = props; + + return ( + <div className={classes.root}> + <Grid container spacing={3}> + <Grid item xs={12}> + <Paper className={classes.paper}>xs=12</Paper> + </Grid> + <Grid item xs={12} sm={6}> + <Paper className={classes.paper}>xs=12 sm=6</Paper> + </Grid> + <Grid item xs={12} sm={6}> + <Paper className={classes.paper}>xs=12 sm=6</Paper> + </Grid> + <Grid item xs={6} sm={3}> + <Paper className={classes.paper}>xs=6 sm=3</Paper> + </Grid> + <Grid item xs={6} sm={3}> + <Paper className={classes.paper}>xs=6 sm=3</Paper> + </Grid> + <Grid item xs={6} sm={3}> + <Paper className={classes.paper}>xs=6 sm=3</Paper> + </Grid> + <Grid item xs={6} sm={3}> + <Paper className={classes.paper}>xs=6 sm=3</Paper> + </Grid> + </Grid> + </div> + ); +} + +FullWidthGrid.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(FullWidthGrid); diff --git a/front/odiparpack/app/containers/Layouts/demos/GridLayout.js b/front/odiparpack/app/containers/Layouts/demos/GridLayout.js new file mode 100644 index 0000000..d764c1c --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/GridLayout.js @@ -0,0 +1,78 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Grid, FormLabel, FormControlLabel, Radio, RadioGroup, Paper } from '@material-ui/core'; + +const styles = theme => ({ + root: { + flexGrow: 1, + }, + paper: { + height: 140, + width: 100, + backgroundColor: theme.palette.secondary.light, + }, + control: { + marginTop: theme.spacing(2), + padding: theme.spacing(2), + }, +}); + +class GuttersGrid extends React.Component { + state = { + spacing: '2', + }; + + handleChange = key => (event, value) => { + this.setState({ + [key]: value, + }); + }; + + render() { + const { classes } = this.props; + const { spacing } = this.state; + + return ( + <Grid container className={classes.root}> + <Grid item xs={12}> + <Grid container className={classes.demo} justify="center" spacing={Number(spacing)}> + {[0, 1, 2].map(value => ( + <Grid key={value} item> + <Paper className={classes.paper} /> + </Grid> + ))} + </Grid> + </Grid> + <Grid item xs={12}> + <Paper className={classes.control}> + <Grid container> + <Grid item> + <FormLabel>spacing</FormLabel> + <RadioGroup + name="spacing" + aria-label="spacing" + value={spacing} + onChange={this.handleChange('spacing')} + row + > + <FormControlLabel value="0" control={<Radio />} label="0" /> + <FormControlLabel value="1" control={<Radio />} label="1" /> + <FormControlLabel value="2" control={<Radio />} label="2" /> + <FormControlLabel value="3" control={<Radio />} label="3" /> + <FormControlLabel value="4" control={<Radio />} label="4" /> + </RadioGroup> + </Grid> + </Grid> + </Paper> + </Grid> + </Grid> + ); + } +} + +GuttersGrid.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(GuttersGrid); diff --git a/front/odiparpack/app/containers/Layouts/demos/Interactive.js b/front/odiparpack/app/containers/Layouts/demos/Interactive.js new file mode 100644 index 0000000..2f46841 --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/Interactive.js @@ -0,0 +1,138 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Grid, FormControl, FormLabel, FormControlLabel, RadioGroup, Radio, Paper } from '@material-ui/core'; + +const styles = theme => ({ + root: { + flexGrow: 1, + }, + demo: { + height: 240, + }, + paper: { + padding: theme.spacing(2), + height: '100%', + backgroundColor: theme.palette.secondary.light, + }, + control: { + padding: theme.spacing(2), + }, +}); + +class InteractiveGrid extends React.Component { + state = { + direction: 'row', + justify: 'center', + alignItems: 'center', + }; + + handleChange = key => (event, value) => { + this.setState({ + [key]: value, + }); + }; + + render() { + const { classes } = this.props; + const { alignItems, direction, justify } = this.state; + return ( + <Grid container className={classes.root}> + <Grid item xs={12}> + <Grid + container + className={classes.demo} + alignItems={alignItems} + direction={direction} + justify={justify} + > + {[0, 1, 2].map(value => ( + <Grid key={value} item> + <Paper + className={classes.paper} + style={{ paddingTop: (value + 1) * 10, paddingBottom: (value + 1) * 10 }} + > + {`Cell ${value + 1}`} + </Paper> + </Grid> + ))} + </Grid> + </Grid> + <Grid item xs={12}> + <Paper className={classes.control}> + <Grid container> + <Grid item xs={6} sm={4}> + <FormControl component="fieldset"> + <FormLabel>direction</FormLabel> + <RadioGroup + name="direction" + aria-label="direction" + value={direction} + onChange={this.handleChange('direction')} + > + <FormControlLabel value="row" control={<Radio />} label="row" /> + <FormControlLabel value="row-reverse" control={<Radio />} label="row-reverse" /> + <FormControlLabel value="column" control={<Radio />} label="column" /> + <FormControlLabel + value="column-reverse" + control={<Radio />} + label="column-reverse" + /> + </RadioGroup> + </FormControl> + </Grid> + <Grid item xs={6} sm={4}> + <FormControl component="fieldset"> + <FormLabel>justify</FormLabel> + <RadioGroup + name="justify" + aria-label="justify" + value={justify} + onChange={this.handleChange('justify')} + > + <FormControlLabel value="flex-start" control={<Radio />} label="flex-start" /> + <FormControlLabel value="center" control={<Radio />} label="center" /> + <FormControlLabel value="flex-end" control={<Radio />} label="flex-end" /> + <FormControlLabel + value="space-between" + control={<Radio />} + label="space-between" + /> + <FormControlLabel + value="space-around" + control={<Radio />} + label="space-around" + /> + </RadioGroup> + </FormControl> + </Grid> + <Grid item xs={6} sm={4}> + <FormControl component="fieldset"> + <FormLabel>alignItems</FormLabel> + <RadioGroup + name="alignItems" + aria-label="alignItems" + value={alignItems} + onChange={this.handleChange('alignItems')} + > + <FormControlLabel value="flex-start" control={<Radio />} label="flex-start" /> + <FormControlLabel value="center" control={<Radio />} label="center" /> + <FormControlLabel value="flex-end" control={<Radio />} label="flex-end" /> + <FormControlLabel value="stretch" control={<Radio />} label="stretch" /> + <FormControlLabel value="baseline" control={<Radio />} label="baseline" /> + </RadioGroup> + </FormControl> + </Grid> + </Grid> + </Paper> + </Grid> + </Grid> + ); + } +} + +InteractiveGrid.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(InteractiveGrid); diff --git a/front/odiparpack/app/containers/Layouts/demos/Limitation.js b/front/odiparpack/app/containers/Layouts/demos/Limitation.js new file mode 100644 index 0000000..076c85f --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/Limitation.js @@ -0,0 +1,68 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Paper, Grid, Avatar, Typography } from '@material-ui/core'; + +const styles = theme => ({ + root: { + overflow: 'hidden', + padding: `0 ${theme.spacing(3)}px`, + }, + wrapper: { + maxWidth: 400, + }, + paper: { + margin: theme.spacing(1), + padding: theme.spacing(2), + backgroundColor: theme.palette.secondary.light, + }, +}); + +function AutoGridNoWrap(props) { + const { classes } = props; + const message = `Truncation should be conditionally applicable on this long line of text + as this is a much longer line than what the container can support. `; + + return ( + <div className={classes.root}> + <div className={classes.wrapper}> + <Paper className={classes.paper}> + <Grid container wrap="nowrap"> + <Grid item> + <Avatar>W</Avatar> + </Grid> + <Grid item xs zeroMinWidth> + <Typography noWrap>{message}</Typography> + </Grid> + </Grid> + </Paper> + <Paper className={classes.paper}> + <Grid container wrap="nowrap"> + <Grid item> + <Avatar>W</Avatar> + </Grid> + <Grid item xs> + <Typography noWrap>{message}</Typography> + </Grid> + </Grid> + </Paper> + <Paper className={classes.paper}> + <Grid container wrap="nowrap"> + <Grid item> + <Avatar>W</Avatar> + </Grid> + <Grid item xs> + <Typography>{message}</Typography> + </Grid> + </Grid> + </Paper> + </div> + </div> + ); +} + +AutoGridNoWrap.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(AutoGridNoWrap); diff --git a/front/odiparpack/app/containers/Layouts/demos/MediaQueries.js b/front/odiparpack/app/containers/Layouts/demos/MediaQueries.js new file mode 100644 index 0000000..89de6fc --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/MediaQueries.js @@ -0,0 +1,41 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { red, green, indigo as blue } from '@material-ui/core/colors'; +import { Typography } from '@material-ui/core'; + +const styles = theme => ({ + root: { + padding: theme.spacing(1), + '& h3': { + color: theme.palette.common.white, + }, + [theme.breakpoints.down('sm')]: { + backgroundColor: red[500], + }, + [theme.breakpoints.up('md')]: { + backgroundColor: blue[500], + }, + [theme.breakpoints.up('lg')]: { + backgroundColor: green[500], + }, + }, +}); + +function MediaQuery(props) { + const { classes } = props; + + return ( + <div className={classes.root}> + <Typography variant="subtitle1">down(sm): red</Typography> + <Typography variant="subtitle1">up(md): blue</Typography> + <Typography variant="subtitle1">up(lg): green</Typography> + </div> + ); +} + +MediaQuery.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(MediaQuery); diff --git a/front/odiparpack/app/containers/Layouts/demos/SidebarLayout.js b/front/odiparpack/app/containers/Layouts/demos/SidebarLayout.js new file mode 100644 index 0000000..3f1c10c --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/SidebarLayout.js @@ -0,0 +1,169 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import MenuIcon from '@material-ui/icons/Menu'; +import { Drawer, AppBar, Toolbar, List, Typography, Divider, IconButton, Hidden } from '@material-ui/core'; +import { mailFolderListItems, otherMailFolderListItems } from './menuData'; + + +const drawerWidth = 240; + +const styles = theme => ({ + root: { + flexGrow: 1, + height: 430, + zIndex: 1, + overflow: 'hidden', + position: 'relative', + display: 'flex', + }, + appBar: { + zIndex: theme.zIndex.drawer + 1, + transition: theme.transitions.create(['width', 'margin'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + }, + appBarShift: { + marginLeft: 0, + width: '100%', + transition: theme.transitions.create(['width', 'margin'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + [theme.breakpoints.up('md')]: { + marginLeft: drawerWidth, + width: `calc(100% - ${drawerWidth}px)`, + }, + }, + menuButton: { + marginLeft: 0, + marginRight: 36, + }, + drawerPaper: { + width: drawerWidth, + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + [theme.breakpoints.up('md')]: { + position: 'relative', + }, + }, + drawerPaperClose: { + overflowX: 'hidden', + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + width: 70, + [theme.breakpoints.up('sm')]: { + width: 70, + }, + }, + toolbar: { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + padding: '0 8px', + ...theme.mixins.toolbar, + }, + content: { + flexGrow: 1, + backgroundColor: theme.palette.background.default, + padding: theme.spacing(3), + }, +}); + +class SidebarLayout extends React.Component { + state = { + open: true, + }; + + componentDidMount() { + window.addEventListener('resize', this.resize.bind(this)); + this.resize(); + } + + resize() { + this.setState({ open: window.innerWidth >= 760 }); + } + + handleDrawerToggle = () => { + this.setState({ open: !this.state.open }); + }; + + render() { + const { classes } = this.props; + const { open } = this.state; + const drawer = ( + <div> + <div className={classes.toolbar} /> + <Divider /> + <List>{mailFolderListItems}</List> + <Divider /> + <List>{otherMailFolderListItems}</List> + </div> + ); + return ( + <div className={classes.root}> + <AppBar + position="absolute" + className={classNames(classes.appBar, open && classes.appBarShift)} + > + <Toolbar> + <IconButton + color="inherit" + aria-label="open drawer" + onClick={this.handleDrawerToggle} + className={classNames(classes.menuButton)} + > + <MenuIcon /> + </IconButton> + <Typography variant="h6" color="inherit" noWrap> + App Layout with Sidebar + </Typography> + </Toolbar> + </AppBar> + <Hidden mdUp> + <Drawer + variant="temporary" + anchor="left" + open={open} + onClose={this.handleDrawerToggle} + classes={{ + paper: classes.drawerPaper, + }} + ModalProps={{ + keepMounted: true, // Better open performance on mobile. + }} + > + {drawer} + </Drawer> + </Hidden> + <Hidden smDown implementation="css"> + <Drawer + variant="permanent" + classes={{ + paper: classNames(classes.drawerPaper, !open && classes.drawerPaperClose), + }} + open={open} + > + {drawer} + </Drawer> + </Hidden> + <main className={classes.content}> + <div className={classes.toolbar} /> + <Typography noWrap>Your App Content</Typography> + </main> + </div> + ); + } +} + +SidebarLayout.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles, { withTheme: true })(SidebarLayout); diff --git a/front/odiparpack/app/containers/Layouts/demos/SidebarLayoutRight.js b/front/odiparpack/app/containers/Layouts/demos/SidebarLayoutRight.js new file mode 100644 index 0000000..85c5229 --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/SidebarLayoutRight.js @@ -0,0 +1,175 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import MenuIcon from '@material-ui/icons/Menu'; +import { Drawer, AppBar, Toolbar, List, Typography, Divider, IconButton, Hidden } from '@material-ui/core'; +import { mailFolderListItems, otherMailFolderListItems } from './menuData'; + + +const drawerWidth = 240; + +const styles = theme => ({ + root: { + flexGrow: 1, + height: 430, + zIndex: 1, + overflow: 'hidden', + position: 'relative', + display: 'flex', + }, + flexwrap: { + display: 'flex' + }, + flex: { + flex: 1 + }, + appBar: { + zIndex: theme.zIndex.drawer + 1, + transition: theme.transitions.create(['width', 'margin'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + }, + appBarShift: { + marginRight: 0, + width: '100%', + transition: theme.transitions.create(['width', 'margin'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + [theme.breakpoints.up('md')]: { + marginRight: drawerWidth, + width: `calc(100% - ${drawerWidth}px)`, + }, + }, + menuButton: { + marginRight: 0, + marginLeft: 36, + }, + drawerPaper: { + width: drawerWidth, + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + [theme.breakpoints.up('md')]: { + position: 'relative', + }, + }, + drawerPaperClose: { + overflowX: 'hidden', + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + width: 70, + [theme.breakpoints.up('sm')]: { + width: 70, + }, + }, + toolbar: { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + padding: '0 8px', + ...theme.mixins.toolbar, + }, + content: { + flexGrow: 1, + backgroundColor: theme.palette.background.default, + padding: theme.spacing(3), + }, +}); + +class SidebarLayoutRight extends React.Component { + state = { + open: true, + }; + + componentDidMount() { + window.addEventListener('resize', this.resize.bind(this)); + this.resize(); + } + + resize() { + this.setState({ open: window.innerWidth >= 760 }); + } + + handleDrawerToggle = () => { + this.setState({ open: !this.state.open }); + }; + + render() { + const { classes } = this.props; + const { open } = this.state; + const drawer = ( + <div> + <div className={classes.toolbar} /> + <Divider /> + <List>{mailFolderListItems}</List> + <Divider /> + <List>{otherMailFolderListItems}</List> + </div> + ); + return ( + <div className={classes.root}> + <AppBar + position="absolute" + className={classNames(classes.appBar, open && classes.appBarShift)} + > + <Toolbar className={classes.flexwrap}> + <Typography variant="h6" color="inherit" className={classes.flex} noWrap> + App Layout with Right Sidebar + </Typography> + <IconButton + color="inherit" + aria-label="open drawer" + onClick={this.handleDrawerToggle} + className={classNames(classes.menuButton)} + > + <MenuIcon /> + </IconButton> + </Toolbar> + </AppBar> + <main className={classes.content}> + <div className={classes.toolbar} /> + <Typography noWrap>Your App Content</Typography> + </main> + <Hidden mdUp> + <Drawer + variant="temporary" + anchor="right" + open={open} + onClose={this.handleDrawerToggle} + classes={{ + paper: classes.drawerPaper, + }} + ModalProps={{ + keepMounted: true, // Better open performance on mobile. + }} + > + {drawer} + </Drawer> + </Hidden> + <Hidden smDown implementation="css"> + <Drawer + variant="permanent" + classes={{ + paper: classNames(classes.drawerPaper, !open && classes.drawerPaperClose), + }} + open={open} + > + {drawer} + </Drawer> + </Hidden> + </div> + ); + } +} + +SidebarLayoutRight.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles, { withTheme: true })(SidebarLayoutRight); diff --git a/front/odiparpack/app/containers/Layouts/demos/WIthWIdth.js b/front/odiparpack/app/containers/Layouts/demos/WIthWIdth.js new file mode 100644 index 0000000..2aab0e3 --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/WIthWIdth.js @@ -0,0 +1,26 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withWidth, Typography } from '@material-ui/core'; + +const components = { + sm: 'em', + md: 'u', + lg: 'del', +}; + +function WithWidth(props) { + const { width } = props; + const Component = components[width] || 'span'; + + return ( + <Typography variant="subtitle1"> + <Component>{`Current width: ${width}`}</Component> + </Typography> + ); +} + +WithWidth.propTypes = { + width: PropTypes.string.isRequired, +}; + +export default withWidth()(WithWidth); diff --git a/front/odiparpack/app/containers/Layouts/demos/breakpoint.md b/front/odiparpack/app/containers/Layouts/demos/breakpoint.md new file mode 100644 index 0000000..e5037ec --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/breakpoint.md @@ -0,0 +1,6 @@ + + | innerWidth |xs | sm | md | lg | xl | + |--------|-----|----|----|----|----|----| + | width | xs | sm | md | lg | xl | + | smUp | show | hide | + | mdDown | | | hide | show | diff --git a/front/odiparpack/app/containers/Layouts/demos/index.js b/front/odiparpack/app/containers/Layouts/demos/index.js new file mode 100644 index 0000000..70f3804 --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/index.js @@ -0,0 +1,13 @@ +export AutoLayout from './AutoLayout'; +export Centered from './Centered'; +export FullWidth from './FullWidth'; +export GridLayout from './GridLayout'; +export Interactive from './Interactive'; +export Limitation from './Limitation'; +export SidebarLayout from './SidebarLayout'; +export SidebarLayoutRight from './SidebarLayoutRight'; +export FullHeader from './FullHeader'; +export Breakpoint from './Breakpoint'; +export BreakpointGrid from './BreakpointGrid'; +export MediaQueries from './MediaQueries'; +export WIthWIdth from './WIthWIdth'; diff --git a/front/odiparpack/app/containers/Layouts/demos/menuData.js b/front/odiparpack/app/containers/Layouts/demos/menuData.js new file mode 100644 index 0000000..cd2d653 --- /dev/null +++ b/front/odiparpack/app/containers/Layouts/demos/menuData.js @@ -0,0 +1,64 @@ +// This file is shared across the demos. + +import React from 'react'; +import InboxIcon from '@material-ui/icons/MoveToInbox'; +import DraftsIcon from '@material-ui/icons/Drafts'; +import StarIcon from '@material-ui/icons/Star'; +import SendIcon from '@material-ui/icons/Send'; +import MailIcon from '@material-ui/icons/Mail'; +import DeleteIcon from '@material-ui/icons/Delete'; +import ReportIcon from '@material-ui/icons/Report'; + +import { ListItem, ListItemIcon, ListItemText } from '@material-ui/core'; + +export const mailFolderListItems = ( + <div> + <ListItem button> + <ListItemIcon> + <InboxIcon /> + </ListItemIcon> + <ListItemText primary="Inbox" /> + </ListItem> + <ListItem button> + <ListItemIcon> + <StarIcon /> + </ListItemIcon> + <ListItemText primary="Starred" /> + </ListItem> + <ListItem button> + <ListItemIcon> + <SendIcon /> + </ListItemIcon> + <ListItemText primary="Send mail" /> + </ListItem> + <ListItem button> + <ListItemIcon> + <DraftsIcon /> + </ListItemIcon> + <ListItemText primary="Drafts" /> + </ListItem> + </div> +); + +export const otherMailFolderListItems = ( + <div> + <ListItem button> + <ListItemIcon> + <MailIcon /> + </ListItemIcon> + <ListItemText primary="All mail" /> + </ListItem> + <ListItem button> + <ListItemIcon> + <DeleteIcon /> + </ListItemIcon> + <ListItemText primary="Trash" /> + </ListItem> + <ListItem button> + <ListItemIcon> + <ReportIcon /> + </ListItemIcon> + <ListItemText primary="Spam" /> + </ListItem> + </div> +); diff --git a/front/odiparpack/app/containers/Maps/Info.js b/front/odiparpack/app/containers/Maps/Info.js new file mode 100644 index 0000000..1faea09 --- /dev/null +++ b/front/odiparpack/app/containers/Maps/Info.js @@ -0,0 +1,18 @@ +import React from 'react'; +import Type from 'ba-styles/Typography.scss'; +import { Typography } from '@material-ui/core'; + +class Info extends React.Component { + render() { + return ( + <Typography className={Type.textWarning} gutterBottom style={{ marginBottom: 20 }}> + This demo may not working properly because it has not added Google Map api key. To add Your own Google Map api key please follow this link + + {' '} + <a rel="noopener noreferrer" style={{ wordWrap: 'break-word' }} target="_blank" href="https://developers.google.com/maps/documentation/javascript/get-api-key">https://developers.google.com/maps/documentation/javascript/get-api-key</a> + </Typography> + ); + } +} + +export default Info; diff --git a/front/odiparpack/app/containers/Maps/MapDirection.js b/front/odiparpack/app/containers/Maps/MapDirection.js new file mode 100644 index 0000000..97a463b --- /dev/null +++ b/front/odiparpack/app/containers/Maps/MapDirection.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Direction } from './demos'; +import Info from './Info'; + +class MapDirection extends React.Component { + render() { + const title = brand.name + ' - Map'; + const description = brand.desc; + const docSrc = 'containers/Maps/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock overflowX title="Map with Direction" desc="Rendering map with default configuration"> + <Info /> + <Direction /> + <SourceReader componentName={docSrc + 'Direction.js'} /> + </PapperBlock> + </div> + ); + } +} + +export default MapDirection; diff --git a/front/odiparpack/app/containers/Maps/MapMarker.js b/front/odiparpack/app/containers/Maps/MapMarker.js new file mode 100644 index 0000000..65f2420 --- /dev/null +++ b/front/odiparpack/app/containers/Maps/MapMarker.js @@ -0,0 +1,38 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { BasicMarker, PopoverMarker } from './demos'; +import Info from './Info'; + +class MapMarker extends React.Component { + render() { + const title = brand.name + ' - Map'; + const description = brand.desc; + const docSrc = 'containers/Maps/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock overflowX title="Map with a Marker" desc="A sample for basic mark a coodinate in map"> + <Info /> + <BasicMarker /> + <SourceReader componentName={docSrc + 'BasicMarker.js'} /> + </PapperBlock> + <PapperBlock overflowX title="Marker with Popover/Info Window" desc="Click marker to show detail place"> + <Info /> + <PopoverMarker /> + <SourceReader componentName={docSrc + 'BasicMarker.js'} /> + </PapperBlock> + </div> + ); + } +} + +export default MapMarker; diff --git a/front/odiparpack/app/containers/Maps/SearchMap.js b/front/odiparpack/app/containers/Maps/SearchMap.js new file mode 100644 index 0000000..b3e21d9 --- /dev/null +++ b/front/odiparpack/app/containers/Maps/SearchMap.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { SearchLocation } from './demos'; +import Info from './Info'; + +class SearchMap extends React.Component { + render() { + const title = brand.name + ' - Map'; + const description = brand.desc; + const docSrc = 'containers/Maps/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock overflowX title="Search Location" desc="Map with search input to find any location"> + <Info /> + <SearchLocation /> + <SourceReader componentName={docSrc + 'SearchLocation.js'} /> + </PapperBlock> + </div> + ); + } +} + +export default SearchMap; diff --git a/front/odiparpack/app/containers/Maps/StreetViewMap.js b/front/odiparpack/app/containers/Maps/StreetViewMap.js new file mode 100644 index 0000000..e307da8 --- /dev/null +++ b/front/odiparpack/app/containers/Maps/StreetViewMap.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { StreetView } from './demos'; +import Info from './Info'; + +class StreetViewMap extends React.Component { + render() { + const title = brand.name + ' - Map'; + const description = brand.desc; + const docSrc = 'containers/Maps/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock overflowX title="StreetView" desc="View location in 3d perspective"> + <Info /> + <StreetView /> + <SourceReader componentName={docSrc + 'StreetView.js'} /> + </PapperBlock> + </div> + ); + } +} + +export default StreetViewMap; diff --git a/front/odiparpack/app/containers/Maps/TrafficIndicator.js b/front/odiparpack/app/containers/Maps/TrafficIndicator.js new file mode 100644 index 0000000..e1e8a69 --- /dev/null +++ b/front/odiparpack/app/containers/Maps/TrafficIndicator.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Traffic } from './demos'; +import Info from './Info'; + +class TrafficIndicator extends React.Component { + render() { + const title = brand.name + ' - Map'; + const description = brand.desc; + const docSrc = 'containers/Maps/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock overflowX title="Traffic Indicator" desc="Get information of transportation traffic arround the world"> + <Info /> + <Traffic /> + <SourceReader componentName={docSrc + 'Traffic.js'} /> + </PapperBlock> + </div> + ); + } +} + +export default TrafficIndicator; diff --git a/front/odiparpack/app/containers/Maps/demos/BasicMarker.js b/front/odiparpack/app/containers/Maps/demos/BasicMarker.js new file mode 100644 index 0000000..7577923 --- /dev/null +++ b/front/odiparpack/app/containers/Maps/demos/BasicMarker.js @@ -0,0 +1,38 @@ +import React from 'react'; +import { compose } from 'recompose'; +import { + withScriptjs, + withGoogleMap, + GoogleMap, + Marker, +} from 'react-google-maps'; + +const MapWithAMarker = compose( + withScriptjs, + withGoogleMap +)(props => ( + <GoogleMap + {...props} + defaultZoom={8} + defaultCenter={{ lat: -34.397, lng: 150.644 }} + > + <Marker + position={{ lat: -34.397, lng: 150.644 }} + /> + </GoogleMap> +)); + +class BasicMarker extends React.Component { + render() { + return ( + <MapWithAMarker + googleMapURL="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places" + loadingElement={<div style={{ height: '100%' }} />} + containerElement={<div style={{ height: '400px' }} />} + mapElement={<div style={{ height: '100%' }} />} + /> + ); + } +} + +export default BasicMarker; diff --git a/front/odiparpack/app/containers/Maps/demos/Direction.js b/front/odiparpack/app/containers/Maps/demos/Direction.js new file mode 100644 index 0000000..8867466 --- /dev/null +++ b/front/odiparpack/app/containers/Maps/demos/Direction.js @@ -0,0 +1,56 @@ +/* eslint-disable no-undef */ +import React from 'react'; +import { compose, withProps, lifecycle } from 'recompose'; +import { + withScriptjs, + withGoogleMap, + GoogleMap, + DirectionsRenderer, +} from 'react-google-maps'; + +const MapWithADirectionsRenderer = compose( + withProps({ + googleMapURL: 'https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places', + loadingElement: <div style={{ height: '100%' }} />, + containerElement: <div style={{ height: '400px' }} />, + mapElement: <div style={{ height: '100%' }} />, + }), + withScriptjs, + withGoogleMap, + lifecycle({ + componentDidMount() { + const DirectionsService = new google.maps.DirectionsService(); + + DirectionsService.route({ + origin: new google.maps.LatLng(41.8507300, -87.6512600), + destination: new google.maps.LatLng(41.8525800, -87.6514100), + travelMode: google.maps.TravelMode.DRIVING, + }, (result, status) => { + if (status === google.maps.DirectionsStatus.OK) { + this.setState({ + directions: result, + }); + } else { + console.error(`error fetching directions ${result}`); + } + }); + } + }) +)(props => ( + <GoogleMap + defaultZoom={8} + defaultCenter={new google.maps.LatLng(41.8507300, -87.6512600)} + > + {props.directions && <DirectionsRenderer directions={props.directions} />} + </GoogleMap> +)); + +class Direction extends React.Component { + render() { + return ( + <MapWithADirectionsRenderer /> + ); + } +} + +export default Direction; diff --git a/front/odiparpack/app/containers/Maps/demos/PopoverMarker.js b/front/odiparpack/app/containers/Maps/demos/PopoverMarker.js new file mode 100644 index 0000000..9a07f11 --- /dev/null +++ b/front/odiparpack/app/containers/Maps/demos/PopoverMarker.js @@ -0,0 +1,55 @@ +import React from 'react'; +import { compose, withStateHandlers } from 'recompose'; +import LocalDining from '@material-ui/icons/LocalDining'; +import { + withScriptjs, + withGoogleMap, + GoogleMap, + Marker, + InfoWindow +} from 'react-google-maps'; + +const MapWithAMakredInfoWindow = compose( + withStateHandlers(() => ({ + isOpen: false, + }), { + onToggleOpen: ({ isOpen }) => () => ({ + isOpen: !isOpen, + }) + }), + withScriptjs, + withGoogleMap +)(props => ( + <GoogleMap + defaultZoom={8} + defaultCenter={{ lat: -34.397, lng: 150.644 }} + > + <Marker + position={{ lat: -34.397, lng: 150.644 }} + onClick={props.onToggleOpen} + > + {props.isOpen && + <InfoWindow onCloseClick={props.onToggleOpen}> + <span> + <LocalDining /> A marked place + </span> + </InfoWindow> + } + </Marker> + </GoogleMap> +)); + +class PopoverMarker extends React.Component { + render() { + return ( + <MapWithAMakredInfoWindow + googleMapURL="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places" + loadingElement={<div style={{ height: '100%' }} />} + containerElement={<div style={{ height: '400px' }} />} + mapElement={<div style={{ height: '100%' }} />} + /> + ); + } +} + +export default PopoverMarker; diff --git a/front/odiparpack/app/containers/Maps/demos/SearchLocation.js b/front/odiparpack/app/containers/Maps/demos/SearchLocation.js new file mode 100644 index 0000000..16449d0 --- /dev/null +++ b/front/odiparpack/app/containers/Maps/demos/SearchLocation.js @@ -0,0 +1,116 @@ +import React from 'react'; +import { compose, withProps, lifecycle } from 'recompose'; +import { + withScriptjs, + withGoogleMap, + GoogleMap, + Marker, +} from 'react-google-maps'; +import { SearchBox } from 'react-google-maps/lib/components/places/SearchBox'; +const _ = require('lodash'); + +const MapWithASearchBox = compose( + withProps({ + googleMapURL: 'https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places', + loadingElement: <div style={{ height: '100%' }} />, + containerElement: <div style={{ height: '400px' }} />, + mapElement: <div style={{ height: '100%' }} />, + }), + lifecycle({ + componentWillMount() { + const refs = {}; + + this.setState({ + bounds: null, + center: { + lat: 41.9, lng: -87.624 + }, + markers: [], + onMapMounted: ref => { + refs.map = ref; + }, + onBoundsChanged: () => { + this.setState({ + bounds: refs.map.getBounds(), + center: refs.map.getCenter(), + }); + }, + onSearchBoxMounted: ref => { + refs.searchBox = ref; + }, + onPlacesChanged: () => { + const places = refs.searchBox.getPlaces(); + const bounds = new google.maps.LatLngBounds(); // eslint-disable-line + + places.forEach(place => { + if (place.geometry.viewport) { + bounds.union(place.geometry.viewport); + } else { + bounds.extend(place.geometry.location); + } + }); + const nextMarkers = places.map(place => ({ + position: place.geometry.location, + })); + const nextCenter = _.get(nextMarkers, '0.position', this.state.center); + + this.setState({ + center: nextCenter, + markers: nextMarkers, + }); + // refs.map.fitBounds(bounds); + }, + }); + }, + }), + withScriptjs, + withGoogleMap +)(props => ( + <GoogleMap + {...props} + ref={props.onMapMounted} + defaultZoom={15} + center={props.center} + onBoundsChanged={props.onBoundsChanged} + > + <SearchBox + ref={props.onSearchBoxMounted} + bounds={props.bounds} + controlPosition={google.maps.ControlPosition.TOP_LEFT} // eslint-disable-line + onPlacesChanged={props.onPlacesChanged} + > + <input + type="text" + placeholder="Customized your placeholder" + style={{ + boxSizing: 'border-box', + border: '1px solid transparent', + width: '240px', + height: '32px', + marginTop: '7px', + marginLeft: '10px', + padding: '0 12px', + borderRadius: '3px', + background: '#FAFAFA', + boxShadow: '0 2px 6px rgba(0, 0, 0, 0.3)', + fontSize: '14px', + outline: 'none', + textOverflow: 'ellipses', + }} + /> + </SearchBox> + {props.markers.map((marker, index) => + <Marker key={index.toString()} position={marker.position} /> + )} + </GoogleMap> +)); + +class SearchLocation extends React.Component { + render() { + return ( + <MapWithASearchBox /> + ); + } +} + +export default SearchLocation; diff --git a/front/odiparpack/app/containers/Maps/demos/StreetView.js b/front/odiparpack/app/containers/Maps/demos/StreetView.js new file mode 100644 index 0000000..a8d3108 --- /dev/null +++ b/front/odiparpack/app/containers/Maps/demos/StreetView.js @@ -0,0 +1,62 @@ +import React from 'react'; +import { compose, withProps } from 'recompose'; +import { + withScriptjs, + withGoogleMap, + GoogleMap, + StreetViewPanorama, + OverlayView, +} from 'react-google-maps'; + +const getPixelPositionOffset = (width, height) => ({ + x: -(width / 2), + y: -(height / 2), +}); + +const MapWithAMarker = compose( + withProps({ + googleMapURL: 'https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places', + loadingElement: <div style={{ height: '100%' }} />, + containerElement: <div style={{ height: '400px' }} />, + mapElement: <div style={{ height: '100%' }} />, + center: { lat: 49.2853171, lng: -123.1119202 }, + }), + withScriptjs, + withGoogleMap +)(props => ( + <GoogleMap defaultZoom={8} defaultCenter={props.center}> + <StreetViewPanorama defaultPosition={props.center} visible> + <OverlayView + position={{ lat: 49.28590291211115, lng: -123.11248166065218 }} + mapPaneName={OverlayView.OVERLAY_LAYER} + getPixelPositionOffset={getPixelPositionOffset} + > + <div + style={{ + background: 'red', + color: 'white', + padding: 5, + borderRadius: '50%' + }} + > + OverlayView + </div> + </OverlayView> + </StreetViewPanorama> + </GoogleMap> +)); + +class StreetView extends React.Component { + render() { + return ( + <MapWithAMarker + googleMapURL="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places" + loadingElement={<div style={{ height: '100%' }} />} + containerElement={<div style={{ height: '400px' }} />} + mapElement={<div style={{ height: '100%' }} />} + /> + ); + } +} + +export default StreetView; diff --git a/front/odiparpack/app/containers/Maps/demos/Traffic.js b/front/odiparpack/app/containers/Maps/demos/Traffic.js new file mode 100644 index 0000000..0f5cdfd --- /dev/null +++ b/front/odiparpack/app/containers/Maps/demos/Traffic.js @@ -0,0 +1,37 @@ +import React from 'react'; +import { compose, withProps } from 'recompose'; +import { + withScriptjs, + withGoogleMap, + GoogleMap, + TrafficLayer, +} from 'react-google-maps'; + +const MapWithATrafficLayer = compose( + withProps({ + googleMapURL: 'https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places', + loadingElement: <div style={{ height: '100%' }} />, + containerElement: <div style={{ height: '400px' }} />, + mapElement: <div style={{ height: '100%' }} />, + }), + withScriptjs, + withGoogleMap +)(props => ( + <GoogleMap + {...props} + defaultZoom={8} + defaultCenter={{ lat: 41.9, lng: -87.624 }} + > + <TrafficLayer autoUpdate /> + </GoogleMap> +)); + +class Traffic extends React.Component { + render() { + return ( + <MapWithATrafficLayer /> + ); + } +} + +export default Traffic; diff --git a/front/odiparpack/app/containers/Maps/demos/index.js b/front/odiparpack/app/containers/Maps/demos/index.js new file mode 100644 index 0000000..33e8376 --- /dev/null +++ b/front/odiparpack/app/containers/Maps/demos/index.js @@ -0,0 +1,6 @@ +export BasicMarker from './BasicMarker'; +export PopoverMarker from './PopoverMarker'; +export Direction from './Direction'; +export SearchLocation from './SearchLocation'; +export Traffic from './Traffic'; +export StreetView from './StreetView'; diff --git a/front/odiparpack/app/containers/NotFound/NotFound.js b/front/odiparpack/app/containers/NotFound/NotFound.js new file mode 100644 index 0000000..4662a06 --- /dev/null +++ b/front/odiparpack/app/containers/NotFound/NotFound.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { Route } from 'react-router-dom'; +import { ErrorWrap } from 'ba-components'; + +const title = brand.name + ' - Page Not Found'; +const description = brand.desc; + +const NotFound = () => ( + <Route + render={({ staticContext }) => { + if (staticContext) { + staticContext.status = 404; // eslint-disable-line + } + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <ErrorWrap title="404" desc="Oops, Page Not Found :(" /> + </div> + ); + }} + /> +); + +export default NotFound; diff --git a/front/odiparpack/app/containers/Pages/.DS_Store b/front/odiparpack/app/containers/Pages/.DS_Store Binary files differnew file mode 100644 index 0000000..f59225b --- /dev/null +++ b/front/odiparpack/app/containers/Pages/.DS_Store diff --git a/front/odiparpack/app/containers/Pages/BlankPage/index.js b/front/odiparpack/app/containers/Pages/BlankPage/index.js new file mode 100644 index 0000000..640da6e --- /dev/null +++ b/front/odiparpack/app/containers/Pages/BlankPage/index.js @@ -0,0 +1,28 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { PapperBlock } from 'ba-components'; + +class BlankPage extends React.Component { + render() { + const title = brand.name + ' - Blank Page'; + const description = brand.desc; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Blank Page" desc="Some text description"> + Content + </PapperBlock> + </div> + ); + } +} + +export default BlankPage; diff --git a/front/odiparpack/app/containers/Pages/Calendar/index.js b/front/odiparpack/app/containers/Pages/Calendar/index.js new file mode 100644 index 0000000..fd5f412 --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Calendar/index.js @@ -0,0 +1,137 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import events from 'ba-api/eventData'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import 'ba-styles/vendors/react-big-calendar/react-big-calendar.css'; +import { EventCalendar, DetailEvent, AddEvent, Notification } from 'ba-components'; +import { + fetchAction, + addAction, + discardAction, + submitAction, + deleteAction, + closeNotifAction +} from 'ba-actions/CalendarEventActions'; + +const styles = { + root: { + display: 'block' + } +}; + +class Calendar extends React.Component { + state = { + anchorEl: false, + event: null, + anchorPos: { top: 0, left: 0 } + }; + + componentDidMount() { + this.props.fetchEventsData(events); + } + + handleClick = event => { + setTimeout(() => { + const target = document.getElementsByClassName('rbc-selected')[0]; + const targetBounding = target.getBoundingClientRect(); + this.setState({ + event, + anchorEl: true, + anchorPos: { top: targetBounding.top, left: targetBounding.left } + }); + }, 200); + }; + + handleClose = () => { + this.setState({ + anchorEl: false, + }); + }; + + render() { + const title = brand.name + ' - Calendar'; + const description = brand.desc; + const { anchorEl, anchorPos, event } = this.state; + const { + classes, + eventData, + openFrm, + addEvent, + discardEvent, + submit, + remove, + closeNotif, + messageNotif + } = this.props; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <Notification close={() => closeNotif()} message={messageNotif} /> + <div className={classes.root}> + <EventCalendar events={eventData.toJS()} handleEventClick={this.handleClick} /> + <DetailEvent + event={event} + anchorEl={anchorEl} + anchorPos={anchorPos} + close={this.handleClose} + remove={remove} + /> + <AddEvent + openForm={openFrm} + addEvent={addEvent} + closeForm={discardEvent} + submit={submit} + /> + </div> + </div> + ); + } +} + +Calendar.propTypes = { + classes: PropTypes.object.isRequired, + eventData: PropTypes.object.isRequired, + fetchEventsData: PropTypes.func.isRequired, + addEvent: PropTypes.func.isRequired, + submit: PropTypes.func.isRequired, + discardEvent: PropTypes.func.isRequired, + remove: PropTypes.func.isRequired, + openFrm: PropTypes.bool.isRequired, + closeNotif: PropTypes.func.isRequired, + messageNotif: PropTypes.string.isRequired, +}; + +const reducer = 'calendar'; +const mapStateToProps = state => ({ + force: state, // force state from reducer + eventData: state.getIn([reducer, 'events']), + openFrm: state.getIn([reducer, 'openFrm']), + messageNotif: state.getIn([reducer, 'notifMsg']), +}); + +const constDispatchToProps = dispatch => ({ + fetchEventsData: bindActionCreators(fetchAction, dispatch), + submit: bindActionCreators(submitAction, dispatch), + remove: bindActionCreators(deleteAction, dispatch), + addEvent: () => dispatch(addAction), + discardEvent: () => dispatch(discardAction), + closeNotif: () => dispatch(closeNotifAction), +}); + +const CalendarMapped = connect( + mapStateToProps, + constDispatchToProps +)(Calendar); + +export default withStyles(styles)(CalendarMapped); diff --git a/front/odiparpack/app/containers/Pages/Chat/index.js b/front/odiparpack/app/containers/Pages/Chat/index.js new file mode 100644 index 0000000..99cf875 --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Chat/index.js @@ -0,0 +1,123 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import contactData from 'ba-api/contactData'; +import chatData from 'ba-api/chatData'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { fetchAction, searchAction } from 'ba-actions/ContactActions'; +import { + fetchChatAction, + showChatAction, + sendAction, + hideDetailAction, + deleteAction +} from 'ba-actions/ChatActions'; +import { ContactList, ChatHeader, ChatRoom } from 'ba-components'; +import styles from 'ba-components/Contact/contact-jss'; + +class Chat extends React.Component { + componentDidMount() { + this.props.fetchChatData(chatData); + this.props.fetchContactData(contactData); + } + + render() { + const title = brand.name + ' - Chat App'; + const description = brand.desc; + const { + classes, + dataContact, + showDetail, + hideDetail, + keyword, + search, + dataChat, + chatSelected, + sendMessage, + remove, + showMobileDetail + } = this.props; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <div className={classes.root}> + <ChatHeader + dataContact={dataContact} + chatSelected={chatSelected} + remove={remove} + showMobileDetail={showMobileDetail} + hideDetail={hideDetail} + /> + <ContactList + itemSelected={chatSelected} + dataContact={dataContact} + showDetail={showDetail} + search={search} + keyword={keyword} + /> + <ChatRoom + showMobileDetail={showMobileDetail} + dataChat={dataChat} + chatSelected={chatSelected} + dataContact={dataContact} + sendMessage={sendMessage} + /> + </div> + </div> + ); + } +} + +Chat.propTypes = { + classes: PropTypes.object.isRequired, + fetchContactData: PropTypes.func.isRequired, + fetchChatData: PropTypes.func.isRequired, + showDetail: PropTypes.func.isRequired, + hideDetail: PropTypes.func.isRequired, + sendMessage: PropTypes.func.isRequired, + search: PropTypes.func.isRequired, + remove: PropTypes.func.isRequired, + keyword: PropTypes.string.isRequired, + dataContact: PropTypes.object.isRequired, + dataChat: PropTypes.object.isRequired, + chatSelected: PropTypes.number.isRequired, + showMobileDetail: PropTypes.bool.isRequired, +}; + +const reducerContact = 'contact'; +const reducerChat = 'chat'; +const mapStateToProps = state => ({ + force: state, // force state from reducer + dataContact: state.getIn([reducerContact, 'contactList']), + dataChat: state.getIn([reducerChat, 'activeChat']), + chatSelected: state.getIn([reducerChat, 'chatSelected']), + showMobileDetail: state.getIn([reducerChat, 'showMobileDetail']), + keyword: state.getIn([reducerContact, 'keywordValue']), +}); + +const dispatchToProps = dispatch => ({ + fetchContactData: bindActionCreators(fetchAction, dispatch), + hideDetail: () => dispatch(hideDetailAction), + fetchChatData: bindActionCreators(fetchChatAction, dispatch), + showDetail: bindActionCreators(showChatAction, dispatch), + search: bindActionCreators(searchAction, dispatch), + sendMessage: bindActionCreators(sendAction, dispatch), + remove: () => dispatch(deleteAction), +}); + +const ChatMapped = connect( + mapStateToProps, + dispatchToProps +)(Chat); + +export default withStyles(styles)(ChatMapped); diff --git a/front/odiparpack/app/containers/Pages/Contact/index.js b/front/odiparpack/app/containers/Pages/Contact/index.js new file mode 100644 index 0000000..8ed34de --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Contact/index.js @@ -0,0 +1,166 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import data from 'ba-api/contactData'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import dummy from 'ba-api/dummyContents'; +import { + fetchAction, + showDetailAction, + hideDetailAction, + submitAction, + editAction, + addAction, + closeAction, + removeAction, + addToFavoriteAction, + searchAction, + closeNotifAction +} from 'ba-actions/ContactActions'; +import { + ContactHeader, + ContactList, + ContactDetail, + AddContact, + Notification +} from 'ba-components'; +import styles from 'ba-components/Contact/contact-jss'; + +class Contact extends React.Component { + componentDidMount() { + this.props.fetchData(data); + } + + submitContact = (item, avatar) => { + const { submit } = this.props; + const avatarBase64 = typeof avatar === 'object' ? URL.createObjectURL(avatar) : avatar; + const avatarPreview = avatar !== null ? avatarBase64 : dummy.user.avatar; + submit(item, avatarPreview); + } + + render() { + const title = brand.name + ' - Contact'; + const description = brand.desc; + const { + classes, + dataContact, + itemSelected, + showDetail, + hideDetail, + avatarInit, + open, + showMobileDetail, + add, + edit, + close, + remove, + favorite, + keyword, + search, + closeNotif, + messageNotif + } = this.props; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <Notification close={() => closeNotif()} message={messageNotif} /> + <div className={classes.root}> + <ContactHeader + hideDetail={hideDetail} + addContact={add} + total={dataContact.size} + showMobileDetail={showMobileDetail} + /> + <ContactList + clippedRight + itemSelected={itemSelected} + dataContact={dataContact} + showDetail={showDetail} + search={search} + keyword={keyword} + /> + <ContactDetail + showMobileDetail={showMobileDetail} + dataContact={dataContact} + itemSelected={itemSelected} + edit={edit} + remove={remove} + favorite={favorite} + /> + </div> + <AddContact + addContact={add} + openForm={open} + closeForm={close} + submit={this.submitContact} + avatarInit={avatarInit} + /> + </div> + ); + } +} + +Contact.propTypes = { + classes: PropTypes.object.isRequired, + avatarInit: PropTypes.string.isRequired, + fetchData: PropTypes.func.isRequired, + showDetail: PropTypes.func.isRequired, + hideDetail: PropTypes.func.isRequired, + keyword: PropTypes.string.isRequired, + open: PropTypes.bool.isRequired, + showMobileDetail: PropTypes.bool.isRequired, + add: PropTypes.func.isRequired, + close: PropTypes.func.isRequired, + submit: PropTypes.func.isRequired, + edit: PropTypes.func.isRequired, + remove: PropTypes.func.isRequired, + favorite: PropTypes.func.isRequired, + search: PropTypes.func.isRequired, + dataContact: PropTypes.object.isRequired, + itemSelected: PropTypes.number.isRequired, + closeNotif: PropTypes.func.isRequired, + messageNotif: PropTypes.string.isRequired, +}; + +const reducer = 'contact'; +const mapStateToProps = state => ({ + force: state, // force state from reducer + avatarInit: state.getIn([reducer, 'avatarInit']), + dataContact: state.getIn([reducer, 'contactList']), + itemSelected: state.getIn([reducer, 'selectedIndex']), + keyword: state.getIn([reducer, 'keywordValue']), + open: state.getIn([reducer, 'openFrm']), + showMobileDetail: state.getIn([reducer, 'showMobileDetail']), + messageNotif: state.getIn([reducer, 'notifMsg']), +}); + +const constDispatchToProps = dispatch => ({ + fetchData: bindActionCreators(fetchAction, dispatch), + showDetail: bindActionCreators(showDetailAction, dispatch), + hideDetail: () => dispatch(hideDetailAction), + submit: bindActionCreators(submitAction, dispatch), + edit: bindActionCreators(editAction, dispatch), + add: () => dispatch(addAction), + close: () => dispatch(closeAction), + remove: bindActionCreators(removeAction, dispatch), + favorite: bindActionCreators(addToFavoriteAction, dispatch), + search: bindActionCreators(searchAction, dispatch), + closeNotif: () => dispatch(closeNotifAction), +}); + +const ContactMapped = connect( + mapStateToProps, + constDispatchToProps +)(Contact); + +export default withStyles(styles)(ContactMapped); diff --git a/front/odiparpack/app/containers/Pages/Ecommerce/index.js b/front/odiparpack/app/containers/Pages/Ecommerce/index.js new file mode 100644 index 0000000..8eb15a4 --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Ecommerce/index.js @@ -0,0 +1,117 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import data from 'ba-api/productData'; +import { SearchProduct, ProductGallery, Notification } from 'ba-components'; +import { + fetchAction, + addAction, + removeAction, + checkoutAction, + detailAction, + searchAction, + closeNotifAction +} from 'ba-actions/EcommerceActions'; + +class Ecommerce extends React.Component { + componentDidMount() { + this.props.fetchData(data); + } + + render() { + const title = brand.name + ' - Ecommerce'; + const description = brand.desc; + const { + dataProduct, + handleAddToCart, + dataCart, + removeItem, + checkout, + showDetail, + productIndex, + totalItems, + totalPrice, + search, + keyword, + closeNotif, + messageNotif + } = this.props; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <Notification close={() => closeNotif()} message={messageNotif} /> + <SearchProduct + dataCart={dataCart} + removeItem={removeItem} + checkout={checkout} + totalItems={totalItems} + totalPrice={totalPrice} + search={search} + /> + <ProductGallery + dataProduct={dataProduct} + showDetail={showDetail} + handleAddToCart={handleAddToCart} + productIndex={productIndex} + keyword={keyword} + /> + </div> + ); + } +} + +Ecommerce.propTypes = { + fetchData: PropTypes.func.isRequired, + handleAddToCart: PropTypes.func.isRequired, + removeItem: PropTypes.func.isRequired, + showDetail: PropTypes.func.isRequired, + checkout: PropTypes.func.isRequired, + search: PropTypes.func.isRequired, + keyword: PropTypes.string.isRequired, + dataProduct: PropTypes.object.isRequired, + dataCart: PropTypes.object.isRequired, + productIndex: PropTypes.number.isRequired, + totalItems: PropTypes.number.isRequired, + totalPrice: PropTypes.number.isRequired, + closeNotif: PropTypes.func.isRequired, + messageNotif: PropTypes.string.isRequired, +}; + +const reducer = 'ecommerce'; +const mapStateToProps = state => ({ + force: state, // force state from reducer + keyword: state.getIn([reducer, 'keywordValue']), + dataProduct: state.getIn([reducer, 'productList']), + dataCart: state.getIn([reducer, 'cart']), + productIndex: state.getIn([reducer, 'productIndex']), + totalItems: state.getIn([reducer, 'totalItems']), + totalPrice: state.getIn([reducer, 'totalPrice']), + messageNotif: state.getIn([reducer, 'notifMsg']), +}); + +const constDispatchToProps = dispatch => ({ + fetchData: bindActionCreators(fetchAction, dispatch), + search: bindActionCreators(searchAction, dispatch), + handleAddToCart: bindActionCreators(addAction, dispatch), + removeItem: bindActionCreators(removeAction, dispatch), + showDetail: bindActionCreators(detailAction, dispatch), + checkout: () => dispatch(checkoutAction), + closeNotif: () => dispatch(closeNotifAction), +}); + +const EcommerceMapped = connect( + mapStateToProps, + constDispatchToProps +)(Ecommerce); + +export default EcommerceMapped; diff --git a/front/odiparpack/app/containers/Pages/Email/index.js b/front/odiparpack/app/containers/Pages/Email/index.js new file mode 100644 index 0000000..93285cb --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Email/index.js @@ -0,0 +1,210 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import data from 'ba-api/emailData'; +import { + EmailHeader, + EmailList, + EmailSidebar, + ComposeEmail, + Notification +} from 'ba-components'; +import { + fetchMailAction, + openMailAction, + filterAction, + composeAction, + discardAction, + searchAction, + sendAction, + moveAction, + deleteAction, + toggleStaredAction, + closeNotifAction +} from 'ba-actions/EmailActions'; + +const styles = theme => ({ + root: { + flexGrow: 1, + minHeight: 600, + zIndex: 1, + background: theme.palette.grey[50], + overflow: 'hidden', + display: 'flex', + boxShadow: theme.shadows[2] + } +}); + +// validation functions +const email = value => ( + value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) + ? 'Invalid email' + : '' +); + +class Email extends React.Component { + state = { + to: '', + subject: '', + validMail: '', + mobileOpen: false, + }; + + componentDidMount() { + this.props.fetchData(data); + } + + handleChange = (event, name) => { + if (name === 'to') { + this.setState({ validMail: email(event.target.value) }); + } + this.setState({ + [name]: event.target.value, + }); + }; + + handleReply = (mail) => { + this.props.compose(); + this.setState({ + to: mail.get('name'), + subject: 'Reply: ' + mail.get('subject'), + }); + } + + handleCompose = () => { + this.props.compose(); + this.setState({ + to: ' ', + subject: ' ', + }); + } + + handleDrawerToggle = () => { + this.setState(state => ({ mobileOpen: !state.mobileOpen })); + }; + + render() { + const { + classes, + emailData, + openMail, + goto, + currentPage, + openFrm, + discard, + search, + keyword, + sendEmail, + moveTo, + remove, + toggleStar, + closeNotif, + messageNotif + } = this.props; + const { + to, + subject, + validMail, + mobileOpen + } = this.state; + const title = brand.name + ' - Email'; + const description = brand.desc; + return ( + <div className={classes.root}> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <Notification close={() => closeNotif()} message={messageNotif} /> + <EmailHeader search={search} handleDrawerToggle={this.handleDrawerToggle} /> + <EmailSidebar + compose={this.handleCompose} + goto={goto} + selected={currentPage} + handleDrawerToggle={this.handleDrawerToggle} + mobileOpen={mobileOpen} + /> + <EmailList + emailData={emailData} + openMail={openMail} + filterPage={currentPage} + keyword={keyword} + moveTo={moveTo} + remove={remove} + toggleStar={toggleStar} + reply={this.handleReply} + /> + <ComposeEmail + to={to} + subject={subject} + compose={this.handleCompose} + validMail={validMail} + sendEmail={sendEmail} + inputChange={this.handleChange} + open={openFrm} + closeForm={discard} + /> + </div> + ); + } +} + +Email.propTypes = { + classes: PropTypes.object.isRequired, + emailData: PropTypes.object.isRequired, + fetchData: PropTypes.func.isRequired, + openMail: PropTypes.func.isRequired, + goto: PropTypes.func.isRequired, + compose: PropTypes.func.isRequired, + discard: PropTypes.func.isRequired, + search: PropTypes.func.isRequired, + sendEmail: PropTypes.func.isRequired, + moveTo: PropTypes.func.isRequired, + remove: PropTypes.func.isRequired, + toggleStar: PropTypes.func.isRequired, + keyword: PropTypes.string.isRequired, + currentPage: PropTypes.string.isRequired, + openFrm: PropTypes.bool.isRequired, + closeNotif: PropTypes.func.isRequired, + messageNotif: PropTypes.string.isRequired, +}; + +const reducer = 'email'; +const mapStateToProps = state => ({ + force: state, // force state from reducer + keyword: state.getIn([reducer, 'keywordValue']), + initValues: state.getIn([reducer, 'formValues']), + emailData: state.getIn([reducer, 'inbox']), + currentPage: state.getIn([reducer, 'currentPage']), + openFrm: state.getIn([reducer, 'openFrm']), + messageNotif: state.getIn([reducer, 'notifMsg']), +}); + +const constDispatchToProps = dispatch => ({ + fetchData: bindActionCreators(fetchMailAction, dispatch), + openMail: bindActionCreators(openMailAction, dispatch), + goto: bindActionCreators(filterAction, dispatch), + search: bindActionCreators(searchAction, dispatch), + moveTo: bindActionCreators(moveAction, dispatch), + remove: bindActionCreators(deleteAction, dispatch), + toggleStar: bindActionCreators(toggleStaredAction, dispatch), + compose: () => dispatch(composeAction), + discard: () => dispatch(discardAction), + sendEmail: bindActionCreators(sendAction, dispatch), + closeNotif: () => dispatch(closeNotifAction), +}); + +const EmailMapped = connect( + mapStateToProps, + constDispatchToProps +)(Email); + +export default withStyles(styles)(EmailMapped); diff --git a/front/odiparpack/app/containers/Pages/Error/index.js b/front/odiparpack/app/containers/Pages/Error/index.js new file mode 100644 index 0000000..b408b53 --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Error/index.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { Route } from 'react-router-dom'; +import { ErrorWrap } from 'ba-components'; + +const title = brand.name + ' - Aplication Error'; +const description = brand.desc; + +const Error = () => ( + <Route + render={({ staticContext }) => { + if (staticContext) { + staticContext.status = 404; // eslint-disable-line + } + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <ErrorWrap title="500" desc="Sorry, server goes wrong" /> + </div> + ); + }} + /> +); + +export default Error; diff --git a/front/odiparpack/app/containers/Pages/HelpSupport/ContactForm.js b/front/odiparpack/app/containers/Pages/HelpSupport/ContactForm.js new file mode 100644 index 0000000..2568943 --- /dev/null +++ b/front/odiparpack/app/containers/Pages/HelpSupport/ContactForm.js @@ -0,0 +1,115 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Field, reduxForm } from 'redux-form/immutable'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { PapperBlock } from 'ba-components'; +import { initAction, clearAction } from 'ba-actions/ReduxFormActions'; +import { TextFieldRedux } from 'ba-components/Forms/ReduxFormMUI'; +import { Button } from '@material-ui/core'; +import styles from './helpSupport-jss'; + +// validation functions +const required = value => (value == null ? 'Required' : undefined); +const email = value => ( + value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) + ? 'Invalid email' + : undefined +); + +class ContactForm extends Component { + render() { + const trueBool = true; + const { + classes, + handleSubmit, + pristine, + reset, + submitting, + } = this.props; + return ( + <div className={classes.frmWrap}> + <PapperBlock title="Contact Us" desc="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum."> + <form onSubmit={handleSubmit}> + <div> + <Field + name="name" + component={TextFieldRedux} + placeholder="Name" + label="Name" + validate={required} + required + ref={this.saveRef} + className={classes.field} + /> + </div> + <div> + <Field + name="email" + component={TextFieldRedux} + placeholder="Email Field" + label="Email" + required + validate={[required, email]} + className={classes.field} + /> + </div> + <div className={classes.field}> + <Field + name="message" + className={classes.field} + component={TextFieldRedux} + validate={required} + placeholder="Message" + label="Message" + multiline={trueBool} + rows={4} + /> + </div> + <div> + <Button variant="contained" color="secondary" type="submit" disabled={submitting}> + Submit + </Button> + <Button + type="button" + disabled={pristine || submitting} + onClick={reset} + > + Reset + </Button> + </div> + </form> + </PapperBlock> + </div> + ); + } +} + +ContactForm.propTypes = { + classes: PropTypes.object.isRequired, + handleSubmit: PropTypes.func.isRequired, + reset: PropTypes.func.isRequired, + pristine: PropTypes.bool.isRequired, + submitting: PropTypes.bool.isRequired, +}; + +const mapDispatchToProps = dispatch => ({ + init: bindActionCreators(initAction, dispatch), + clear: () => dispatch(clearAction), +}); + +const ContactFormMapped = reduxForm({ + form: 'immutableExample', +})(ContactForm); + +const reducer = 'initval'; +const FormInit = connect( + state => ({ + force: state, + initialValues: state.getIn([reducer, 'formValues']) + }), + mapDispatchToProps, +)(ContactFormMapped); + +export default withStyles(styles)(FormInit); diff --git a/front/odiparpack/app/containers/Pages/HelpSupport/Qna.js b/front/odiparpack/app/containers/Pages/HelpSupport/Qna.js new file mode 100644 index 0000000..bb8a6ae --- /dev/null +++ b/front/odiparpack/app/containers/Pages/HelpSupport/Qna.js @@ -0,0 +1,141 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import { ExpansionPanel, ExpansionPanelDetails, ExpansionPanelSummary, Typography } from '@material-ui/core'; +import styles from './helpSupport-jss'; + + +class Qna extends React.Component { + state = { + expanded: null, + }; + + handleChange = panel => (event, expanded) => { + this.setState({ + expanded: expanded ? panel : false, + }); + }; + + render() { + const { classes } = this.props; + const { expanded } = this.state; + + return ( + <div> + <ExpansionPanel expanded={expanded === 'panel1'} onChange={this.handleChange('panel1')}> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Pellentesque ac bibendum tortor?</Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + Nulla facilisi. Phasellus sollicitudin nulla et quam mattis feugiat. Aliquam eget + maximus est, id dignissim quam. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + <ExpansionPanel expanded={expanded === 'panel2'} onChange={this.handleChange('panel2')}> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Vivamus sit amet interdum elit?</Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + Donec placerat, lectus sed mattis semper, neque lectus feugiat lectus, varius pulvinar + diam eros in elit. Pellentesque convallis laoreet laoreet. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + <ExpansionPanel expanded={expanded === 'panel3'} onChange={this.handleChange('panel3')}> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Vestibulum nec mi suscipit?</Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + Nunc vitae orci ultricies, auctor nunc in, volutpat nisl. Integer sit amet egestas + eros, vitae egestas augue. Duis vel est augue. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + <ExpansionPanel expanded={expanded === 'panel4'} onChange={this.handleChange('panel4')}> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Cras convallis lacus orci?</Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + Nunc vitae orci ultricies, auctor nunc in, volutpat nisl. Integer sit amet egestas + eros, vitae egestas augue. Duis vel est augue. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + <ExpansionPanel expanded={expanded === 'panel5'} onChange={this.handleChange('panel5')}> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Quisque ut metus sit amet?</Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + enean sit amet magna vel magna fringilla fermentum. Donec sit amet nulla sed arcu pulvinar ultricies commodo id ligula. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + <ExpansionPanel expanded={expanded === 'panel6'} onChange={this.handleChange('panel6')}> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Nulla vehicula leo ut augue tincidunt?</Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + Curabitur egestas consequat lorem, vel fermentum augue porta id. Aliquam lobortis magna neque, gravida consequat velit venenatis at. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + <ExpansionPanel expanded={expanded === 'panel7'} onChange={this.handleChange('panel7')}> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Vivamus sit amet interdum elit?</Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + Donec dignissim, odio ac imperdiet luctus, ante nisl accumsan justo, et venenatis ante metus pellentesque sem. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + <ExpansionPanel expanded={expanded === 'panel8'} onChange={this.handleChange('panel8')}> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Maecenas nisl libero, tincidunt id odio id?</Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + Ut sed eros finibus, placerat orci id, dapibus mauris. Vestibulum consequat hendrerit lacus. In id nisi id neque venenatis molestie. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + <ExpansionPanel expanded={expanded === 'panel9'} onChange={this.handleChange('panel9')}> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Vestibulum nec mi suscipit?</Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + Nunc vitae orci ultricies, auctor nunc in, volutpat nisl. Integer sit amet egestas + eros, vitae egestas augue. Duis vel est augue. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + <ExpansionPanel expanded={expanded === 'panel10'} onChange={this.handleChange('panel10')}> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Cras convallis lacus orci?</Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + Nunc vitae orci ultricies, auctor nunc in, volutpat nisl. Integer sit amet egestas + eros, vitae egestas augue. Duis vel est augue. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + </div> + ); + } +} + +Qna.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Qna); diff --git a/front/odiparpack/app/containers/Pages/HelpSupport/helpSupport-jss.js b/front/odiparpack/app/containers/Pages/HelpSupport/helpSupport-jss.js new file mode 100644 index 0000000..5c954cb --- /dev/null +++ b/front/odiparpack/app/containers/Pages/HelpSupport/helpSupport-jss.js @@ -0,0 +1,49 @@ +const styles = theme => ({ + title: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + color: theme.palette.common.white, + }, + iconTitle: { + position: 'relative', + top: theme.spacing(0.5), + marginRight: theme.spacing(0.5), + }, + heading: { + fontSize: theme.typography.pxToRem(15), + flexBasis: '100%', + fontWeight: 700, + flexShrink: 0, + }, + secondaryHeading: { + fontSize: theme.typography.pxToRem(15), + color: theme.palette.text.secondary, + }, + root: { + width: '100%', + flexGrow: 1, + padding: 30 + }, + field: { + width: '100%', + marginBottom: 20 + }, + fieldBasic: { + width: '100%', + marginBottom: 20, + marginTop: 10 + }, + inlineWrap: { + display: 'flex', + flexDirection: 'row' + }, + buttonInit: { + margin: theme.spacing(4), + textAlign: 'center' + }, + frmWrap: { + marginTop: theme.spacing(1) * -3 + } +}); + +export default styles; diff --git a/front/odiparpack/app/containers/Pages/HelpSupport/index.js b/front/odiparpack/app/containers/Pages/HelpSupport/index.js new file mode 100644 index 0000000..68df97b --- /dev/null +++ b/front/odiparpack/app/containers/Pages/HelpSupport/index.js @@ -0,0 +1,58 @@ +import React from 'react'; +import { PropTypes } from 'prop-types'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import LiveHelp from '@material-ui/icons/LiveHelp'; +import { isWidthUp } from '@material-ui/core/withWidth'; +import { withStyles } from '@material-ui/core/styles'; +import { Typography, withWidth, Grid } from '@material-ui/core'; +import styles from './helpSupport-jss'; +import Qna from './Qna'; +import ContactForm from './ContactForm'; + + +class Settings extends React.Component { + showResult(values) { + setTimeout(() => { + this.setState({ valueForm: values }); + window.alert(`You submitted:\n\n${this.state.valueForm}`); + }, 500); // simulate server latency + } + + render() { + const title = brand.name; + const description = brand.desc; + const { classes, width } = this.props; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <Typography variant="h5" className={classes.title}> + <LiveHelp className={classes.iconTitle} /> + Help & Support + </Typography> + <Grid container spacing={2} direction={isWidthUp('md', width) ? 'row' : 'column-reverse'}> + <Grid item md={6} xs={12}> + <Qna /> + </Grid> + <Grid item md={6} xs={12}> + <ContactForm onSubmit={(values) => this.showResult(values)} /> + </Grid> + </Grid> + </div> + ); + } +} + +Settings.propTypes = { + classes: PropTypes.object.isRequired, + width: PropTypes.string.isRequired, +}; + +export default withStyles(styles)(withWidth()(Settings)); diff --git a/front/odiparpack/app/containers/Pages/Maintenance/index.js b/front/odiparpack/app/containers/Pages/Maintenance/index.js new file mode 100644 index 0000000..8771e51 --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Maintenance/index.js @@ -0,0 +1,88 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Build from '@material-ui/icons/Build'; +import Settings from '@material-ui/icons/SettingsApplications'; +import Warning from '@material-ui/icons/Warning'; +import { Typography, Avatar, Hidden, Paper } from '@material-ui/core'; + +const styles = theme => ({ + root: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '100%' + }, + paper: { + margin: 'auto', + padding: 40, + width: '90%', + [theme.breakpoints.up('sm')]: { + width: 600, + height: 300, + }, + textAlign: 'center' + }, + artwork: { + display: 'flex', + justifyContent: 'center', + marginBottom: 30 + }, + icon: { + margin: '10px 20px', + background: theme.palette.secondary.main, + color: theme.palette.common.white, + width: 100, + height: 100, + '& svg': { + fontSize: 64, + }, + }, +}); + +class Maintenance extends React.Component { + render() { + const title = brand.name + ' - Maintenance'; + const description = brand.desc; + const { classes } = this.props; + return ( + <div className={classes.root}> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <div className={classes.container}> + <Paper className={classes.paper}> + <div className={classes.artwork}> + <Avatar className={classes.icon}><Build /></Avatar> + <Hidden xsDown> + <Avatar className={classes.icon}><Warning /></Avatar> + </Hidden> + <Hidden xsDown> + <Avatar className={classes.icon}><Settings /></Avatar> + </Hidden> + </div> + <Typography variant="h4" gutterBottom>Website under maintenance</Typography> + <Typography variant="subtitle1"> +Our website is under maintenance. We + {"'"} +ll be back shortly + </Typography> + </Paper> + </div> + </div> + ); + } +} + +Maintenance.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Maintenance); diff --git a/front/odiparpack/app/containers/Pages/Photos/index.js b/front/odiparpack/app/containers/Pages/Photos/index.js new file mode 100644 index 0000000..7068ed5 --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Photos/index.js @@ -0,0 +1,27 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import imgData from 'ba-api/imgDataMasonry'; +import { PhotoGallery } from 'ba-components'; + +class Photos extends React.Component { + render() { + const title = brand.name + ' - Photo Gallery'; + const description = brand.desc; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PhotoGallery imgData={imgData} /> + </div> + ); + } +} + +export default Photos; diff --git a/front/odiparpack/app/containers/Pages/Settings/DetailSettings.js b/front/odiparpack/app/containers/Pages/Settings/DetailSettings.js new file mode 100644 index 0000000..25bad64 --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Settings/DetailSettings.js @@ -0,0 +1,203 @@ +import React from 'react'; +import { PropTypes } from 'prop-types'; +import classNames from 'classnames'; +import SettingsIcon from '@material-ui/icons/SettingsApplications'; +import CloseIcon from '@material-ui/icons/Close'; +import { withStyles } from '@material-ui/core/styles'; +import { + AppBar, + Grid, + Dialog, + Toolbar, + ListItemText, + ListItem, + List, + ListItemSecondaryAction, + Divider, + IconButton, + Typography, + Button, + Switch, + Slide, + InputLabel, + MenuItem, + FormControl, + Select, + Checkbox, + TextField, +} from '@material-ui/core'; +import styles from './settings-jss'; + + +const Transition = React.forwardRef(function Transition(props, ref) { // eslint-disable-line + return <Slide direction="up" ref={ref} {...props} />; +}); + +class DetailSettings extends React.Component { + state = { + checked: ['switch', 'check2'], + option: '', + }; + + handleToggle = value => () => { + const { checked } = this.state; + const currentIndex = checked.indexOf(value); + const newChecked = [...checked]; + + if (currentIndex === -1) { + newChecked.push(value); + } else { + newChecked.splice(currentIndex, 1); + } + + this.setState({ + checked: newChecked, + }); + }; + + handleChange = name => event => { + this.setState({ + [name]: event.target.value, + }); + }; + + handleChangeSelection = event => { + this.setState({ [event.target.name]: event.target.value }); + }; + + render() { + const { classes, open, handleClose } = this.props; + return ( + <Dialog + fullScreen + open={open} + onClose={handleClose} + TransitionComponent={Transition} + > + <AppBar className={classes.appBar}> + <Toolbar> + <IconButton color="inherit" onClick={handleClose} aria-label="Close"> + <CloseIcon /> + </IconButton> + <Typography variant="h6" color="inherit" className={classes.flex}> + Setting + </Typography> + <Button color="inherit" onClick={handleClose}> + done + </Button> + </Toolbar> + </AppBar> + <Grid container justify="center"> + <Grid item md={8} xs={12}> + <List> + <ListItem> + <ListItemText primary="Switch input" secondary="Odio ac imperdiet luctus" /> + <ListItemSecondaryAction> + <Switch + onChange={this.handleToggle('switch')} + checked={this.state.checked.indexOf('switch') !== -1} + /> + </ListItemSecondaryAction> + </ListItem> + <Divider /> + <ListItem> + <ListItemText primary="Another switch input" secondary="Lorem Ipsum" /> + <ListItemSecondaryAction> + <Switch + onChange={this.handleToggle('switch2')} + checked={this.state.checked.indexOf('switch2') !== -1} + /> + </ListItemSecondaryAction> + </ListItem> + <Divider /> + <ListItem> + <ListItemText primary="Checkbox input" secondary="Dolor sit amet" /> + <ListItemSecondaryAction> + <Checkbox + onChange={this.handleToggle('check')} + checked={this.state.checked.indexOf('check') !== -1} + /> + </ListItemSecondaryAction> + </ListItem> + <Divider /> + <ListItem> + <ListItemText primary="Another checkbox input" secondary="Donec dignissim" /> + <ListItemSecondaryAction> + <Checkbox + onChange={this.handleToggle('check2')} + checked={this.state.checked.indexOf('check2') !== -1} + /> + </ListItemSecondaryAction> + </ListItem> + <Divider /> + <ListItem> + <ListItemText primary="Selection field" secondary="Nam posuere accumsan porta" /> + <ListItemSecondaryAction> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="age-simple">Option</InputLabel> + <Select + value={this.state.option} + onChange={this.handleChangeSelection} + inputProps={{ + name: 'option', + id: 'opt-simple', + }} + > + <MenuItem value=""> + <em>None</em> + </MenuItem> + <MenuItem value={10}>Option Ten</MenuItem> + <MenuItem value={20}>Option Twenty</MenuItem> + <MenuItem value={30}>Option Thirty</MenuItem> + </Select> + </FormControl> + </ListItemSecondaryAction> + </ListItem> + <Divider /> + <ListItem> + <ListItemText primary="Input text" secondary="Donec dignissim, odio ac imperdiet luctus" /> + <ListItemSecondaryAction> + <TextField + id="name" + label="Name" + className={classes.textField} + value={this.state.name} + onChange={this.handleChange('name')} + margin="normal" + /> + </ListItemSecondaryAction> + </ListItem> + <Divider /> + <ListItem> + <ListItemText primary="Input text" secondary="Donec dignissim, odio ac imperdiet luctus" /> + <ListItemSecondaryAction> + <Button variant="outlined" size="small" color="secondary" className={classes.button}> + Action Button + </Button> + </ListItemSecondaryAction> + </ListItem> + <Divider /> + <ListItem> + <ListItemText primary="Input text" secondary="Donec dignissim, odio ac imperdiet luctus" /> + <ListItemSecondaryAction> + <Button variant="outlined" size="small" color="secondary"> + <SettingsIcon className={classNames(classes.leftIcon, classes.iconSmall)} /> + Action Button Icon + </Button> + </ListItemSecondaryAction> + </ListItem> + </List> + </Grid> + </Grid> + </Dialog> + ); + } +} + +DetailSettings.propTypes = { + classes: PropTypes.object.isRequired, + open: PropTypes.bool.isRequired, + handleClose: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(DetailSettings); diff --git a/front/odiparpack/app/containers/Pages/Settings/index.js b/front/odiparpack/app/containers/Pages/Settings/index.js new file mode 100644 index 0000000..2ab94d2 --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Settings/index.js @@ -0,0 +1,117 @@ +import React from 'react'; +import { PropTypes } from 'prop-types'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import SearchIcon from '@material-ui/icons/Search'; +import SettingsIcon from '@material-ui/icons/SettingsApplications'; +import { withStyles } from '@material-ui/core/styles'; +import settingList from 'ba-api/settingList'; +import { AppBar, Grid, Toolbar, Typography, Button, Icon } from '@material-ui/core'; +import DetailSettings from './DetailSettings'; +import styles from './settings-jss'; + + +class Settings extends React.Component { + state = { + open: false, + checked: ['switch', 'check2'], + keyword: '' + }; + + handleToggle = value => () => { + const { checked } = this.state; + const currentIndex = checked.indexOf(value); + const newChecked = [...checked]; + + if (currentIndex === -1) { + newChecked.push(value); + } else { + newChecked.splice(currentIndex, 1); + } + + this.setState({ + checked: newChecked, + }); + }; + + handleChange = name => event => { + this.setState({ + [name]: event.target.value, + }); + }; + + handleClickOpen = () => { + this.setState({ open: true }); + }; + + handleClose = () => { + this.setState({ open: false }); + }; + + handleSearch = event => { + this.setState({ keyword: event.target.value.toLowerCase() }); + } + + render() { + const title = brand.name; + const description = brand.desc; + const { classes } = this.props; + const { keyword } = this.state; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <Typography variant="h5" className={classes.title}> + <SettingsIcon className={classes.iconTitle} /> + Appication Settings + </Typography> + <AppBar position="static" color="inherit" className={classes.searchSettings}> + <Toolbar> + <div className={classes.flex}> + <div className={classes.wrapper}> + <div className={classes.search}> + <SearchIcon /> + </div> + <input className={classes.input} placeholder="Find a setting" onChange={(event) => this.handleSearch(event)} /> + </div> + </div> + </Toolbar> + </AppBar> + <section className={classes.settingList}> + <Grid container spacing={2}> + {settingList.map(menu => { + const rawKey = menu.name + menu.caption; + if (rawKey.toLowerCase().indexOf(keyword) === -1) { + return false; + } + return ( + <Grid item md={3} sm={4} xs={12} key={menu.name}> + <Button variant="outlined" onClick={this.handleClickOpen} color="secondary" className={classes.button}> + <Icon className={classes.icon}>{menu.icon}</Icon> + {menu.name} + <Typography variant="caption" noWrap className={classes.info}> + {menu.caption} + </Typography> + </Button> + </Grid> + ); + })} + </Grid> + </section> + <DetailSettings open={this.state.open} handleClose={this.handleClose} /> + </div> + ); + } +} + +Settings.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Settings); diff --git a/front/odiparpack/app/containers/Pages/Settings/settings-jss.js b/front/odiparpack/app/containers/Pages/Settings/settings-jss.js new file mode 100644 index 0000000..5424d4e --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Settings/settings-jss.js @@ -0,0 +1,87 @@ +const styles = theme => ({ + appBar: { + position: 'relative', + }, + flex: { + flex: 1, + }, + title: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + color: theme.palette.common.white, + }, + searchSettings: { + marginBottom: theme.spacing(4), + }, + wrapper: { + fontFamily: theme.typography.fontFamily, + position: 'relative', + marginRight: theme.spacing(2), + marginLeft: theme.spacing(1), + borderRadius: 2, + display: 'block', + }, + search: { + width: 'auto', + height: '100%', + position: 'absolute', + pointerEvents: 'none', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + input: { + font: 'inherit', + padding: `${theme.spacing(1)}px ${theme.spacing(1)}px ${theme.spacing(1)}px ${theme.spacing(4)}px`, + border: 0, + display: 'block', + verticalAlign: 'middle', + whiteSpace: 'normal', + background: 'none', + margin: 0, // Reset for Safari + color: 'inherit', + width: '100%', + '&:focus': { + outline: 0, + }, + }, + iconTitle: { + position: 'relative', + marginRight: theme.spacing(0.5), + }, + button: { + display: 'block', + width: '100%', + background: theme.palette.grey[50], + '&:hover': { + background: theme.palette.secondary.light + }, + '& $icon': { + margin: '0 auto', + display: 'block', + fontSize: 64 + }, + '& $info': { + display: 'block', + textTransform: 'none', + color: theme.palette.grey[500] + } + }, + info: {}, + icon: {}, + formControl: { + margin: theme.spacing(1), + minWidth: 120, + }, + selectEmpty: { + marginTop: theme.spacing(2), + }, + iconSmall: { + fontSize: 20, + }, + leftIcon: { + marginRight: theme.spacing(1), + }, +}); + +export default styles; diff --git a/front/odiparpack/app/containers/Pages/SocialMedia/index.js b/front/odiparpack/app/containers/Pages/SocialMedia/index.js new file mode 100644 index 0000000..3a5ae2c --- /dev/null +++ b/front/odiparpack/app/containers/Pages/SocialMedia/index.js @@ -0,0 +1,110 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import data from 'ba-api/timelineData'; +import { + fetchAction, + postAction, + toggleLikeAction, + fetchCommentAction, + postCommentAction, + closeNotifAction +} from 'ba-actions/SocmedActions'; +import { Timeline, WritePost, SideSection, Notification } from 'ba-components'; +import { Grid } from '@material-ui/core'; + +class SocialMedia extends React.Component { + componentDidMount() { + this.props.fetchData(data); + } + + render() { + const title = brand.name + ' - Social Media'; + const description = brand.desc; + const { + dataProps, + submitPost, + submitLike, + submitComment, + fetchComment, + commentIndex, + closeNotif, + messageNotif, + } = this.props; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <Notification close={() => closeNotif()} message={messageNotif} /> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={3} + > + <Grid item md={8} xs={12}> + <div> + <WritePost submitPost={submitPost} /> + <Timeline + dataTimeline={dataProps} + onlike={submitLike} + submitComment={submitComment} + fetchComment={fetchComment} + commentIndex={commentIndex} + /> + </div> + </Grid> + <Grid item md={4} xs={12}> + <SideSection /> + </Grid> + </Grid> + </div> + ); + } +} + +SocialMedia.propTypes = { + fetchData: PropTypes.func.isRequired, + submitPost: PropTypes.func.isRequired, + submitLike: PropTypes.func.isRequired, + submitComment: PropTypes.func.isRequired, + dataProps: PropTypes.object.isRequired, + fetchComment: PropTypes.func.isRequired, + commentIndex: PropTypes.number.isRequired, + closeNotif: PropTypes.func.isRequired, + messageNotif: PropTypes.string.isRequired, +}; + +const reducer = 'socmed'; +const mapStateToProps = state => ({ + force: state, // force state from reducer + dataProps: state.getIn([reducer, 'dataTimeline']), + commentIndex: state.getIn([reducer, 'commentIndex']), + messageNotif: state.getIn([reducer, 'notifMsg']), +}); + +const constDispatchToProps = dispatch => ({ + fetchData: bindActionCreators(fetchAction, dispatch), + submitPost: bindActionCreators(postAction, dispatch), + submitComment: bindActionCreators(postCommentAction, dispatch), + submitLike: bindActionCreators(toggleLikeAction, dispatch), + fetchComment: bindActionCreators(fetchCommentAction, dispatch), + closeNotif: () => dispatch(closeNotifAction), +}); + +const SocialMediaMapped = connect( + mapStateToProps, + constDispatchToProps +)(SocialMedia); + +export default SocialMediaMapped; diff --git a/front/odiparpack/app/containers/Pages/Standalone/LoginDedicated.js b/front/odiparpack/app/containers/Pages/Standalone/LoginDedicated.js new file mode 100644 index 0000000..9cba8fb --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Standalone/LoginDedicated.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { PropTypes } from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import brand from 'ba-api/brand'; +import logo from 'ba-images/logo.svg'; +import styles from 'ba-containers/Templates/appStyles-jss'; +import { Hidden } from '@material-ui/core'; +import Login from '../Users/Login'; + +class LoginDedicated extends React.Component { + render() { + const { classes } = this.props; + return ( + <div className={classes.appFrameOuter}> + <main className={classes.outerContent} id="mainContent"> + <Hidden mdUp> + <div className={classes.brand}> + <img src={logo} alt={brand.name} /> + <h3>{brand.name}</h3> + </div> + </Hidden> + <Login /> + </main> + </div> + ); + } +} + +LoginDedicated.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default (withStyles(styles)(LoginDedicated)); diff --git a/front/odiparpack/app/containers/Pages/Standalone/NotFoundDedicated.js b/front/odiparpack/app/containers/Pages/Standalone/NotFoundDedicated.js new file mode 100644 index 0000000..3d78b32 --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Standalone/NotFoundDedicated.js @@ -0,0 +1,24 @@ +import React from 'react'; +import { PropTypes } from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import styles from 'ba-containers/Templates/appStyles-jss'; +import Error from './../Error'; + +class NotFoundDedicated extends React.Component { + render() { + const { classes } = this.props; + return ( + <div className={classes.appFrameOuter}> + <main className={classes.outerContent} id="mainContent"> + <Error /> + </main> + </div> + ); + } +} + +NotFoundDedicated.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default (withStyles(styles)(NotFoundDedicated)); diff --git a/front/odiparpack/app/containers/Pages/UserProfile/index.js b/front/odiparpack/app/containers/Pages/UserProfile/index.js new file mode 100644 index 0000000..2065f68 --- /dev/null +++ b/front/odiparpack/app/containers/Pages/UserProfile/index.js @@ -0,0 +1,131 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import dummy from 'ba-api/dummyContents'; +import AccountCircle from '@material-ui/icons/AccountCircle'; +import SupervisorAccount from '@material-ui/icons/SupervisorAccount'; +import Favorite from '@material-ui/icons/Favorite'; +import PhotoLibrary from '@material-ui/icons/PhotoLibrary'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import data from 'ba-api/timelineData'; +import { fetchAction } from 'ba-actions/SocmedActions'; +import { + Cover, + About, + Connection, + Favorites, + Albums +} from 'ba-components'; + +import { AppBar, Tabs, Tab, Hidden } from '@material-ui/core'; + +function TabContainer(props) { + return ( + <div style={{ paddingTop: 8 * 3 }}> + {props.children} + </div> + ); +} + +TabContainer.propTypes = { + children: PropTypes.node.isRequired, +}; + +class UserProfile extends React.Component { + state = { + value: 0, + }; + + componentDidMount() { + this.props.fetchData(data); + } + + handleChange = (event, value) => { + this.setState({ value }); + }; + + render() { + const title = brand.name + ' - Profile'; + const description = brand.desc; + const { dataProps } = this.props; + const { value } = this.state; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <Cover + coverImg="/images/material_bg.svg" + avatar={dummy.user.avatar} + name={dummy.user.name} + desc="Lorem ipsum dolor sit amet, consectetur adipiscing elit." + /> + <AppBar position="static" color="default"> + <Hidden mdUp> + <Tabs + value={this.state.value} + onChange={this.handleChange} + variant="fullWidth" + centered + indicatorColor="primary" + textColor="primary" + > + <Tab icon={<AccountCircle />} /> + <Tab icon={<SupervisorAccount />} /> + <Tab icon={<Favorite />} /> + <Tab icon={<PhotoLibrary />} /> + </Tabs> + </Hidden> + <Hidden smDown> + <Tabs + value={this.state.value} + onChange={this.handleChange} + variant="fullWidth" + centered + indicatorColor="primary" + textColor="primary" + > + <Tab icon={<AccountCircle />} label="ABOUT" /> + <Tab icon={<SupervisorAccount />} label="20 CONNECTIONS" /> + <Tab icon={<Favorite />} label="18 FAVORITES" /> + <Tab icon={<PhotoLibrary />} label="4 ALBUMS" /> + </Tabs> + </Hidden> + </AppBar> + {value === 0 && <TabContainer><About data={dataProps} /></TabContainer>} + {value === 1 && <TabContainer><Connection /></TabContainer>} + {value === 2 && <TabContainer><Favorites /></TabContainer>} + {value === 3 && <TabContainer><Albums /></TabContainer>} + </div> + ); + } +} + +UserProfile.propTypes = { + dataProps: PropTypes.object.isRequired, + fetchData: PropTypes.func.isRequired, +}; + +const reducer = 'socmed'; +const mapStateToProps = state => ({ + force: state, // force state from reducer + dataProps: state.getIn([reducer, 'dataTimeline']) +}); + +const constDispatchToProps = dispatch => ({ + fetchData: bindActionCreators(fetchAction, dispatch) +}); + +const UserProfileMapped = connect( + mapStateToProps, + constDispatchToProps +)(UserProfile); + +export default UserProfileMapped; diff --git a/front/odiparpack/app/containers/Pages/Users/LockScreen.js b/front/odiparpack/app/containers/Pages/Users/LockScreen.js new file mode 100644 index 0000000..2752ae6 --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Users/LockScreen.js @@ -0,0 +1,55 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { LockForm } from 'ba-components'; +import styles from 'ba-components/Forms/user-jss'; +import { Grid } from '@material-ui/core'; + +class ResetPassword extends React.Component { + state = { + valueForm: [] + } + + submitForm(values) { + setTimeout(() => { + this.setState({ valueForm: values }); + console.log(`You submitted:\n\n${this.state.valueForm}`); + window.location.href = '/app'; + }, 500); // simulate server latency + } + + render() { + const title = brand.name + ' - Lock Screen'; + const description = brand.desc; + const { classes } = this.props; + return ( + <div className={classes.root}> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <div className={classes.container}> + <Grid container spacing={3} alignItems="center" direction="row" justify="center"> + <Grid item md={4} xs={11}> + {/* ----------------------------------------------------------------------*/} + {/* Load Login Form */} + <LockForm onSubmit={(values) => this.submitForm(values)} /> + </Grid> + </Grid> + </div> + </div> + ); + } +} + +ResetPassword.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ResetPassword); diff --git a/front/odiparpack/app/containers/Pages/Users/Login.js b/front/odiparpack/app/containers/Pages/Users/Login.js new file mode 100644 index 0000000..300f7b4 --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Users/Login.js @@ -0,0 +1,85 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Type from 'ba-styles/Typography.scss'; +import ArrowForward from '@material-ui/icons/ArrowForward'; +import logo from 'ba-images/logo.svg'; +import { LoginForm } from 'ba-components'; +import styles from 'ba-components/Forms/user-jss'; + +import { Grid, Hidden, Typography } from '@material-ui/core'; + +class Login extends React.Component { + state = { + valueForm: [] + } + + submitForm(values) { + setTimeout(() => { + this.setState({ valueForm: values }); + console.log(`You submitted:\n\n${this.state.valueForm}`); + window.location.href = '/app'; + }, 500); // simulate server latency + } + + render() { + const title = brand.name + ' - Login'; + const description = brand.desc; + const { classes } = this.props; + return ( + <div className={classes.root}> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <div className={classes.container}> + <Grid container spacing={3} alignItems="center" direction="row" justify="center"> + <Grid item container justify="center" spacing={0} className={classes.loginWrap}> + <Hidden smDown> + <Grid item md={6} className={classes.welcomeWrap}> + {/* Welcome Login */} + <div className={classes.welcome}> + <div className={classes.welcomeContent}> + <div className={classes.brand}> + <img src={logo} alt={brand.name} /> + <h3>{brand.name}</h3> + </div> + <Typography variant="h3"> + <span className={Type.light}>Hello there,</span> + </Typography> + <Typography variant="h6" className={classes.brandText}> + <span className={Type.regular}> + welcome to + {' '} + {brand.name} + </span> + </Typography> + </div> + <ArrowForward className={classes.decoBottom} /> + </div> + </Grid> + </Hidden> + <Grid item md={6} sm={8} xs={11}> + {/* ----------------------------------------------------------------------*/} + {/* Load Login Form */} + <LoginForm onSubmit={(values) => this.submitForm(values)} /> + </Grid> + </Grid> + </Grid> + </div> + </div> + ); + } +} + +Login.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Login); diff --git a/front/odiparpack/app/containers/Pages/Users/Register.js b/front/odiparpack/app/containers/Pages/Users/Register.js new file mode 100644 index 0000000..c139068 --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Users/Register.js @@ -0,0 +1,78 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Type from 'ba-styles/Typography.scss'; +import ArrowForward from '@material-ui/icons/ArrowForward'; +import brand from 'ba-api/brand'; +import logo from 'ba-images/logo.svg'; +import { RegisterForm } from 'ba-components'; +import styles from 'ba-components/Forms/user-jss'; + +import { Grid, Hidden, Typography } from '@material-ui/core'; + +class Login extends React.Component { + state = { + valueForm: [] + } + + submitForm(values) { + setTimeout(() => { + this.setState({ valueForm: values }); + console.log(`You submitted:\n\n${this.state.valueForm}`); + window.location.href = '/app'; + }, 500); // simulate server latency + } + + render() { + const title = brand.name + ' - Register'; + const description = brand.desc; + const { classes } = this.props; + return ( + <div className={classes.root}> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <div className={classes.container}> + <Grid container spacing={3} alignItems="center" direction="row" justify="center"> + <Grid item container justify="center" spacing={0} className={classes.loginWrap}> + <Hidden smDown> + <Grid item md={6} className={classes.welcomeWrap}> + {/* Welcome Login */} + <div className={classes.welcome}> + <div className={classes.welcomeContent}> + <div className={classes.brand}> + <img src={logo} alt={brand.name} /> + <h3>{brand.name}</h3> + </div> + <Typography variant="h4"> + <span className={Type.light}>Nice to meet You :)</span> + </Typography> + </div> + <ArrowForward className={classes.decoBottom} /> + </div> + </Grid> + </Hidden> + <Grid item md={6} sm={8} xs={11}> + {/* ----------------------------------------------------------------------*/} + {/* Load Register Form */} + <RegisterForm onSubmit={(values) => this.submitForm(values)} /> + </Grid> + </Grid> + </Grid> + </div> + </div> + ); + } +} + +Login.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Login); diff --git a/front/odiparpack/app/containers/Pages/Users/ResetPassword.js b/front/odiparpack/app/containers/Pages/Users/ResetPassword.js new file mode 100644 index 0000000..e80253d --- /dev/null +++ b/front/odiparpack/app/containers/Pages/Users/ResetPassword.js @@ -0,0 +1,54 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { ResetForm } from 'ba-components'; +import styles from 'ba-components/Forms/user-jss'; +import { Grid } from '@material-ui/core'; + +class ResetPassword extends React.Component { + state = { + valueForm: [] + } + + submitForm(values) { + setTimeout(() => { + this.setState({ valueForm: values }); + console.log(`You submitted:\n\n${this.state.valueForm}`); + }, 500); // simulate server latency + } + + render() { + const title = brand.name + ' - Reset Password'; + const description = brand.desc; + const { classes } = this.props; + return ( + <div className={classes.root}> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <div className={classes.container}> + <Grid container spacing={3} alignItems="center" direction="row" justify="center"> + <Grid item md={6} xs={11}> + {/* ----------------------------------------------------------------------*/} + {/* Load Login Form */} + <ResetForm onSubmit={(values) => this.submitForm(values)} /> + </Grid> + </Grid> + </div> + </div> + ); + } +} + +ResetPassword.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ResetPassword); diff --git a/front/odiparpack/app/containers/Parent/index.js b/front/odiparpack/app/containers/Parent/index.js new file mode 100644 index 0000000..50067c3 --- /dev/null +++ b/front/odiparpack/app/containers/Parent/index.js @@ -0,0 +1,72 @@ +import React from 'react'; +import { PropTypes } from 'prop-types'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import { Link } from 'react-router-dom'; +import MenuContent from 'ba-api/menu'; +import { PapperBlock } from 'ba-components'; +import { Button } from '@material-ui/core'; + +const styles = { + link: { + display: 'block', + textTransform: 'capitalize' + } +}; + +function sortByKey(array, key) { + return array.sort((a, b) => { + const x = a[key]; const y = b[key]; + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + }); +} + +class Parent extends React.Component { + render() { + const title = brand.name; + const description = brand.desc; + const { classes } = this.props; + // Get Path Location + let parts = this.props.history.location.pathname.split('/'); + const place = parts[parts.length - 1]; + parts = parts.slice(1, parts.length - 1); + const menuItems = MenuContent + .find(obj => ( + obj.key === place + )); + const getMenus = menuArray => menuArray.map((item, index) => ( + <Button + key={index.toString()} + color="primary" + component={Link} + className={classes.link} + to={item.link} + > + {item.name} + </Button> + )); + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title={place} desc=""> + {menuItems !== undefined && getMenus(sortByKey(menuItems.child, 'key'))} + </PapperBlock> + </div> + ); + } +} + +Parent.propTypes = { + classes: PropTypes.object.isRequired, + history: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Parent); diff --git a/front/odiparpack/app/containers/Tables/AdvancedTable.js b/front/odiparpack/app/containers/Tables/AdvancedTable.js new file mode 100644 index 0000000..4d1b60a --- /dev/null +++ b/front/odiparpack/app/containers/Tables/AdvancedTable.js @@ -0,0 +1,46 @@ +import React, { Component } from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { AdvTableDemo, AdvFilter } from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class AdvancedTable extends Component { + render() { + const title = brand.name + ' - Table'; + const description = brand.desc; + const docSrc = 'containers/Tables/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Basic Data Table" desc="This is default example from Material UI. It Demonstrates the use of Checkbox and clickable rows for selection, with a custom Toolbar. It uses the TableSortLabel component to help style column headings."> + <div> + <AdvTableDemo /> + <SourceReader componentName={docSrc + 'AdvTableDemo.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Advanced Data Table" desc="It uses npm mui-datatables. It's easy to use, you just describe columns and data collection. After that it will magically build features such as filtering, viewing / hiding columns, exporting to CSV downloads, printing, and more."> + <div> + <AdvFilter /> + <SourceReader componentName={docSrc + 'AdvFilter.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default withStyles(styles)(AdvancedTable); diff --git a/front/odiparpack/app/containers/Tables/BasicTable.js b/front/odiparpack/app/containers/Tables/BasicTable.js new file mode 100644 index 0000000..a6f81e1 --- /dev/null +++ b/front/odiparpack/app/containers/Tables/BasicTable.js @@ -0,0 +1,110 @@ +import React, { Component } from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Markdown from 'react-markdown'; +import codeStyle from 'ba-styles/Code.scss'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Grid } from '@material-ui/core'; +import { + StrippedTable, + HoverTable, + BorderedTable, + TrackingTable, + StatusLabel, + StatusColorRow, + EmptyTable +} from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class BasicTable extends Component { + render() { + const { classes } = this.props; + const title = brand.name + ' - Table'; + const description = brand.desc; + const docSrc = 'containers/Tables/demos/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Stripped Table" desc="They (allegedly) aid usability in reading tabular data by offering the user a coloured means of separating and differentiating rows from one another"> + <div> + <Markdown className={codeStyle.codePre} source="Simply by ``` import tableStyles from 'ba-components/Table.scss'; ``` then add the classNames ``` tableStyles.stripped ```" /> + <StrippedTable /> + <SourceReader componentName={docSrc + 'StrippedTable.js'} /> + </div> + </PapperBlock> + <div className={classes.root}> + <Grid container spacing={3}> + <Grid item md={6} xs={12}> + <PapperBlock title="Hover Table" desc="Hover tables is addition option that allows you to see what row you selected"> + <div> + <Markdown className={codeStyle.codePre} source="Simply by ``` import tableStyles from 'ba-components/Table.scss'; ``` then add the classNames ``` tableStyles.hover ```" /> + <HoverTable /> + <SourceReader componentName={docSrc + 'HoverTable.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6} xs={12}> + <PapperBlock title="Bordered Table" desc="Old is gold, here is old fashion bordered table, we tweaked it a bit so that the headings looks more prominent."> + <div> + <Markdown className={codeStyle.codePre} source="Simply by ``` import tableStyles from 'ba-components/Table.scss'; ``` then add the classNames ``` tableStyles.bordered ```" /> + <BorderedTable /> + <SourceReader componentName={docSrc + 'BorderedTable.js'} /> + </div> + </PapperBlock> + </Grid> + </Grid> + </div> + <div className={classes.root}> + <Grid container spacing={3}> + <Grid item md={6} xs={12}> + <PapperBlock title="Status Table with Label" desc=""> + <div> + <StatusLabel /> + <SourceReader componentName={docSrc + 'StatusLabel.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6} xs={12}> + <PapperBlock title="Coloured Row" desc=""> + <div> + <StatusColorRow /> + <SourceReader componentName={docSrc + 'StatusColorRow.js'} /> + </div> + </PapperBlock> + </Grid> + </Grid> + </div> + <div className={classes.root}> + <TrackingTable /> + <SourceReader componentName={docSrc + 'TrackingTable.js'} /> + </div> + <PapperBlock title="Empty Table" desc=""> + <div> + <EmptyTable /> + <SourceReader componentName={docSrc + 'EmptyTable.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +BasicTable.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(BasicTable); diff --git a/front/odiparpack/app/containers/Tables/CrudTable.js b/front/odiparpack/app/containers/Tables/CrudTable.js new file mode 100644 index 0000000..7ee18dd --- /dev/null +++ b/front/odiparpack/app/containers/Tables/CrudTable.js @@ -0,0 +1,57 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Paper } from '@material-ui/core'; +import { CrudTableDemo, CrudTbFormDemo } from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class CrudTablePage extends Component { + render() { + const title = brand.name + ' - Table'; + const description = brand.desc; + const docSrc = 'containers/Tables/demos/'; + const { classes } = this.props; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="CRUD Table" desc="The CRUD Table supports editing features including creating, updating and deleting rows. The editing state contains information about rows currently being edited, changes applied to a particular row, and rows that have been deleted and created."> + <div> + <Paper className={classes.root}> + <CrudTableDemo /> + </Paper> + <SourceReader componentName={docSrc + 'CrudTableDemo.js'} /> + </div> + </PapperBlock> + <PapperBlock title="CRUD Table with Redux Form" desc="In the CRUD Table Form mode allow You to create or edit via dedicated form(Redux Form). The design form itself inspired by Gmail with floating design and it can be expanded become popup mode"> + <div> + <Paper className={classes.root}> + <CrudTbFormDemo /> + </Paper> + <SourceReader componentName={docSrc + 'CrudTbFormDemo.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +CrudTablePage.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CrudTablePage); diff --git a/front/odiparpack/app/containers/Tables/TablePlayground.js b/front/odiparpack/app/containers/Tables/TablePlayground.js new file mode 100644 index 0000000..30382bb --- /dev/null +++ b/front/odiparpack/app/containers/Tables/TablePlayground.js @@ -0,0 +1,419 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import tableStyles from 'ba-styles/Table.scss'; +import EnhancedTableHead from 'ba-components/Tables/tableParts/TableHeader'; +import EnhancedTableToolbar from 'ba-components/Tables/tableParts/TableToolbar'; +import { PapperBlock } from 'ba-components'; + +import { + Grid, + FormControl, + FormLabel, + FormControlLabel, + FormGroup, + RadioGroup, + Radio, + Table, + TableBody, + TableCell, + TableRow, + TablePagination, + Paper, + Checkbox, +} from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '100%', + marginTop: theme.spacing(3), + overflowX: 'auto', + }, + demo: { + height: 240, + }, + paper: { + padding: theme.spacing(2), + height: '100%', + backgroundColor: theme.palette.secondary.light, + }, + control: { + padding: theme.spacing(2), + }, + settings: { + padding: 30 + }, +}); + +let counter = 0; +function createData(name, calories, fat, carbs, protein) { + counter += 1; + return { + id: counter, + name, + calories, + fat, + carbs, + protein + }; +} + +class InteractiveGrid extends React.Component { + constructor(props, context) { + super(props, context); + + this.state = { + order: 'asc', + orderBy: 'calories', + selected: [], + columnData: [ + { + id: 'name', + numeric: false, + disablePadding: false, + label: 'Dessert (100g serving)' + }, { + id: 'calories', + numeric: true, + disablePadding: false, + label: 'Calories' + }, { + id: 'fat', + numeric: true, + disablePadding: false, + label: 'Fat (g)' + }, { + id: 'carbs', + numeric: true, + disablePadding: false, + label: 'Carbs (g)' + }, { + id: 'protein', + numeric: true, + disablePadding: false, + label: 'Protein (g)' + }, + ], + data: [ + createData('Cupcake', 305, 3.7, 67, 4.3), + createData('Donut', 452, 25.0, 51, 4.9), + createData('Eclair', 262, 16.0, 24, 6.0), + createData('Frozen yoghurt', 159, 6.0, 24, 4.0), + createData('Gingerbread', 356, 16.0, 49, 3.9), + createData('Honeycomb', 408, 3.2, 87, 6.5), + createData('Ice cream sandwich', 237, 9.0, 37, 4.3), + createData('Jelly Bean', 375, 0.0, 94, 0.0), + createData('KitKat', 518, 26.0, 65, 7.0), + createData('Lollipop', 392, 0.2, 98, 0.0), + createData('Marshmallow', 318, 0, 81, 2.0), + createData('Nougat', 360, 19.0, 9, 37.0), + createData('Oreo', 437, 18.0, 63, 4.0), + ].sort((a, b) => (a.calories < b.calories ? -1 : 1)), + page: 0, + rowsPerPage: 5, + defaultPerPage: 5, + filterText: '', + size: 'medium', + bordered: false, + stripped: true, + hovered: false, + toolbar: true, + checkcell: false, + pagination: true + }; + } + + handleChangeRadio = key => (event, value) => { + this.setState({ + [key]: value, + }); + }; + + handleChangeCheck = name => event => { + this.setState({ [name]: event.target.checked }); + }; + + handleRequestSort = (event, property) => { + const orderBy = property; + let order = 'desc'; + + if (this.state.orderBy === property && this.state.order === 'desc') { + order = 'asc'; + } + + const data = order === 'desc' + ? this.state.data.sort((a, b) => (b[orderBy] < a[orderBy] ? -1 : 1)) + : this.state.data.sort((a, b) => (a[orderBy] < b[orderBy] ? -1 : 1)); + + this.setState({ data, order, orderBy }); + }; + + handleSelectAllClick = (event, checked) => { + if (checked) { + this.setState({ selected: this.state.data.map(n => n.id) }); + return; + } + this.setState({ selected: [] }); + }; + + handleClick = (event, id) => { + if (!this.state.checkcell) { + return; + } + const { selected } = this.state; + const selectedIndex = selected.indexOf(id); + let newSelected = []; + + if (selectedIndex === -1) { + newSelected = newSelected.concat(selected, id); + } else if (selectedIndex === 0) { + newSelected = newSelected.concat(selected.slice(1)); + } else if (selectedIndex === selected.length - 1) { + newSelected = newSelected.concat(selected.slice(0, -1)); + } else if (selectedIndex > 0) { + newSelected = newSelected.concat( + selected.slice(0, selectedIndex), + selected.slice(selectedIndex + 1), + ); + } + + this.setState({ selected: newSelected }); + }; + + handleChangePage = (event, page) => { + this.setState({ page }); + }; + + handleChangeRowsPerPage = event => { + this.setState({ rowsPerPage: event.target.value }); + }; + + isSelected = id => this.state.selected.indexOf(id) !== -1; + + handleUserInput(value) { + // Show all item first + if (value !== '') { + this.setState({ rowsPerPage: this.state.data.length }); + } else { + this.setState({ rowsPerPage: this.state.defaultPerPage }); + } + + // Show result base on keyword + this.setState({ filterText: value.toLowerCase() }); + } + + render() { + const { classes } = this.props; + const { + data, + order, + orderBy, + selected, + rowsPerPage, + page, + filterText, + size, + columnData, + toolbar, pagination, checkcell, + bordered, stripped, hovered + } = this.state; + const emptyRows = rowsPerPage - Math.min(rowsPerPage, data.length - (page * rowsPerPage)); + const renderCell = (dataArray, keyArray) => keyArray.map((itemCell, index) => ( + <TableCell align={itemCell.numeric ? 'right' : 'left'} key={index.toString()}>{dataArray[itemCell.id]}</TableCell> + )); + return ( + <PapperBlock title="Table Playground" desc=""> + <div> + <Grid container className={classes.root}> + <Grid item xs={12}> + <Grid container className={classes.settings}> + <Grid item xs={6} sm={4}> + <FormControl component="fieldset"> + <FormLabel>Size</FormLabel> + <RadioGroup + name="size" + aria-label="size" + value={size} + onChange={this.handleChangeRadio('size')} + > + <FormControlLabel value="small" control={<Radio />} label="Small" /> + <FormControlLabel value="medium" control={<Radio />} label="Medium" /> + <FormControlLabel value="big" control={<Radio />} label="Big" /> + </RadioGroup> + </FormControl> + </Grid> + <Grid item xs={6} sm={4}> + <FormControl component="fieldset"> + <FormLabel>Style</FormLabel> + <FormGroup> + <FormControlLabel + control={( + <Checkbox + checked={this.state.bordered} + onChange={this.handleChangeCheck('bordered')} + value="bordered" + /> + )} + label="Bordered" + /> + <FormControlLabel + control={( + <Checkbox + checked={this.state.stripped} + onChange={this.handleChangeCheck('stripped')} + value="stripped" + /> + )} + label="Strppied" + /> + <FormControlLabel + control={( + <Checkbox + checked={this.state.hovered} + onChange={this.handleChangeCheck('hovered')} + value="hovered" + /> + )} + label="Hovered" + /> + </FormGroup> + </FormControl> + </Grid> + <Grid item xs={6} sm={4}> + <FormControl component="fieldset"> + <FormLabel>Component</FormLabel> + <FormGroup> + <FormControlLabel + control={( + <Checkbox + checked={this.state.toolbar} + onChange={this.handleChangeCheck('toolbar')} + value="toolbar" + /> + )} + label="Toolbar" + /> + <FormControlLabel + control={( + <Checkbox + checked={this.state.checkcell} + onChange={this.handleChangeCheck('checkcell')} + value="checkcell" + /> + )} + label="Checkbox" + /> + <FormControlLabel + control={( + <Checkbox + checked={this.state.pagination} + onChange={this.handleChangeCheck('pagination')} + value="pagination" + /> + )} + label="Pagination" + /> + </FormGroup> + </FormControl> + </Grid> + </Grid> + </Grid> + <Grid item xs={12}> + <Paper className={classes.root}> + {toolbar + && ( + <EnhancedTableToolbar + numSelected={selected.length} + filterText={filterText} + onUserInput={(event) => this.handleUserInput(event)} + /> + ) + } + <div className={classes.tableWrapper}> + <Table className={ + classNames( + classes.table, + hovered && tableStyles.hover, + stripped && tableStyles.stripped, + bordered && tableStyles.bordered, + tableStyles[size] + )} + > + <EnhancedTableHead + numSelected={selected.length} + order={order} + orderBy={orderBy} + onSelectAllClick={this.handleSelectAllClick} + onRequestSort={this.handleRequestSort} + rowCount={data.length} + columnData={columnData} + checkcell={checkcell} + /> + <TableBody> + {data.slice(page * rowsPerPage, (page * rowsPerPage) + rowsPerPage).map(n => { + const isSelected = this.isSelected(n.id); + if (n.name.toLowerCase().indexOf(filterText) === -1) { + return false; + } + return ( + <TableRow + onClick={event => this.handleClick(event, n.id)} + role="checkbox" + aria-checked={isSelected} + tabIndex={-1} + key={n.id} + selected={isSelected} + > + {checkcell + && ( + <TableCell padding="checkbox"> + <Checkbox checked={isSelected} /> + </TableCell> + ) + } + {renderCell(n, columnData)} + </TableRow> + ); + })} + {emptyRows > 0 && ( + <TableRow style={{ height: 49 * emptyRows }}> + <TableCell colSpan={6} /> + </TableRow> + )} + </TableBody> + </Table> + </div> + {pagination + && ( + <TablePagination + rowsPerPageOptions={[5, 10, 25]} + component="div" + count={data.length} + rowsPerPage={rowsPerPage} + page={page} + backIconButtonProps={{ + 'aria-label': 'Previous Page', + }} + nextIconButtonProps={{ + 'aria-label': 'Next Page', + }} + onChangePage={this.handleChangePage} + onChangeRowsPerPage={this.handleChangeRowsPerPage} + /> + ) + } + </Paper> + </Grid> + </Grid> + </div> + </PapperBlock> + ); + } +} + +InteractiveGrid.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(InteractiveGrid); diff --git a/front/odiparpack/app/containers/Tables/TreeTable.js b/front/odiparpack/app/containers/Tables/TreeTable.js new file mode 100644 index 0000000..9b715fb --- /dev/null +++ b/front/odiparpack/app/containers/Tables/TreeTable.js @@ -0,0 +1,60 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import Markdown from 'react-markdown'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import codeStyle from 'ba-styles/Code.scss'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Paper } from '@material-ui/core'; +import { TreeTableDemo, TreeTableDemoIcon } from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class TreeTablePage extends Component { + render() { + const title = brand.name + ' - Table'; + const description = brand.desc; + const docSrc = 'containers/Tables/demos/'; + const { classes } = this.props; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Tree Table Arrow Icon" desc="A simple Tree Collapsed/Expanded Table"> + <div> + <Paper className={classes.root}> + <TreeTableDemo /> + </Paper> + <SourceReader componentName={docSrc + 'TreeTableDemo.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Tree Table +/- Icon" desc="A Tree Table with +/- Icon"> + <div> + <Markdown className={codeStyle.codePre} source="Just add attribute ``` icon='plusminus' ``` to change icon to +/-" /> + <Paper className={classes.root}> + <TreeTableDemoIcon /> + </Paper> + <SourceReader componentName={docSrc + 'TreeTableDemoIcon.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +TreeTablePage.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(TreeTablePage); diff --git a/front/odiparpack/app/containers/Tables/demos/AdvFilter.js b/front/odiparpack/app/containers/Tables/demos/AdvFilter.js new file mode 100644 index 0000000..091703a --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/AdvFilter.js @@ -0,0 +1,64 @@ +import React from 'react'; +import MUIDataTable from 'mui-datatables'; +/* + It uses npm mui-datatables. It's easy to use, you just describe columns and data collection. + Checkout full documentation here : + https://github.com/gregnb/mui-datatables/blob/master/README.md +*/ +class AdvFilter extends React.Component { + state = { + columns: ['Name', 'Title', 'Location', 'Age', 'Salary'], + data: [ + ['Gabby George', 'Business Analyst', 'Minneapolis', 30, '$100,000'], + ['Aiden Lloyd', 'Business Consultant', 'Dallas', 55, '$200,000'], + ['Jaden Collins', 'Attorney', 'Santa Ana', 27, '$500,000'], + ['Franky Rees', 'Business Analyst', 'St. Petersburg', 22, '$50,000'], + ['Aaren Rose', 'Business Consultant', 'Toledo', 28, '$75,000'], + ['Blake Duncan', 'Business Management Analyst', 'San Diego', 65, '$94,000'], + ['Frankie Parry', 'Agency Legal Counsel', 'Jacksonville', 71, '$210,000'], + ['Lane Wilson', 'Commercial Specialist', 'Omaha', 19, '$65,000'], + ['Robin Duncan', 'Business Analyst', 'Los Angeles', 20, '$77,000'], + ['Mel Brooks', 'Business Consultant', 'Oklahoma City', 37, '$135,000'], + ['Harper White', 'Attorney', 'Pittsburgh', 52, '$420,000'], + ['Kris Humphrey', 'Agency Legal Counsel', 'Laredo', 30, '$150,000'], + ['Frankie Long', 'Industrial Analyst', 'Austin', 31, '$170,000'], + ['Brynn Robbins', 'Business Analyst', 'Norfolk', 22, '$90,000'], + ['Justice Mann', 'Business Consultant', 'Chicago', 24, '$133,000'], + ['Addison Navarro', 'Business Management Analyst', 'New York', 50, '$295,000'], + ['Jesse Welch', 'Agency Legal Counsel', 'Seattle', 28, '$200,000'], + ['Eli Mejia', 'Commercial Specialist', 'Long Beach', 65, '$400,000'], + ['Gene Leblanc', 'Industrial Analyst', 'Hartford', 34, '$110,000'], + ['Danny Leon', 'Computer Scientist', 'Newark', 60, '$220,000'], + ['Lane Lee', 'Corporate Counselor', 'Cincinnati', 52, '$180,000'], + ['Jesse Hall', 'Business Analyst', 'Baltimore', 44, '$99,000'], + ['Danni Hudson', 'Agency Legal Counsel', 'Tampa', 37, '$90,000'], + ['Terry Macdonald', 'Commercial Specialist', 'Miami', 39, '$140,000'], + ['Justice Mccarthy', 'Attorney', 'Tucson', 26, '$330,000'], + ['Silver Carey', 'Computer Scientist', 'Memphis', 47, '$250,000'], + ['Franky Miles', 'Industrial Analyst', 'Buffalo', 49, '$190,000'], + ['Glen Nixon', 'Corporate Counselor', 'Arlington', 44, '$80,000'], + ['Gabby Strickland', 'Business Process Consultant', 'Scottsdale', 26, '$45,000'], + ['Mason Ray', 'Computer Scientist', 'San Francisco', 39, '$142,000'] + ] + } + render() { + const { columns, data } = this.state; + const options = { + filterType: 'dropdown', + responsive: 'stacked', + print: true, + rowsPerPage: 10, + page: 1 + }; + return ( + <MUIDataTable + title="Employee list" + data={data} + columns={columns} + options={options} + /> + ); + } +} + +export default AdvFilter; diff --git a/front/odiparpack/app/containers/Tables/demos/AdvTableDemo.js b/front/odiparpack/app/containers/Tables/demos/AdvTableDemo.js new file mode 100644 index 0000000..d67990d --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/AdvTableDemo.js @@ -0,0 +1,101 @@ +import React from 'react'; +import { AdvTable } from 'ba-components'; + +let counter = 0; +function createData(name, calories, fat, carbs, protein) { + counter += 1; + return { + id: counter, + name, + calories, + fat, + carbs, + protein + }; +} + +class AdvTableDemo extends React.Component { + state = { + order: 'asc', + orderBy: 'calories', + selected: [], + columnData: [ + { + id: 'name', + numeric: false, + disablePadding: true, + label: 'Dessert (100g serving)' + }, { + id: 'calories', + numeric: true, + disablePadding: false, + label: 'Calories' + }, { + id: 'fat', + numeric: true, + disablePadding: false, + label: 'Fat (g)' + }, { + id: 'carbs', + numeric: true, + disablePadding: false, + label: 'Carbs (g)' + }, { + id: 'protein', + numeric: true, + disablePadding: false, + label: 'Protein (g)' + }, + ], + data: [ + createData('Cupcake', 305, 3.7, 67, 4.3), + createData('Donut', 452, 25.0, 51, 4.9), + createData('Eclair', 262, 16.0, 24, 6.0), + createData('Frozen yoghurt', 159, 6.0, 24, 4.0), + createData('Gingerbread', 356, 16.0, 49, 3.9), + createData('Honeycomb', 408, 3.2, 87, 6.5), + createData('Ice cream sandwich', 237, 9.0, 37, 4.3), + createData('Jelly Bean', 375, 0.0, 94, 0.0), + createData('KitKat', 518, 26.0, 65, 7.0), + createData('Lollipop', 392, 0.2, 98, 0.0), + createData('Marshmallow', 318, 0, 81, 2.0), + createData('Nougat', 360, 19.0, 9, 37.0), + createData('Oreo', 437, 18.0, 63, 4.0), + ].sort((a, b) => (a.calories < b.calories ? -1 : 1)), + page: 0, + rowsPerPage: 5, + defaultPerPage: 5, + filterText: '', + }; + + render() { + const { + order, + orderBy, + selected, + data, + page, + rowsPerPage, + defaultPerPage, + filterText, + columnData + } = this.state; + + return ( + <AdvTable + order={order} + orderBy={orderBy} + selected={selected} + data={data} + page={page} + rowsPerPage={rowsPerPage} + defaultPerPage={defaultPerPage} + filterText={filterText} + columnData={columnData} + /> + ); + } +} + + +export default AdvTableDemo; diff --git a/front/odiparpack/app/containers/Tables/demos/BorderedTable.js b/front/odiparpack/app/containers/Tables/demos/BorderedTable.js new file mode 100644 index 0000000..a01979d --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/BorderedTable.js @@ -0,0 +1,91 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import tableStyles from 'ba-styles/Table.scss'; + +import { + Toolbar, + Typography, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Paper, +} from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '100%', + marginTop: theme.spacing(3), + overflowX: 'auto', + }, + table: { + minWidth: 700, + }, +}); + +let id = 0; +function createData(name, calories, fat, carbs, protein) { + id += 1; + return { + id, + name, + calories, + fat, + carbs, + protein + }; +} + +const data = [ + createData('Frozen yoghurt', 159, 6.0, 24, 4.0), + createData('Ice cream sandwich', 237, 9.0, 37, 4.3), + createData('Eclair', 262, 16.0, 24, 6.0), + createData('Cupcake', 305, 3.7, 67, 4.3), + createData('Gingerbread', 356, 16.0, 49, 3.9), +]; + +function BorderedTable(props) { + const { classes } = props; + + return ( + <Paper className={classes.root}> + <Toolbar> + <div className={classes.title}> + <Typography variant="h6">Nutrition</Typography> + </div> + </Toolbar> + <Table className={classNames(classes.table, tableStyles.bordered)}> + <TableHead> + <TableRow> + <TableCell>Dessert (100g serving)</TableCell> + <TableCell align="right">Calories</TableCell> + <TableCell align="right">Fat (g)</TableCell> + <TableCell align="right">Carbs (g)</TableCell> + <TableCell align="right">Protein (g)</TableCell> + </TableRow> + </TableHead> + <TableBody> + {data.map(n => ([ + <TableRow key={n.id}> + <TableCell>{n.name}</TableCell> + <TableCell align="right">{n.calories}</TableCell> + <TableCell align="right">{n.fat}</TableCell> + <TableCell align="right">{n.carbs}</TableCell> + <TableCell align="right">{n.protein}</TableCell> + </TableRow> + ]) + )} + </TableBody> + </Table> + </Paper> + ); +} + +BorderedTable.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(BorderedTable); diff --git a/front/odiparpack/app/containers/Tables/demos/CrudTableDemo.js b/front/odiparpack/app/containers/Tables/demos/CrudTableDemo.js new file mode 100644 index 0000000..98a1868 --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/CrudTableDemo.js @@ -0,0 +1,220 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { + fetchAction, + addAction, + removeAction, + updateAction, + editAction, + saveAction, + closeNotifAction, +} from 'ba-actions/CrudTbActions'; +import { CrudTable, Notification } from 'ba-components'; +import { Paper } from '@material-ui/core'; + +// Reducer Branch +const branch = 'crudTableDemo'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +const anchorTable = [ + { + name: 'id', + label: 'Id', + type: 'static', + initialValue: '', + hidden: true + }, { + name: 'category', + label: 'Category', + type: 'selection', + initialValue: 'Electronics', + options: ['Electronics', 'Sporting Goods', 'Apparels', 'Other'], + width: '150', + hidden: false + }, { + name: 'price', + label: 'Price', + type: 'number', + initialValue: 0, + width: '100', + hidden: false + }, { + name: 'date', + label: 'Date Updated', + type: 'date', + initialValue: new Date(), + width: 'auto', + hidden: false + }, { + name: 'time', + label: 'Time Updated', + type: 'time', + initialValue: new Date(), + width: 'auto', + hidden: false + }, { + name: 'name', + label: 'Name', + type: 'text', + initialValue: '', + width: 'auto', + hidden: false + }, { + name: 'available', + label: 'Available', + type: 'toggle', + initialValue: true, + width: '100', + hidden: false + }, { + name: 'edited', + label: '', + type: 'static', + initialValue: '', + hidden: true + }, { + name: 'action', + label: 'Action', + type: 'static', + initialValue: '', + hidden: false + }, +]; +const dataApi = [ + { + id: 1, + category: 'Sporting Goods', + price: '49.99', + date: '4/3/2018', + time: 'Tue Apr 03 2018 00:00:00 GMT+0700 (WIB)', + name: 'football', + available: true, + edited: false, + }, { + id: 2, + category: 'Other', + price: '9.99', + date: '4/2/2018', + time: 'Tue Apr 03 2018 00:00:00 GMT+0700 (WIB)', + name: 'baseball', + available: true, + edited: false, + }, { + id: 3, + category: 'Sporting Goods', + price: '29.99', + date: '4/1/2018', + time: 'Tue Apr 03 2018 00:00:00 GMT+0700 (WIB)', + name: 'basketball', + available: false, + edited: false, + }, { + id: 4, + category: 'Electronics', + price: '99.99', + date: '3/30/2018', + time: 'Tue Apr 03 2018 00:00:00 GMT+0700 (WIB)', + name: 'iPod Touch', + available: true, + edited: false, + }, { + id: 5, + category: 'Electronics', + price: '399.99', + date: '3/29/2018', + time: 'Tue Apr 03 2018 00:00:00 GMT+0700 (WIB)', + name: 'iPhone 5', + available: false, + edited: false, + }, { + id: 6, + category: 'Electronics', + price: '199.99', + date: '3/28/2018', + time: 'Tue Apr 03 2018 00:00:00 GMT+0700 (WIB)', + name: 'nexus 7', + available: true, + edited: false, + } +]; + +class CrudTableDemo extends Component { + render() { + const { + classes, + fetchData, + addEmptyRow, + dataTable, + removeRow, + updateRow, + editRow, + finishEditRow, + closeNotif, + messageNotif, + } = this.props; + return ( + <div> + <Notification close={() => closeNotif(branch)} message={messageNotif} /> + <Paper className={classes.root}> + <CrudTable + dataInit={dataApi} + anchor={anchorTable} + title="Inventory" + dataTable={dataTable} + fetchData={fetchData} + addEmptyRow={addEmptyRow} + removeRow={removeRow} + updateRow={updateRow} + editRow={editRow} + finishEditRow={finishEditRow} + branch={branch} + /> + </Paper> + </div> + ); + } +} + +CrudTableDemo.propTypes = { + classes: PropTypes.object.isRequired, + fetchData: PropTypes.func.isRequired, + dataTable: PropTypes.object.isRequired, + addEmptyRow: PropTypes.func.isRequired, + removeRow: PropTypes.func.isRequired, + updateRow: PropTypes.func.isRequired, + editRow: PropTypes.func.isRequired, + finishEditRow: PropTypes.func.isRequired, + closeNotif: PropTypes.func.isRequired, + messageNotif: PropTypes.string.isRequired, +}; + +const mapStateToProps = state => ({ + force: state, // force state from reducer + dataTable: state.getIn([branch, 'dataTable']), + messageNotif: state.getIn([branch, 'notifMsg']), +}); + +const mapDispatchToProps = dispatch => ({ + fetchData: bindActionCreators(fetchAction, dispatch), + addEmptyRow: bindActionCreators(addAction, dispatch), + removeRow: bindActionCreators(removeAction, dispatch), + updateRow: bindActionCreators(updateAction, dispatch), + editRow: bindActionCreators(editAction, dispatch), + finishEditRow: bindActionCreators(saveAction, dispatch), + closeNotif: bindActionCreators(closeNotifAction, dispatch), +}); + +const CrudTableMapped = connect( + mapStateToProps, + mapDispatchToProps +)(CrudTableDemo); + +export default withStyles(styles)(CrudTableMapped); diff --git a/front/odiparpack/app/containers/Tables/demos/CrudTbFormDemo.js b/front/odiparpack/app/containers/Tables/demos/CrudTbFormDemo.js new file mode 100644 index 0000000..c20803e --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/CrudTbFormDemo.js @@ -0,0 +1,230 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Field } from 'redux-form/immutable'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { + CheckboxRedux, + SelectRedux, + TextFieldRedux, + SwitchRedux +} from 'ba-components/Forms/ReduxFormMUI'; +import { + fetchAction, + addAction, + closeAction, + submitAction, + removeAction, + editAction, + closeNotifAction +} from 'ba-actions/CrudTbFrmActions'; +import { CrudTableForm, Notification } from 'ba-components'; +import { + Paper, + MenuItem, + InputLabel, + Radio, + RadioGroup, + FormControl, + FormLabel, + FormControlLabel, +} from '@material-ui/core'; +import { anchorTable, dataApi } from './sampleData'; + + +const branch = 'crudTbFrmDemo'; + +const renderRadioGroup = ({ input, ...rest }) => ( + <RadioGroup + {...input} + {...rest} + valueselected={input.value} + onChange={(event, value) => input.onChange(value)} + /> +); + +// validation functions +const required = value => (value == null ? 'Required' : undefined); +const email = value => ( + value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) + ? 'Invalid email' + : undefined +); + +const styles = ({ + root: { + flexGrow: 1, + }, + field: { + width: '100%', + marginBottom: 20 + }, + fieldBasic: { + width: '100%', + marginBottom: 20, + marginTop: 10 + }, + inlineWrap: { + display: 'flex', + flexDirection: 'row' + } +}); + +class CrudTbFormDemo extends Component { + saveRef = ref => { + this.ref = ref; + return this.ref; + }; + + render() { + const { + classes, + fetchData, + addNew, + closeForm, + submit, + removeRow, + editRow, + dataTable, + openForm, + initValues, + closeNotif, + messageNotif, + } = this.props; + const trueBool = true; + return ( + <div> + <Notification close={() => closeNotif(branch)} message={messageNotif} /> + <Paper className={classes.root}> + <CrudTableForm + dataTable={dataTable} + openForm={openForm} + dataInit={dataApi} + anchor={anchorTable} + title="Title of Table" + fetchData={fetchData} + addNew={addNew} + closeForm={closeForm} + submit={submit} + removeRow={removeRow} + editRow={editRow} + branch={branch} + initValues={initValues} + > + {/* Create Your own form, then arrange or custom it as You like */} + <div> + <Field + name="text" + component={TextFieldRedux} + placeholder="Text Field" + label="Text Field" + validate={required} + required + ref={this.saveRef} + className={classes.field} + /> + </div> + <div> + <Field + name="email" + component={TextFieldRedux} + placeholder="Email Field" + label="Email" + required + validate={[required, email]} + className={classes.field} + /> + </div> + <div className={classes.fieldBasic}> + <FormLabel component="label">Choose One Option</FormLabel> + <Field name="radio" className={classes.inlineWrap} component={renderRadioGroup}> + <FormControlLabel value="option1" control={<Radio />} label="Option 1" /> + <FormControlLabel value="option2" control={<Radio />} label="Option 2" /> + </Field> + </div> + <div> + <FormControl className={classes.field}> + <InputLabel htmlFor="selection">Selection</InputLabel> + <Field + name="selection" + component={SelectRedux} + placeholder="Selection" + autoWidth={trueBool} + > + <MenuItem value="option1">Option One</MenuItem> + <MenuItem value="option2">Option Two</MenuItem> + <MenuItem value="option3">Option Three</MenuItem> + </Field> + </FormControl> + </div> + <div className={classes.fieldBasic}> + <FormLabel component="label">Toggle Input</FormLabel> + <div className={classes.inlineWrap}> + <FormControlLabel control={<Field name="onof" component={SwitchRedux} />} label="On/OF Switch" /> + <FormControlLabel control={<Field name="checkbox" component={CheckboxRedux} />} label="Checkbox" /> + </div> + </div> + <div className={classes.field}> + <Field + name="textarea" + className={classes.field} + component={TextFieldRedux} + placeholder="Textarea" + label="Textarea" + multiline={trueBool} + rows={4} + /> + </div> + {/* No need create button or submit, because that already made in this component */} + </CrudTableForm> + </Paper> + </div> + ); + } +} + +renderRadioGroup.propTypes = { + input: PropTypes.object.isRequired, +}; + +CrudTbFormDemo.propTypes = { + dataTable: PropTypes.object.isRequired, + openForm: PropTypes.bool.isRequired, + classes: PropTypes.object.isRequired, + fetchData: PropTypes.func.isRequired, + addNew: PropTypes.func.isRequired, + closeForm: PropTypes.func.isRequired, + submit: PropTypes.func.isRequired, + removeRow: PropTypes.func.isRequired, + editRow: PropTypes.func.isRequired, + initValues: PropTypes.object.isRequired, + closeNotif: PropTypes.func.isRequired, + messageNotif: PropTypes.string.isRequired, +}; + + +const mapStateToProps = state => ({ + force: state, // force state from reducer + initValues: state.getIn([branch, 'formValues']), + dataTable: state.getIn([branch, 'dataTable']), + openForm: state.getIn([branch, 'showFrm']), + messageNotif: state.getIn([branch, 'notifMsg']), +}); + +const mapDispatchToProps = dispatch => ({ + fetchData: bindActionCreators(fetchAction, dispatch), + addNew: bindActionCreators(addAction, dispatch), + closeForm: bindActionCreators(closeAction, dispatch), + submit: bindActionCreators(submitAction, dispatch), + removeRow: bindActionCreators(removeAction, dispatch), + editRow: bindActionCreators(editAction, dispatch), + closeNotif: bindActionCreators(closeNotifAction, dispatch), +}); + +const CrudTbFormDemoMapped = connect( + mapStateToProps, + mapDispatchToProps +)(CrudTbFormDemo); + +export default withStyles(styles)(CrudTbFormDemoMapped); diff --git a/front/odiparpack/app/containers/Tables/demos/EmptyTable.js b/front/odiparpack/app/containers/Tables/demos/EmptyTable.js new file mode 100644 index 0000000..c9517a2 --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/EmptyTable.js @@ -0,0 +1,48 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { EmptyData } from 'ba-components'; + +import { Toolbar, Typography, Table, TableCell, TableHead, TableRow, Paper } from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '100%', + marginTop: theme.spacing(3), + overflowX: 'auto', + }, + table: { + minWidth: 700, + }, +}); + +function EmptyTable(props) { + const { classes } = props; + return ( + <Paper className={classes.root}> + <Toolbar> + <div className={classes.title}> + <Typography variant="h6">Nutrition</Typography> + </div> + </Toolbar> + <Table className={classes.table}> + <TableHead> + <TableRow> + <TableCell>Dessert (100g serving)</TableCell> + <TableCell align="right">Calories</TableCell> + <TableCell align="right">Fat (g)</TableCell> + <TableCell align="right">Carbs (g)</TableCell> + <TableCell align="right">Protein (g)</TableCell> + </TableRow> + </TableHead> + </Table> + <EmptyData /> + </Paper> + ); +} + +EmptyTable.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(EmptyTable); diff --git a/front/odiparpack/app/containers/Tables/demos/HoverTable.js b/front/odiparpack/app/containers/Tables/demos/HoverTable.js new file mode 100644 index 0000000..48047c6 --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/HoverTable.js @@ -0,0 +1,91 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import tableStyles from 'ba-styles/Table.scss'; + +import { + Toolbar, + Typography, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Paper, +} from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '100%', + marginTop: theme.spacing(3), + overflowX: 'auto', + }, + table: { + minWidth: 700, + }, +}); + +let id = 0; +function createData(name, calories, fat, carbs, protein) { + id += 1; + return { + id, + name, + calories, + fat, + carbs, + protein + }; +} + +const data = [ + createData('Frozen yoghurt', 159, 6.0, 24, 4.0), + createData('Ice cream sandwich', 237, 9.0, 37, 4.3), + createData('Eclair', 262, 16.0, 24, 6.0), + createData('Cupcake', 305, 3.7, 67, 4.3), + createData('Gingerbread', 356, 16.0, 49, 3.9), +]; + +function HoverTable(props) { + const { classes } = props; + + return ( + <Paper className={classes.root}> + <Toolbar> + <div className={classes.title}> + <Typography variant="h6">Nutrition</Typography> + </div> + </Toolbar> + <Table className={classNames(classes.table, tableStyles.hover)}> + <TableHead> + <TableRow> + <TableCell>Dessert (100g serving)</TableCell> + <TableCell align="right">Calories</TableCell> + <TableCell align="right">Fat (g)</TableCell> + <TableCell align="right">Carbs (g)</TableCell> + <TableCell align="right">Protein (g)</TableCell> + </TableRow> + </TableHead> + <TableBody> + {data.map(n => ([ + <TableRow key={n.id}> + <TableCell>{n.name}</TableCell> + <TableCell align="right">{n.calories}</TableCell> + <TableCell align="right">{n.fat}</TableCell> + <TableCell align="right">{n.carbs}</TableCell> + <TableCell align="right">{n.protein}</TableCell> + </TableRow> + ]) + )} + </TableBody> + </Table> + </Paper> + ); +} + +HoverTable.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(HoverTable); diff --git a/front/odiparpack/app/containers/Tables/demos/SimpleTable.js b/front/odiparpack/app/containers/Tables/demos/SimpleTable.js new file mode 100644 index 0000000..e064d5e --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/SimpleTable.js @@ -0,0 +1,88 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; + +import { + Toolbar, + Typography, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Paper, +} from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '100%', + marginTop: theme.spacing(3), + overflowX: 'auto', + }, + table: { + minWidth: 700, + }, +}); + +let id = 0; +function createData(name, calories, fat, carbs, protein) { + id += 1; + return { + id, + name, + calories, + fat, + carbs, + protein + }; +} + +const data = [ + createData('Frozen yoghurt', 159, 6.0, 24, 4.0), + createData('Ice cream sandwich', 237, 9.0, 37, 4.3), + createData('Eclair', 262, 16.0, 24, 6.0), + createData('Cupcake', 305, 3.7, 67, 4.3), + createData('Gingerbread', 356, 16.0, 49, 3.9), +]; + +function SimpleTable(props) { + const { classes } = props; + return ( + <Paper className={classes.root}> + <Toolbar> + <div className={classes.title}> + <Typography variant="h6">Nutrition</Typography> + </div> + </Toolbar> + <Table className={classes.table}> + <TableHead> + <TableRow> + <TableCell>Dessert (100g serving)</TableCell> + <TableCell align="right">Calories</TableCell> + <TableCell align="right">Fat (g)</TableCell> + <TableCell align="right">Carbs (g)</TableCell> + <TableCell align="right">Protein (g)</TableCell> + </TableRow> + </TableHead> + <TableBody> + {data.map(n => ([ + <TableRow key={n.id}> + <TableCell>{n.name}</TableCell> + <TableCell align="right">{n.calories}</TableCell> + <TableCell align="right">{n.fat}</TableCell> + <TableCell align="right">{n.carbs}</TableCell> + <TableCell align="right">{n.protein}</TableCell> + </TableRow> + ]) + )} + </TableBody> + </Table> + </Paper> + ); +} + +SimpleTable.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(SimpleTable); diff --git a/front/odiparpack/app/containers/Tables/demos/StatusColorRow.js b/front/odiparpack/app/containers/Tables/demos/StatusColorRow.js new file mode 100644 index 0000000..2e21190 --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/StatusColorRow.js @@ -0,0 +1,121 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import messageStyles from 'ba-styles/Messages.scss'; +import progressStyles from 'ba-styles/Progress.scss'; + +import { + Toolbar, + Typography, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Paper, + Chip, + LinearProgress, +} from '@material-ui/core'; + +const CustomTableCell = withStyles(theme => ({ + head: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); + +const styles = theme => ({ + root: { + width: '100%', + marginTop: theme.spacing(3), + overflowX: 'auto', + }, + chip: { + margin: theme.spacing(1), + fontWeight: 500, + color: '#FFF' + }, +}); + +let id = 0; +function createData(name, progress, status) { + id += 1; + return { + id, + name, + progress, + status, + }; +} + +const data = [ + createData('Frozen yoghurt', 24, 'Error'), + createData('Ice cream sandwich', 37, 'Warning'), + createData('Eclair', 24, 'Info'), + createData('Cupcake', 67, 'Default'), + createData('Gingerbread', 89, 'Success'), +]; + +function StatusLabel(props) { + const { classes } = props; + const getStatus = status => { + switch (status) { + case 'Error': return messageStyles.bgError; + case 'Warning': return messageStyles.bgWarning; + case 'Info': return messageStyles.bgInfo; + case 'Success': return messageStyles.bgSuccess; + default: return messageStyles.bgDefault; + } + }; + const getProgress = status => { + switch (status) { + case 'Error': return progressStyles.bgError; + case 'Warning': return progressStyles.bgWarning; + case 'Info': return progressStyles.bgInfo; + case 'Success': return progressStyles.bgSuccess; + default: return progressStyles.bgDefault; + } + }; + return ( + <Paper className={classes.root}> + <Toolbar> + <div className={classes.title}> + <Typography variant="h6">Nutrition</Typography> + </div> + </Toolbar> + <Table className={classes.table}> + <TableHead> + <TableRow> + <CustomTableCell>Dessert (100g serving)</CustomTableCell> + <CustomTableCell>Progress</CustomTableCell> + <CustomTableCell>Status</CustomTableCell> + </TableRow> + </TableHead> + <TableBody> + {data.map(n => ([ + <TableRow key={n.id} className={getStatus(n.status)}> + <TableCell>{n.name}</TableCell> + <TableCell align="right"> + <LinearProgress variant="determinate" className={getProgress(n.status)} value={n.progress} /> + </TableCell> + <TableCell> + <Chip label={n.status} className={classNames(classes.chip, getStatus(n.status))} /> + </TableCell> + </TableRow> + ]) + )} + </TableBody> + </Table> + </Paper> + ); +} + +StatusLabel.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(StatusLabel); diff --git a/front/odiparpack/app/containers/Tables/demos/StatusLabel.js b/front/odiparpack/app/containers/Tables/demos/StatusLabel.js new file mode 100644 index 0000000..bf011dd --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/StatusLabel.js @@ -0,0 +1,112 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import tableStyles from 'ba-styles/Table.scss'; +import messageStyles from 'ba-styles/Messages.scss'; +import progressStyles from 'ba-styles/Progress.scss'; + +import { + Toolbar, + Typography, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Paper, + Chip, + LinearProgress, +} from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '100%', + marginTop: theme.spacing(3), + overflowX: 'auto', + }, + chip: { + margin: theme.spacing(1), + fontWeight: 'bold', + color: '#FFF' + }, +}); + +let id = 0; +function createData(name, progress, status) { + id += 1; + return { + id, + name, + progress, + status, + }; +} + +const data = [ + createData('Frozen yoghurt', 24, 'Error'), + createData('Ice cream sandwich', 37, 'Warning'), + createData('Eclair', 24, 'Info'), + createData('Cupcake', 67, 'Default'), + createData('Gingerbread', 89, 'Success'), +]; + +function StatusLabel(props) { + const { classes } = props; + const getStatus = status => { + switch (status) { + case 'Error': return messageStyles.bgError; + case 'Warning': return messageStyles.bgWarning; + case 'Info': return messageStyles.bgInfo; + case 'Success': return messageStyles.bgSuccess; + default: return messageStyles.bgDefault; + } + }; + const getProgress = status => { + switch (status) { + case 'Error': return progressStyles.bgError; + case 'Warning': return progressStyles.bgWarning; + case 'Info': return progressStyles.bgInfo; + case 'Success': return progressStyles.bgSuccess; + default: return progressStyles.bgDefault; + } + }; + return ( + <Paper className={classes.root}> + <Toolbar> + <div className={classes.title}> + <Typography variant="h6">Nutrition</Typography> + </div> + </Toolbar> + <Table className={classNames(classes.table, tableStyles.stripped)}> + <TableHead> + <TableRow> + <TableCell>Dessert (100g serving)</TableCell> + <TableCell>Progress</TableCell> + <TableCell>Status</TableCell> + </TableRow> + </TableHead> + <TableBody> + {data.map(n => ([ + <TableRow key={n.id}> + <TableCell>{n.name}</TableCell> + <TableCell align="right"> + <LinearProgress variant="determinate" className={getProgress(n.status)} value={n.progress} /> + </TableCell> + <TableCell> + <Chip label={n.status} className={classNames(classes.chip, getStatus(n.status))} /> + </TableCell> + </TableRow> + ]) + )} + </TableBody> + </Table> + </Paper> + ); +} + +StatusLabel.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(StatusLabel); diff --git a/front/odiparpack/app/containers/Tables/demos/StrippedTable.js b/front/odiparpack/app/containers/Tables/demos/StrippedTable.js new file mode 100644 index 0000000..509bc4a --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/StrippedTable.js @@ -0,0 +1,91 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import tableStyles from 'ba-styles/Table.scss'; + +import { + Toolbar, + Typography, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Paper, +} from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '100%', + marginTop: theme.spacing(3), + overflowX: 'auto', + }, + table: { + minWidth: 700, + }, +}); + +let id = 0; +function createData(name, calories, fat, carbs, protein) { + id += 1; + return { + id, + name, + calories, + fat, + carbs, + protein + }; +} + +const data = [ + createData('Frozen yoghurt', 159, 6.0, 24, 4.0), + createData('Ice cream sandwich', 237, 9.0, 37, 4.3), + createData('Eclair', 262, 16.0, 24, 6.0), + createData('Cupcake', 305, 3.7, 67, 4.3), + createData('Gingerbread', 356, 16.0, 49, 3.9), +]; + +function StrippedTable(props) { + const { classes } = props; + + return ( + <Paper className={classes.root}> + <Toolbar> + <div className={classes.title}> + <Typography variant="h6">Nutrition</Typography> + </div> + </Toolbar> + <Table className={classNames(classes.table, tableStyles.stripped)}> + <TableHead> + <TableRow> + <TableCell>Dessert (100g serving)</TableCell> + <TableCell align="right">Calories</TableCell> + <TableCell align="right">Fat (g)</TableCell> + <TableCell align="right">Carbs (g)</TableCell> + <TableCell align="right">Protein (g)</TableCell> + </TableRow> + </TableHead> + <TableBody> + {data.map(n => ([ + <TableRow key={n.id}> + <TableCell>{n.name}</TableCell> + <TableCell align="right">{n.calories}</TableCell> + <TableCell align="right">{n.fat}</TableCell> + <TableCell align="right">{n.carbs}</TableCell> + <TableCell align="right">{n.protein}</TableCell> + </TableRow> + ]) + )} + </TableBody> + </Table> + </Paper> + ); +} + +StrippedTable.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(StrippedTable); diff --git a/front/odiparpack/app/containers/Tables/demos/TrackingTable.js b/front/odiparpack/app/containers/Tables/demos/TrackingTable.js new file mode 100644 index 0000000..0550869 --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/TrackingTable.js @@ -0,0 +1,124 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import messageStyles from 'ba-styles/Messages.scss'; +import progressStyles from 'ba-styles/Progress.scss'; +import avatarApi from 'ba-api/avatars'; +import { PapperBlock } from 'ba-components'; +import styles from 'ba-components/Widget/widget-jss'; + +import { + Typography, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Chip, + LinearProgress, + Avatar, + Icon, +} from '@material-ui/core'; + +let id = 0; +function createData(name, avatar, title, type, taskNumber, taskTitle, progress, status) { + id += 1; + return { + id, + name, + avatar, + title, + type, + taskNumber, + taskTitle, + progress, + status, + }; +} + +const data = [ + createData('John Doe', avatarApi[6], 'Front End Developer', 'bug_report', 2214, 'Vivamus sit amet interdum elit', 30, 'Error'), + createData('Jim Doe', avatarApi[8], 'System Analyst', 'flag', 2455, 'Nam sollicitudin dignissim nunc', 70, 'Success'), + createData('Jane Doe', avatarApi[2], 'Back End Developer', 'whatshot', 3450, 'Quisque ut metus sit amet augue rutrum', 50, 'Warning'), + createData('Jack Doe', avatarApi[9], 'CTO', 'settings', 4905, 'Cras convallis lacus orci', 85, 'Info'), + createData('Jessica Doe', avatarApi[5], 'Project Manager', 'book', 4118, 'Aenean sit amet magna vel magna', 33, 'Default'), +]; + +function TrackingTable(props) { + const { classes } = props; + const getStatus = status => { + switch (status) { + case 'Error': return messageStyles.bgError; + case 'Warning': return messageStyles.bgWarning; + case 'Info': return messageStyles.bgInfo; + case 'Success': return messageStyles.bgSuccess; + default: return messageStyles.bgDefault; + } + }; + const getProgress = status => { + switch (status) { + case 'Error': return progressStyles.bgError; + case 'Warning': return progressStyles.bgWarning; + case 'Info': return progressStyles.bgInfo; + case 'Success': return progressStyles.bgSuccess; + default: return progressStyles.bgDefault; + } + }; + const getType = type => { + switch (type) { + case 'bug_report': return classes.red; + case 'flag': return classes.indigo; + case 'whatshot': return classes.orange; + case 'settings': return classes.lime; + default: return classes.purple; + } + }; + return ( + <PapperBlock title="Team Progress" whiteBg desc="Monitoring Your Team progress. Tracking task, current progress, and task status here."> + <div className={classes.root}> + <Table className={classNames(classes.table)}> + <TableHead> + <TableRow> + <TableCell>Name</TableCell> + <TableCell>Task</TableCell> + </TableRow> + </TableHead> + <TableBody> + {data.map(n => ([ + <TableRow key={n.id}> + <TableCell> + <div className={classes.user}> + <Avatar alt={n.name} src={n.avatar} className={classes.avatar} /> + <div> + <Typography variant="subtitle1">{n.name}</Typography> + <Typography>{n.title}</Typography> + </div> + </div> + </TableCell> + <TableCell> + <div className={classes.taskStatus}> + <Icon className={classNames(classes.taskIcon, getType(n.type))}>{n.type}</Icon> + <a href="#"> +# + {n.taskNumber} + </a> + <Chip label={n.status} className={classNames(classes.chip, getStatus(n.status))} /> + </div> + <LinearProgress variant="determinate" className={getProgress(n.status)} value={n.progress} /> + </TableCell> + </TableRow> + ]) + )} + </TableBody> + </Table> + </div> + </PapperBlock> + ); +} + +TrackingTable.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(TrackingTable); diff --git a/front/odiparpack/app/containers/Tables/demos/TreeTableDemo.js b/front/odiparpack/app/containers/Tables/demos/TreeTableDemo.js new file mode 100644 index 0000000..4c522e2 --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/TreeTableDemo.js @@ -0,0 +1,68 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import openAction from 'ba-actions/TreeTableActions'; +import { TreeTable } from 'ba-components'; +import { Paper } from '@material-ui/core'; +import data from './dataTreeTable.js'; + +const styles = theme => ({ + root: { + width: '100%', + marginTop: theme.spacing(3), + overflowX: 'auto', + }, +}); + +const branch = 'treeTableArrow'; + +class TreeTableDemo extends Component { + render() { + const { + arrowMore, + treeOpen, + classes, + toggleTree + } = this.props; + return ( + <div> + <Paper className={classes.root}> + <TreeTable + treeOpen={treeOpen} + toggleTree={toggleTree} + arrowMore={arrowMore} + dataTable={data} + branch={branch} + icon="arrow" + /> + </Paper> + </div> + ); + } +} + +TreeTableDemo.propTypes = { + classes: PropTypes.object.isRequired, + treeOpen: PropTypes.object.isRequired, + arrowMore: PropTypes.object.isRequired, + toggleTree: PropTypes.func.isRequired, +}; + +const mapStateToProps = state => ({ + force: state, // force state from reducer + treeOpen: state.getIn([branch, 'treeOpen']), + arrowMore: state.getIn([branch, 'arrowMore']), +}); + +const mapDispatchToProps = dispatch => ({ + toggleTree: bindActionCreators(openAction, dispatch) +}); + +const TreeTableDemoMapped = connect( + mapStateToProps, + mapDispatchToProps +)(TreeTableDemo); + +export default withStyles(styles)(TreeTableDemoMapped); diff --git a/front/odiparpack/app/containers/Tables/demos/TreeTableDemoIcon.js b/front/odiparpack/app/containers/Tables/demos/TreeTableDemoIcon.js new file mode 100644 index 0000000..d669fae --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/TreeTableDemoIcon.js @@ -0,0 +1,68 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import openAction from 'ba-actions/TreeTableActions'; +import { TreeTable } from 'ba-components'; +import { Paper } from '@material-ui/core'; +import data from './dataTreeTable.js'; + +const styles = theme => ({ + root: { + width: '100%', + marginTop: theme.spacing(3), + overflowX: 'auto', + }, +}); + +const branch = 'treeTablePM'; + +class TreeTableDemoIcon extends Component { + render() { + const { + arrowMore, + treeOpen, + classes, + toggleTree + } = this.props; + return ( + <div> + <Paper className={classes.root}> + <TreeTable + treeOpen={treeOpen} + toggleTree={toggleTree} + arrowMore={arrowMore} + dataTable={data} + branch={branch} + icon="plusminus" + /> + </Paper> + </div> + ); + } +} + +TreeTableDemoIcon.propTypes = { + classes: PropTypes.object.isRequired, + treeOpen: PropTypes.object.isRequired, + arrowMore: PropTypes.object.isRequired, + toggleTree: PropTypes.func.isRequired, +}; + +const mapStateToProps = state => ({ + force: state, // force state from reducer + treeOpen: state.getIn([branch, 'treeOpen']), + arrowMore: state.getIn([branch, 'arrowMore']), +}); + +const mapDispatchToProps = dispatch => ({ + toggleTree: bindActionCreators(openAction, dispatch) +}); + +const TreeTableIconMapped = connect( + mapStateToProps, + mapDispatchToProps +)(TreeTableDemoIcon); + +export default withStyles(styles)(TreeTableIconMapped); diff --git a/front/odiparpack/app/containers/Tables/demos/dataTreeTable.js b/front/odiparpack/app/containers/Tables/demos/dataTreeTable.js new file mode 100644 index 0000000..2eba8ab --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/dataTreeTable.js @@ -0,0 +1,128 @@ +module.exports = { + head: [ + { + label: 'KeyId', + }, + { + label: 'Dessert (100g serving)', + }, + { + label: 'Calories', + }, + { + label: 'Fat(g)', + } + ], + body: [ + { + id: '1', + name: 'Frozen yoghurt', + calories: 159, + fat: 24, + child: [ + { + id: '1_1', + name: 'Frozen child 1', + calories: 159, + fat: 24, + }, + { + id: '1_2', + name: 'Frozen child 2', + calories: 159, + fat: 24, + child: [ + { + id: '1_2_1', + name: 'Frozen grand child 1', + calories: 159, + fat: 24, + }, + ] + }, + ] + }, + { + id: '2', + name: 'Eclair', + calories: 159, + fat: 24, + child: [ + { + id: '2_1', + name: 'Eclair Child', + calories: 159, + fat: 24, + child: [ + { + id: '2_1_1', + name: 'Eclair Grand Child 1', + calories: 159, + fat: 24, + }, + { + id: '2_1_2', + name: 'Eclair Grand Child 2', + calories: 159, + fat: 24, + } + ] + }, + ] + }, + { + id: '3', + name: 'Cupcake', + calories: 159, + fat: 24, + }, + { + id: '4', + name: 'Ginger Bread', + calories: 159, + fat: 24, + child: [ + { + id: '4_1', + name: 'Ginger Bread Child', + calories: 159, + fat: 24, + child: [ + { + id: '4_1_1', + name: 'Ginger Bread Grand Child 1', + calories: 159, + fat: 24, + child: [ + { + id: '4_1_1_1', + name: 'Ginger Bread Super Grand Child 1', + calories: 159, + fat: 24, + }, + { + id: '4_1_1_2', + name: 'Ginger Bread Super Grand Child 2', + calories: 159, + fat: 24, + }, + ] + }, + { + id: '4_1_2', + name: 'Ginger Bread Grand Child 2', + calories: 159, + fat: 24, + }, + { + id: '4_1_3', + name: 'Ginger Bread Grand Child 3', + calories: 159, + fat: 24, + } + ] + }, + ] + } + ] +}; diff --git a/front/odiparpack/app/containers/Tables/demos/index.js b/front/odiparpack/app/containers/Tables/demos/index.js new file mode 100644 index 0000000..604e828 --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/index.js @@ -0,0 +1,14 @@ +export SimpleTable from './SimpleTable'; +export StrippedTable from './StrippedTable'; +export HoverTable from './HoverTable'; +export BorderedTable from './BorderedTable'; +export TreeTableDemo from './TreeTableDemo'; +export TreeTableDemoIcon from './TreeTableDemoIcon'; +export CrudTableDemo from './CrudTableDemo'; +export CrudTbFormDemo from './CrudTbFormDemo'; +export AdvTableDemo from './AdvTableDemo'; +export AdvFilter from './AdvFilter'; +export StatusLabel from './StatusLabel'; +export StatusColorRow from './StatusColorRow'; +export EmptyTable from './EmptyTable'; +export TrackingTable from './TrackingTable'; diff --git a/front/odiparpack/app/containers/Tables/demos/sampleData.js b/front/odiparpack/app/containers/Tables/demos/sampleData.js new file mode 100644 index 0000000..d53d8e2 --- /dev/null +++ b/front/odiparpack/app/containers/Tables/demos/sampleData.js @@ -0,0 +1,124 @@ +export const anchorTable = [ + { + name: 'id', + label: 'Id', + initialValue: '', + hidden: true + }, { + name: 'text', + label: 'Text Field', + initialValue: null, + width: 'auto', + hidden: false + }, { + name: 'email', + label: 'Email Field', + initialValue: null, + width: 'auto', + hidden: false + }, { + name: 'radio', + label: 'Radio Option', + initialValue: 'option1', + width: '80', + hidden: false + }, { + name: 'selection', + label: 'Selection', + initialValue: 'option1', + width: '80', + hidden: false + }, { + name: 'onof', + label: 'On/Of Input', + initialValue: true, + width: '80', + hidden: false + }, { + name: 'checkbox', + label: 'Checkbox', + initialValue: true, + width: '80', + hidden: false + }, { + name: 'textarea', + label: 'Text Area', + initialValue: 'This is default text', + width: 'auto', + hidden: false + }, { + name: 'edited', + label: '', + initialValue: '', + hidden: true + }, { + name: 'action', + label: 'Action', + initialValue: '', + hidden: false + }, +]; + +export const dataApi = [ + { + id: '1', + text: 'Just write', + email: '[email protected]', + radio: 'option2', + selection: 'option1', + onof: false, + checkbox: true, + textarea: 'Lorem ipsum dolor sit amet', + edited: false, + }, { + id: '2', + text: 'Some text', + email: '[email protected]', + radio: 'option2', + selection: 'option2', + onof: false, + checkbox: false, + textarea: 'Lorem ipsum dolor sit amet', + edited: false, + }, { + id: '3', + text: 'Because it is a TextField', + email: '[email protected]', + radio: 'option1', + selection: 'option3', + onof: false, + checkbox: false, + textarea: 'Lorem ipsum dolor sit amet', + edited: false, + }, { + id: '4', + text: 'And editable', + email: '[email protected]', + radio: 'option1', + selection: 'option1', + onof: false, + checkbox: true, + textarea: 'Lorem ipsum dolor sit amet', + edited: false, + }, { + id: '5', + text: 'You can write', + email: '[email protected]', + radio: 'option2', + selection: 'option2', + onof: false, + checkbox: true, + textarea: 'Lorem ipsum dolor sit amet', + edited: false, + }, { + id: '6', + text: 'Everything You want here', + email: '[email protected]', + radio: 'option1', + selection: 'option3', + onof: false, + checkbox: false, + textarea: 'Lorem ipsum dolor sit amet', + edited: false, + }, +]; diff --git a/front/odiparpack/app/containers/Templates/Dashboard.js b/front/odiparpack/app/containers/Templates/Dashboard.js new file mode 100644 index 0000000..5f24777 --- /dev/null +++ b/front/odiparpack/app/containers/Templates/Dashboard.js @@ -0,0 +1,114 @@ +import React, { useState, useEffect } from 'react'; +import { PropTypes } from 'prop-types'; +import classNames from 'classnames'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { withStyles } from '@material-ui/core/styles'; +import { Header, Sidebar, BreadCrumb } from 'ba-components'; +import { toggleAction, openAction, playTransitionAction } from 'ba-actions/UiActions'; +import { Fade } from '@material-ui/core'; +import styles from './appStyles-jss'; + +function Dashboard(props) { + const { + classes, + children, + history, + toggleDrawer, + sidebarOpen, + initialOpen, + loadTransition, + pageLoaded + } = props; + const [transform, setTransform] = useState(0); + + const darker = true; + + const handleScroll = () => { + const doc = document.documentElement; + const scroll = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); + setTransform(scroll); + }; + + useEffect(() => { + // Scroll content to top + window.addEventListener('scroll', handleScroll); + + // Set expanded sidebar menu + const currentPath = history.location.pathname; + initialOpen(currentPath); + + // Play page transition + loadTransition(true); + + // Execute all arguments when page changes + const unlisten = history.listen(() => { + window.scrollTo(0, 0); + setTimeout(() => { + loadTransition(true); + }, 500); + }); + + return () => { + unlisten(); + }; + }, []); + + return ( + <div className={classes.appFrameInner}> + <Header toggleDrawerOpen={toggleDrawer} turnDarker={transform > 30 && darker} margin={sidebarOpen} /> + <Sidebar + open={sidebarOpen} + toggleDrawerOpen={toggleDrawer} + loadTransition={loadTransition} + turnDarker={transform > 30 && darker} + /> + <main className={classNames(classes.content, !sidebarOpen && classes.contentPadding)} id="mainContent"> + <div className={classes.bgbar} /> + <section className={classes.mainWrap}> + <BreadCrumb separator=" › " theme="light" location={history.location} /> + <Fade + in={pageLoaded} + mountOnEnter + unmountOnExit + {...(pageLoaded ? { timeout: 700 } : {})} + > + <div className={!pageLoaded ? classes.hideApp : ''}> + {children} + </div> + </Fade> + </section> + </main> + </div> + ); +} + +Dashboard.propTypes = { + classes: PropTypes.object.isRequired, + history: PropTypes.object.isRequired, + children: PropTypes.node.isRequired, + initialOpen: PropTypes.func.isRequired, + toggleDrawer: PropTypes.func.isRequired, + loadTransition: PropTypes.func.isRequired, + sidebarOpen: PropTypes.bool.isRequired, + pageLoaded: PropTypes.bool.isRequired +}; + +const reducer = 'ui'; +const mapStateToProps = state => ({ + sidebarOpen: state.getIn([reducer, 'sidebarOpen']), + pageLoaded: state.getIn([reducer, 'pageLoaded']) +}); + +const mapDispatchToProps = dispatch => ({ + toggleDrawer: () => dispatch(toggleAction), + initialOpen: bindActionCreators(openAction, dispatch), + loadTransition: bindActionCreators(playTransitionAction, dispatch), +}); + +const DashboardMaped = connect( + mapStateToProps, + mapDispatchToProps +)(Dashboard); + +export default (withStyles(styles)(DashboardMaped)); diff --git a/front/odiparpack/app/containers/Templates/Outer.js b/front/odiparpack/app/containers/Templates/Outer.js new file mode 100644 index 0000000..f6448be --- /dev/null +++ b/front/odiparpack/app/containers/Templates/Outer.js @@ -0,0 +1,34 @@ +import React from 'react'; +import { PropTypes } from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import brand from 'ba-api/brand'; +import logo from 'ba-images/logo.svg'; +import { Hidden } from '@material-ui/core'; +import styles from './appStyles-jss'; + +function Outer(props) { + const { + classes, + children, + } = props; + return ( + <div className={classes.appFrameOuter}> + <main className={classes.outerContent} id="mainContent"> + <Hidden mdUp> + <div className={classes.brand}> + <img src={logo} alt={brand.name} /> + <h3>{brand.name}</h3> + </div> + </Hidden> + {children} + </main> + </div> + ); +} + +Outer.propTypes = { + classes: PropTypes.object.isRequired, + children: PropTypes.node.isRequired, +}; + +export default (withStyles(styles)(Outer)); diff --git a/front/odiparpack/app/containers/Templates/appStyles-jss.js b/front/odiparpack/app/containers/Templates/appStyles-jss.js new file mode 100644 index 0000000..f9175eb --- /dev/null +++ b/front/odiparpack/app/containers/Templates/appStyles-jss.js @@ -0,0 +1,144 @@ +import bg from 'ba-images/material_bg.svg'; +import { fade } from '@material-ui/core/styles/colorManipulator'; + +const appFrame = { + display: 'flex', + width: '100%', + minHeight: '100%', + zIndex: 1, +}; + +const styles = theme => ({ + root: { + width: '100%', + minHeight: '100%', + marginTop: 0, + zIndex: 1, + }, + appFrameInner: { + ...appFrame, + flexDirection: 'row' + }, + appFrameOuter: { + ...appFrame, + }, + content: { + backgroundColor: theme.palette.background.default, + width: '100%', + padding: theme.spacing(1.5), + paddingLeft: 0, + minHeight: '100%', + overflow: 'hidden', + }, + outerContent: { + background: `url(${bg}) no-repeat ${theme.palette.primary.main} left bottom`, + width: '100%', + backgroundSize: 'cover', + flexDirection: 'column', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + [theme.breakpoints.down('md')]: { + padding: '20px 0' + }, + }, + bgbar: { + backgroundColor: theme.palette.primary.main, + width: '100%', + position: 'fixed', + height: 184, + top: 0, + left: 0 + }, + mainWrap: { + position: 'relative', + marginTop: theme.spacing(6), + marginLeft: theme.spacing(1.5), + height: '100%', + '& > div': { + paddingBottom: theme.spacing(4), + willChange: 'inherit !important' // hack for floating form issue whne expaded + } + }, + preloader: { + position: 'absolute', + top: 0, + left: 0, + width: '100%', + zIndex: 1000, + background: 'transparent', + height: 3, + }, + materialBg: { + position: 'absolute', + left: 0, + bottom: 0, + width: '100%', + opacity: 0.5 + }, + contentPadding: { + paddingLeft: 80 + }, + hideApp: { + display: 'none' + }, + circularProgress: { + position: 'absolute', + top: 'calc(50% - 100px)', + left: 'calc(50% - 100px)', + }, + brand: { + height: 54, + display: 'flex', + padding: '10px 10px 5px', + position: 'relative', + alignItems: 'center', + justifyContent: 'center', + '& img': { + width: 20 + }, + '& h3': { + margin: 0, + fontSize: 16, + fontWeight: 500, + paddingLeft: 10, + color: theme.palette.common.white, + } + }, + btn: {}, + icon: {}, + btnPicker: { + position: 'fixed', + zIndex: 2000, + right: 0, + top: 200, + background: fade(theme.palette.background.paper, 0.8), + borderRadius: '30px 0 0 30px', + padding: '4px 8px 4px 4px', + overflow: 'hidden', + border: `1px solid ${theme.palette.grey[300]}`, + '& $btn': { + background: theme.palette.secondary.main, + borderRadius: 30, + padding: 8, + boxShadow: theme.shadows[4], + display: 'flex', + alignItems: 'center', + width: 40, + height: 40, + textCenter: 'cener', + overflow: 'hidden', + color: 'transparent', + transition: 'all 0.3s ease', + '& $icon': { + color: theme.palette.background.paper, + }, + '&:hover': { + color: theme.palette.background.paper, + width: 90 + } + } + } +}); + +export default styles; diff --git a/front/odiparpack/app/containers/UiElements/Accordion.js b/front/odiparpack/app/containers/UiElements/Accordion.js new file mode 100644 index 0000000..dd354d3 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/Accordion.js @@ -0,0 +1,45 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { SimpleAccordion, AdvancedAccordion, ControlledAccordion } from './demos'; + +class Accordion extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/Accordion/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Accordion" desc="Accordion is MUI Expansion Panels contain creation flows and allow lightweight editing of an element."> + <div> + <SimpleAccordion /> + <SourceReader componentName={docSrc + 'SimpleAccordion.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Secondary heading and Columns" desc="Multiple columns can be used to structure the content, and a helper text may be added to the panel to assist the user."> + <div> + <AdvancedAccordion /> + <SourceReader componentName={docSrc + 'AdvancedAccordion.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Controlled Accordion" desc="Extend the default panel behavior to create an accordion with the ExpansionPanel component."> + <div> + <ControlledAccordion /> + <SourceReader componentName={docSrc + 'ControlledAccordion.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default Accordion; diff --git a/front/odiparpack/app/containers/UiElements/Avatars.js b/front/odiparpack/app/containers/UiElements/Avatars.js new file mode 100644 index 0000000..3ccbaaa --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/Avatars.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { AvatarsDemo, AvatarsPractice } from './demos'; + +class Avatars extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/Avatars/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Avatars" desc="Avatars are found throughout material design with uses in everything from tables to dialog menus."> + <div> + <AvatarsDemo /> + <SourceReader componentName={docSrc + 'AvatarsDemo.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Implemented Avatars" desc="Some examples of components, using an image Avatar, SVG Icon Avatar, Letter and (string) Avatar."> + <div> + <AvatarsPractice /> + <SourceReader componentName={docSrc + 'AvatarsPractice.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default Avatars; diff --git a/front/odiparpack/app/containers/UiElements/Badges.js b/front/odiparpack/app/containers/UiElements/Badges.js new file mode 100644 index 0000000..ff4783d --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/Badges.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { CommonBadges, VariantBadges } from './demos'; + +class Badges extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/Badges/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Common Badges" desc="Badge generates a small badge to the top-right of its child(ren)."> + <div> + <CommonBadges /> + <SourceReader componentName={docSrc + 'CommonBadges.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Variant Badges" desc="Examples of badges with icon and in tab, using primary and secondary colors. The badge is applied to its children."> + <div> + <VariantBadges /> + <SourceReader componentName={docSrc + 'VariantBadges.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default Badges; diff --git a/front/odiparpack/app/containers/UiElements/Breadcrumbs.js b/front/odiparpack/app/containers/UiElements/Breadcrumbs.js new file mode 100644 index 0000000..daaa24f --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/Breadcrumbs.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { ClassicBreadcrumbs, PaperBreadcrumbs } from './demos'; + +class BreadCrumbs extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/Breadcrumbs/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Classic Breadcrumb" desc="Breadcrumb is one of navigation component. With this User can jump to parent, children or grand-children page as long in a single inheritance.This example is designed with classic style separator"> + <div> + <ClassicBreadcrumbs /> + <SourceReader componentName={docSrc + 'ClassicBreadcrumbs.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Paper Breadcrumb" desc=""> + <div> + <PaperBreadcrumbs /> + <SourceReader componentName={docSrc + 'PaperBreadcrumbs.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default BreadCrumbs; diff --git a/front/odiparpack/app/containers/UiElements/Cards.js b/front/odiparpack/app/containers/UiElements/Cards.js new file mode 100644 index 0000000..7865b9d --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/Cards.js @@ -0,0 +1,63 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { + StandardCards, + ControlCards, + PaperSheet, + SocialCards, + EcommerceCards +} from './demos'; + +class Cards extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/Cards/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Paper" desc="In material design, the physical properties of paper are translated to the screen."> + <div> + <PaperSheet /> + <SourceReader componentName={docSrc + 'PaperSheet.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Standard Cards" desc="A card is a sheet of material that serves as an entry point to more detailed information."> + <div> + <StandardCards /> + <SourceReader componentName={docSrc + 'StandardCards.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Control Cards" desc="Supplemental actions within the card are explicitly called out using icons, text, and UI controls."> + <div> + <ControlCards /> + <SourceReader componentName={docSrc + 'ControlCards.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Social Cards" desc="A Customized material-ui card that contain profile elements"> + <div> + <SocialCards /> + <SourceReader componentName={docSrc + 'SocialCards.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Ecommerce Cards" desc="A Customized material-ui card that contain product information"> + <div> + <EcommerceCards /> + <SourceReader componentName={docSrc + 'EcommerceCards.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default Cards; diff --git a/front/odiparpack/app/containers/UiElements/DialogModal.js b/front/odiparpack/app/containers/UiElements/DialogModal.js new file mode 100644 index 0000000..6e26609 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/DialogModal.js @@ -0,0 +1,97 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Grid } from '@material-ui/core'; +import { + ModalDemo, + AlertDialog, + SelectDialog, + SelectRadioDialog, + FormDialog, + FullScreenDialog, + ImagePopup, + ScrollDialog +} from './demos'; + +class DialogModal extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/DialogModal/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Modals" desc="The modal component provides a solid foundation for creating dialogs, popovers, lightboxes, or whatever else."> + <div> + <ModalDemo /> + <SourceReader componentName={docSrc + 'ModalDemo.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Image Popup" desc="A flexible lightbox component for displaying images. It's also can handle zoom and panning of images. Mobile friendly, with pinch to zoom and swipe"> + <div> + <ImagePopup /> + <SourceReader componentName={docSrc + 'ImagePopup.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Alerts" desc="Alerts are urgent interruptions, requiring acknowledgement, that inform the user about a situation."> + <div> + <AlertDialog /> + <SourceReader componentName={docSrc + 'AlertDialog.js'} /> + </div> + </PapperBlock> + <Grid container spacing={2}> + <Grid item md={6}> + <PapperBlock title="Selection Dialog" desc="Choosing an option immediately commits the option and closes the menu"> + <div> + <SelectDialog /> + <SourceReader componentName={docSrc + 'SelectDialog.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6}> + <PapperBlock title="Selection Radio Dialog" desc="In this example, users can listen to multiple ringtones but only make a final selection upon touching “OK.”"> + <div> + <SelectRadioDialog /> + <SourceReader componentName={docSrc + 'SelectRadioDialog.js'} /> + </div> + </PapperBlock> + </Grid> + </Grid> + <PapperBlock title="Form dialogs" desc="Form dialogs allow users to fill out form fields within a dialog."> + <div> + <FormDialog /> + <SourceReader componentName={docSrc + 'FormDialog.js'} /> + </div> + </PapperBlock> + <Grid container spacing={2}> + <Grid item md={6} xs={12}> + <PapperBlock title="Full Screen (Responsive)" desc="You may make a Dialog responsively full screen the dialog using withMobileDialog. By default, withMobileDialog()(Dialog) responsively full screens at or below the sm screen size."> + <div> + <FullScreenDialog /> + <SourceReader componentName={docSrc + 'FullScreenDialog.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6} xs={12}> + <PapperBlock title="Scrolling dialog" desc="When dialogs become too long for the user’s viewport or device, they scroll."> + <div> + <ScrollDialog /> + <SourceReader componentName={docSrc + 'ScrollDialog.js'} /> + </div> + </PapperBlock> + </Grid> + </Grid> + </div> + ); + } +} + +export default DialogModal; diff --git a/front/odiparpack/app/containers/UiElements/Dividers.js b/front/odiparpack/app/containers/UiElements/Dividers.js new file mode 100644 index 0000000..34c66a9 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/Dividers.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { CommonDividers, SpecialDividers } from './demos'; + +class Dividers extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/Dividers/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Common Dividers" desc="Some variant divider style base on hr tag"> + <div> + <CommonDividers /> + <SourceReader componentName={docSrc + 'CommonDividers.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Special Dividers" desc="Sometime the divider is more than a line. With this You can add text or decoration at the edges"> + <div> + <SpecialDividers /> + <SourceReader componentName={docSrc + 'SpecialDividers.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default Dividers; diff --git a/front/odiparpack/app/containers/UiElements/DrawerMenu.js b/front/odiparpack/app/containers/UiElements/DrawerMenu.js new file mode 100644 index 0000000..dfffbc8 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/DrawerMenu.js @@ -0,0 +1,104 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Grid } from '@material-ui/core'; +import { + TemporaryDrawer, + SwipeDrawer, + PermanentDrawer, + PersistentDrawer, + MiniDrawer, + BasicMenu, + DropdownMenu, + StyledMenu, + MenuTransition +} from './demos'; + +class DrawerMenu extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/DrawerMenu/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <Grid container spacing={2}> + <Grid item md={6}> + <PapperBlock title="Temporary drawer" desc="Temporary navigation drawers can toggle open or closed. Closed by default, the drawer opens temporarily above all other content until a section is selected."> + <div> + <TemporaryDrawer /> + <SourceReader componentName={docSrc + 'TemporaryDrawer.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6}> + <PapperBlock title="Swipeable Temporary drawer" desc="You can make the drawer swipeable with the SwipeableDrawer component."> + <div> + <SwipeDrawer /> + <SourceReader componentName={docSrc + 'SwipeDrawer.js'} /> + </div> + </PapperBlock> + </Grid> + </Grid> + <PapperBlock title="Permanent drawer" desc="Permanent navigation drawers are always visible and pinned to the left edge, at the same elevation as the content or background. They cannot be closed."> + <div> + <PermanentDrawer /> + <SourceReader componentName={docSrc + 'PermanentDrawer.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Persistent drawer" desc="Persistent navigation drawers can toggle open or closed. The drawer sits on the same surface elevation as the content. It is closed by default and opens by selecting the menu icon, and stays open until closed by the user."> + <div> + <PersistentDrawer /> + <SourceReader componentName={docSrc + 'PersistentDrawer.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Mini variant drawer" desc="In this variation, the persistent navigation drawer changes its width. Its resting state is as a mini-drawer at the same elevation as the content, clipped by the app bar."> + <div> + <MiniDrawer /> + <SourceReader componentName={docSrc + 'MiniDrawer.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Basic Menu" desc="Basic menus open over the anchor element by default (this option can be changed via props). When close to a screen edge, simple menus vertically realign to make all menu items are completely visible."> + <div> + <BasicMenu /> + <SourceReader componentName={docSrc + 'BasicMenu.js'} /> + </div> + </PapperBlock> + <Grid container spacing={2}> + <Grid item md={6}> + <PapperBlock title="Selected menus" desc="If used for item selection, when opened, simple menus attempt to vertically align the currently selected menu item with the anchor element."> + <div> + <DropdownMenu /> + <SourceReader componentName={docSrc + 'DropdownMenu.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6}> + <PapperBlock title="Styled Menu" desc="The MenuItem is a wrapper around ListItem with some additional styles."> + <div> + <StyledMenu /> + <SourceReader componentName={docSrc + 'StyledMenu.js'} /> + </div> + </PapperBlock> + </Grid> + </Grid> + <PapperBlock title="Transition" desc="Use a different transition altogether."> + <div> + <MenuTransition /> + <SourceReader componentName={docSrc + 'MenuTransition.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default DrawerMenu; diff --git a/front/odiparpack/app/containers/UiElements/IconGallery/DetailIcon.js b/front/odiparpack/app/containers/UiElements/IconGallery/DetailIcon.js new file mode 100644 index 0000000..e19c17a --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/IconGallery/DetailIcon.js @@ -0,0 +1,149 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import SyntaxHighlighter, { registerLanguage } from 'react-syntax-highlighter/prism-light'; +import jsx from 'react-syntax-highlighter/languages/prism/jsx'; +import themeSource from 'react-syntax-highlighter/styles/prism/prism'; +import { withStyles } from '@material-ui/core/styles'; + +import { + Typography, + Button, + Icon, + Divider, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Slide, +} from '@material-ui/core'; + +const Transition = React.forwardRef(function Transition(props, ref) { // eslint-disable-line + return <Slide direction="up" ref={ref} {...props} />; +}); + +const humanize = (str, space) => { + let string = str; + if (str === '3d_rotation') { + string = 'three_d_rotation'; + } + const frags = string.split('_'); + for (let i = 0; i < frags.length; i += 1) { + frags[i] = frags[i].charAt(0).toUpperCase() + frags[i].slice(1); + } + + if (space) { + return frags.join(' '); + } + return frags.join(''); +}; + +const styles = theme => ({ + code: { + fontSize: 12, + padding: '5px !important' + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + bigIcon: { + textAlign: 'center', + marginBottom: 30, + '& span': { + fontSize: 128 + } + }, + title: { + marginBottom: 40, + fontSize: 22, + paddingBottom: 20, + position: 'relative', + fontWeight: 500, + color: theme.palette.grey[700], + textTransform: 'uppercase', + '&:after': { + content: '""', + display: 'block', + position: 'absolute', + bottom: 0, + left: 24, + width: 40, + borderBottom: `5px solid ${theme.palette.primary.main}` + } + }, +}); + +class DetailIcon extends React.Component { + render() { + registerLanguage('jsx', jsx); + const { + isOpenDetail, + iconName, + iconCode, + closeDetail + } = this.props; + const { classes } = this.props; + const linkCode = '<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />'; + const fontCode = '<Icon>' + iconName + '</Icon>'; + const importCode = 'import ' + humanize(iconName, false) + " from '@material-ui/icons/" + humanize(iconName, false) + "';"; + const importIconCode = "import Icon from '@material-ui/core/Icon';"; + const svgCode = '<' + humanize(iconName, false) + ' />'; + return ( + <Dialog + open={isOpenDetail} + TransitionComponent={Transition} + keepMounted + maxWidth="md" + onClose={closeDetail} + aria-labelledby="alert-dialog-slide-title" + aria-describedby="alert-dialog-slide-description" + > + <DialogTitle id="alert-dialog-slide-title" className={classes.title}> + {humanize(iconName, true)} + {' '} +( + {iconCode} +) + </DialogTitle> + <DialogContent> + <div className={classes.bigIcon}> + <Icon className={classes.icon}>{iconName}</Icon> + </div> + <Typography variant="subtitle1" gutterBottom>Use with Font Icons</Typography> + <SyntaxHighlighter className={classes.code} language="jsx" style={themeSource}> + {linkCode} + </SyntaxHighlighter> + <SyntaxHighlighter className={classes.code} language="jsx" style={themeSource}> + {importIconCode} + </SyntaxHighlighter> + <SyntaxHighlighter className={classes.code} language="jsx" style={themeSource}> + {fontCode} + </SyntaxHighlighter> + <Divider className={classes.divider} /> + <Typography variant="subtitle1" gutterBottom>Use with SVG Material icons</Typography> + <SyntaxHighlighter className={classes.code} language="jsx" style={themeSource}> + {importCode} + </SyntaxHighlighter> + <SyntaxHighlighter className={classes.code} language="jsx" style={themeSource}> + {svgCode} + </SyntaxHighlighter> + </DialogContent> + <DialogActions> + <Button onClick={closeDetail} color="primary"> + Close + </Button> + </DialogActions> + </Dialog> + ); + } +} + +DetailIcon.propTypes = { + classes: PropTypes.object.isRequired, + isOpenDetail: PropTypes.bool.isRequired, + closeDetail: PropTypes.func.isRequired, + iconName: PropTypes.string.isRequired, + iconCode: PropTypes.string.isRequired, +}; + +export default withStyles(styles)(DetailIcon); diff --git a/front/odiparpack/app/containers/UiElements/IconGallery/SearchIcons.js b/front/odiparpack/app/containers/UiElements/IconGallery/SearchIcons.js new file mode 100644 index 0000000..c046362 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/IconGallery/SearchIcons.js @@ -0,0 +1,54 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import SearchIcon from '@material-ui/icons/Search'; + +import { IconButton, Input, InputAdornment, FormControl } from '@material-ui/core'; + +const styles = theme => ({ + search: { + display: 'block', + background: '#fff', + marginBottom: 10, + paddingTop: 5, + boxShadow: theme.shadows[2], + '& > div': { + width: '100%', + }, + '& input': { + padding: '10px 8px' + } + } +}); + +class SearchIcons extends React.Component { + render() { + const { filterText, classes, handleSearch } = this.props; + return ( + <FormControl fullWidth className={classes.search}> + <Input + id="search_filter" + type="text" + placeholder="Search more than 900 icons" + value={filterText} + onChange={handleSearch} + endAdornment={( + <InputAdornment position="end"> + <IconButton aria-label="Search filter"> + <SearchIcon /> + </IconButton> + </InputAdornment> + )} + /> + </FormControl> + ); + } +} + +SearchIcons.propTypes = { + classes: PropTypes.object.isRequired, + filterText: PropTypes.string.isRequired, + handleSearch: PropTypes.func.isRequired, +}; + +export default withStyles(styles)(SearchIcons); diff --git a/front/odiparpack/app/containers/UiElements/Icons.js b/front/odiparpack/app/containers/UiElements/Icons.js new file mode 100644 index 0000000..f207d29 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/Icons.js @@ -0,0 +1,158 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import PropTypes from 'prop-types'; +import Axios from 'axios'; +import { withStyles } from '@material-ui/core/styles'; +import { PapperBlock } from 'ba-components'; +import { Typography, IconButton, Icon, LinearProgress } from '@material-ui/core'; +import DetailIcon from './IconGallery/DetailIcon'; +import SearchIcons from './IconGallery/SearchIcons'; +const url = '/api/icons?src='; + +const styles = theme => ({ + hide: { + display: 'none' + }, + iconsList: { + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'space-between', + [theme.breakpoints.down('xs')]: { + justifyContent: 'center', + }, + overflow: 'auto', + maxHeight: 1000, + position: 'relative' + }, + iconWrap: { + position: 'relative', + width: 120, + margin: 20, + [theme.breakpoints.down('xs')]: { + margin: 10, + }, + textAlign: 'center' + }, + btn: { + display: 'block', + textAlign: 'center', + margin: '0 auto', + }, + icon: { + fontSize: 48, + }, + preloader: { + width: '100%' + }, +}); + +class Icons extends React.Component { + state = { + raws: [], + loading: false, + openDetail: false, + iconName: '', + iconCode: '', + filterText: '' + }; + + componentDidMount = () => { + const name = 'material-icon-cheat.txt'; + this.setState({ loading: true }, () => { + Axios.get(url + name) + .then(response => response.data.records[0].source) + .then(data => { + const namesAndCodes = data.split('\n'); + const icons = namesAndCodes.map(nameAndCode => { + const parts = nameAndCode.split(' '); + return { + name: parts[0], + code: parts[1] + }; + }); + return icons; + }) + .then(icons => { + this.setState({ + raws: icons, + loading: false + }); + }); + }); + } + + handleOpenDetail = (name, code) => { + this.setState({ + openDetail: true, + iconName: name, + iconCode: code, + }); + }; + + handleCloseDetail = () => { + this.setState({ openDetail: false }); + }; + + handleSearch = (event) => { + event.persist(); + // Show result base on keyword + this.setState({ filterText: event.target.value.toLowerCase() }); + } + + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const { + raws, + loading, + openDetail, + iconName, + iconCode, + filterText + } = this.state; + const { classes } = this.props; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Material Icons" desc="Material icons are delightful, beautifully crafted symbols for common actions and items. System icons are designed to be simple, modern, friendly, and sometimes quirky. Each icon is reduced to its minimal form, expressing essential characteristics."> + <div> + {loading + && <LinearProgress color="secondary" className={classes.preloader} /> + } + <SearchIcons filterText={filterText} handleSearch={(event) => this.handleSearch(event)} /> + <div className={classes.iconsList}> + {raws.map((raw, index) => { + if (raw.name.toLowerCase().indexOf(filterText) === -1) { + return false; + } + return ( + <div className={classes.iconWrap} key={index.toString()}> + <IconButton title="Click to see detail" onClick={() => this.handleOpenDetail(raw.name, raw.code)} className={classes.btn}> + <Icon className={classes.icon}>{raw.name}</Icon> + </IconButton> + <Typography gutterBottom noWrap>{raw.name}</Typography> + </div> + ); + })} + <DetailIcon closeDetail={this.handleCloseDetail} isOpenDetail={openDetail} iconName={iconName} iconCode={iconCode} /> + </div> + </div> + </PapperBlock> + </div> + ); + } +} + +Icons.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Icons); diff --git a/front/odiparpack/app/containers/UiElements/ImageGrid.js b/front/odiparpack/app/containers/UiElements/ImageGrid.js new file mode 100644 index 0000000..a95fd50 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/ImageGrid.js @@ -0,0 +1,51 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { ImageGridList, TitlebarGridList, AdvancedGridList, SingleLineGridList } from './demos'; + +class ImageGrid extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/ImageGrid/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Image-only Grid list" desc="A simple example of a scrollable image GridList"> + <div> + <ImageGridList /> + <SourceReader componentName={docSrc + 'ImageGridList.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Grid list with titlebars" desc="This example demonstrates the use of the GridListTileBar to add an overlay to each GridListTile."> + <div> + <TitlebarGridList /> + <SourceReader componentName={docSrc + 'TitlebarGridList.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Advanced Grid list" desc="This example demonstrates featured tiles, using the rows and cols props to adjust the size of the tile, and the padding prop to adjust the spacing."> + <div> + <AdvancedGridList /> + <SourceReader componentName={docSrc + 'AdvancedGridList.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Single line Grid list" desc="This example demonstrates a horizontal scrollable single-line grid list of images."> + <div> + <SingleLineGridList /> + <SourceReader componentName={docSrc + 'SingleLineGridList.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default ImageGrid; diff --git a/front/odiparpack/app/containers/UiElements/List.js b/front/odiparpack/app/containers/UiElements/List.js new file mode 100644 index 0000000..665cd08 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/List.js @@ -0,0 +1,63 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { + ListBasic, + ListMenu, + PinnedList, + ListControl, + ListInteractive +} from './demos'; + +class List extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/List/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="List Basic" desc="The divider renders as a <hr> by default. You can save rendering this DOM element by using the divider property on the ListItem component."> + <div> + <ListBasic /> + <SourceReader componentName={docSrc + 'ListBasic.js'} /> + </div> + </PapperBlock> + <PapperBlock title="List Menu" desc="Lists are made up of a continuous column of rows. Each row contains a tile. Primary actions fill the tile, and supplemental actions are represented by icons and text."> + <div> + <ListMenu /> + <SourceReader componentName={docSrc + 'ListMenu.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Pinned Subheader List" desc="Upon scrolling, subheaders remain pinned to the top of the screen until pushed off screen by the next subheader."> + <div> + <PinnedList /> + <SourceReader componentName={docSrc + 'PinnedList.js'} /> + </div> + </PapperBlock> + <PapperBlock title="List Controls" desc="The checkbox is the primary action and the state indicator for the list item. The comment button is a secondary action and a separate target."> + <div> + <ListControl /> + <SourceReader componentName={docSrc + 'ListControl.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Interactive" desc="Below is an interactive demo that lets you explore the visual results of the different settings:"> + <div> + <ListInteractive /> + <SourceReader componentName={docSrc + 'ListInteractive.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default List; diff --git a/front/odiparpack/app/containers/UiElements/Notification.js b/front/odiparpack/app/containers/UiElements/Notification.js new file mode 100644 index 0000000..b00f24e --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/Notification.js @@ -0,0 +1,56 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Grid } from '@material-ui/core'; +import { SimpleNotif, StyledNotif, TransitionNotif, MobileNotif } from './demos'; + +class Notification extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/Notification/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Snackbars/Notification" desc="Snackbars provide brief feedback about an operation through a message - typically at the bottom of the screen."> + <div> + <SimpleNotif /> + <SourceReader componentName={docSrc + 'SimpleNotif.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Styled Notification" desc="Some snackbars with varying message style. And some snackbars with varying message length."> + <div> + <StyledNotif /> + <SourceReader componentName={docSrc + 'StyledNotif.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Mobile Notification" desc="Move the floating action button vertically to accommodate the snackbar height."> + <div> + <Grid container justify="center"> + <Grid item md={4} xs={12}> + <MobileNotif /> + </Grid> + </Grid> + <SourceReader componentName={docSrc + 'MobileNotif.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Transition" desc="Per Google's guidelines, when a second snackbar is triggered while the first is displayed, the first should start the contraction motion downwards before the second one animates upwards."> + <div> + <TransitionNotif /> + <SourceReader componentName={docSrc + 'TransitionNotif.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default Notification; diff --git a/front/odiparpack/app/containers/UiElements/Paginations.js b/front/odiparpack/app/containers/UiElements/Paginations.js new file mode 100644 index 0000000..7f6fa47 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/Paginations.js @@ -0,0 +1,45 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { TbPagination, TbPaginationCustom, GeneralPagination } from './demos'; + +class Paginations extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/Pagination/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Table Pagination" desc="The Action property of the TablePagination component allows the implementation of custom actions."> + <div> + <TbPagination /> + <SourceReader componentName={docSrc + 'TbPagination.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Table Pagination Custom" desc="The Action property of the TablePagination component allows the implementation of custom actions."> + <div> + <TbPaginationCustom /> + <SourceReader componentName={docSrc + 'TbPaginationCustom.js'} /> + </div> + </PapperBlock> + <PapperBlock title="General Pagination" desc="React.js pagination component based on ultimate-pagination. It's implemented as a higher-order component that allows easy integration of react-ultimate-pagination with different CSS frameworks or approaches."> + <div> + <GeneralPagination /> + <SourceReader componentName={docSrc + 'GeneralPagination.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default Paginations; diff --git a/front/odiparpack/app/containers/UiElements/PopoverTooltip.js b/front/odiparpack/app/containers/UiElements/PopoverTooltip.js new file mode 100644 index 0000000..508129b --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/PopoverTooltip.js @@ -0,0 +1,95 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Grid } from '@material-ui/core'; +import { + SimpleTooltips, + PositionedTooltips, + SimplePopover, + PopoverPlayground, + DelayTooltips, + TransitionsTooltips, + TriggersTooltips, + CustomizedTooltips +} from './demos'; + +class PopoverTooltip extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/PopoverTooltip/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Simple Tooltips" desc="The tooltips are text labels that appear when the user hovers over, focuses on, or touches an element."> + <div> + <SimpleTooltips /> + <SourceReader componentName={docSrc + 'SimpleTooltips.js'} /> + </div> + </PapperBlock> + <PapperBlock overflowX title="Positioned Tooltips" desc="The Tooltip has 12 placements choice. They don’t have directional arrows; instead, they rely on motion emanating from the source to convey direction."> + <div> + <PositionedTooltips /> + <SourceReader componentName={docSrc + 'PositionedTooltips.js'} /> + </div> + </PapperBlock> + <Grid container spacing={2}> + <Grid item md={6} xs={12}> + <PapperBlock title="Triggers Tooltips" desc="You can define the types of events that cause a tooltip to show."> + <div> + <TriggersTooltips /> + <SourceReader componentName={docSrc + 'TriggersTooltips.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6} xs={12}> + <PapperBlock title="Delay Tooltip" desc="A delay in showing or hiding the tooltip can be added also"> + <div> + <DelayTooltips /> + <SourceReader componentName={docSrc + 'DelayTooltips.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6} xs={12}> + <PapperBlock title="Transitions Tooltips" desc="Tooltips with different transition."> + <div> + <TransitionsTooltips /> + <SourceReader componentName={docSrc + 'TransitionsTooltips.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6} xs={12}> + <PapperBlock title="Customized Tooltips" desc="The tooltip with customized css style"> + <div> + <CustomizedTooltips /> + <SourceReader componentName={docSrc + 'CustomizedTooltips.js'} /> + </div> + </PapperBlock> + </Grid> + </Grid> + <PapperBlock title="Simple Popover" desc="A Popover can be used to display some content on top of another."> + <div> + <SimplePopover /> + <SourceReader componentName={docSrc + 'SimplePopover.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Popover playground" desc="Use the radio buttons to adjust the anchorOrigin and transformOrigin positions. You can also set the anchorReference to anchorPosition or anchorEl."> + <div> + <PopoverPlayground /> + <SourceReader componentName={docSrc + 'PopoverPlayground.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default PopoverTooltip; diff --git a/front/odiparpack/app/containers/UiElements/Progress.js b/front/odiparpack/app/containers/UiElements/Progress.js new file mode 100644 index 0000000..669da02 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/Progress.js @@ -0,0 +1,126 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Grid } from '@material-ui/core'; +import { + CircularStatic, + CircularIndeterminate, + CircularDeterminate, + CircularIntegration, + LinearStatic, + LinearIndeterminate, + LinearDeterminate, + LinearBuffer, + LinearQuery, + ProgressDelay +} from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class Progress extends React.Component { + render() { + const { classes } = this.props; + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/Progress/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <div className={classes.root}> + <Grid container spacing={3}> + <Grid item md={6} xs={12}> + <PapperBlock title="Circular Static" desc="Progress and activity indicators are visual indications of an app loading content."> + <div> + <CircularStatic /> + <SourceReader componentName={docSrc + 'CircularStatic.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Circular Determinate" desc="Indicators display how long an operation will take."> + <div> + <CircularDeterminate /> + <SourceReader componentName={docSrc + 'CircularDeterminate.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6} xs={12}> + <PapperBlock title="Circular Indeterminate" desc="Indicators visualize an unspecified wait time."> + <div> + <CircularIndeterminate /> + <SourceReader componentName={docSrc + 'CircularIndeterminate.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Circular Integration" desc="Visual indicator should be used to represent each type of operation."> + <div> + <CircularIntegration /> + <SourceReader componentName={docSrc + 'CircularIntegration.js'} /> + </div> + </PapperBlock> + </Grid> + </Grid> + <PapperBlock title="Linear Static" desc=""> + <div> + <LinearStatic /> + <SourceReader componentName={docSrc + 'LinearStatic.js'} /> + </div> + </PapperBlock> + <Grid container spacing={3}> + <Grid item md={6} xs={12}> + <PapperBlock title="Linear Determinate" desc=""> + <div> + <LinearDeterminate /> + <SourceReader componentName={docSrc + 'LinearDeterminate.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Linear Buffer" desc=""> + <div> + <LinearBuffer /> + <SourceReader componentName={docSrc + 'LinearBuffer.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Linear Query" desc=""> + <div> + <LinearQuery /> + <SourceReader componentName={docSrc + 'LinearQuery.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6} xs={12}> + <PapperBlock title="Linear Indeterminate" desc=""> + <div> + <LinearIndeterminate /> + <SourceReader componentName={docSrc + 'LinearIndeterminate.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Progress Delay Appearance" desc="There are 3 important limits to know around response time. The ripple effect of the ButtonBase component ensures that the user feels that the system is reacting instantaneously."> + <div> + <ProgressDelay /> + <SourceReader componentName={docSrc + 'ProgressDelay.js'} /> + </div> + </PapperBlock> + </Grid> + </Grid> + </div> + </div> + ); + } +} + +Progress.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Progress); diff --git a/front/odiparpack/app/containers/UiElements/SliderCarousel.js b/front/odiparpack/app/containers/UiElements/SliderCarousel.js new file mode 100644 index 0000000..b624774 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/SliderCarousel.js @@ -0,0 +1,77 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { + SingleCarousel, + MultipleCarousel, + AutoplayCarousel, + ThumbnailCarousel, + VerticalCarousel, + CustomCarousel, + AnimatedSlider +} from './demos'; + +class SliderCarousel extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/SliderCaraousel/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Animated Slider" desc="A Slider/Carousel component for React supporting custom css animations."> + <div> + <AnimatedSlider /> + <SourceReader componentName={docSrc + 'AnimatedSlider.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Simple Slider" desc="React slick is a carousel component built with React. It is a react port of slick carousel"> + <div> + <SingleCarousel /> + <SourceReader componentName={docSrc + 'SingleCarousel.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Carousel" desc=""> + <div> + <MultipleCarousel /> + <SourceReader componentName={docSrc + 'MultipleCarousel.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Autoplay Carousel" desc=""> + <div> + <AutoplayCarousel /> + <SourceReader componentName={docSrc + 'AutoplayCarousel.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Carousel with Thumbnail Pagination" desc=""> + <div> + <ThumbnailCarousel /> + <SourceReader componentName={docSrc + 'ThumbnailCarousel.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Vertical Carousel" desc=""> + <div> + <VerticalCarousel /> + <SourceReader componentName={docSrc + 'VerticalCarousel.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Custom Navigation Carousel" desc=""> + <div> + <CustomCarousel /> + <SourceReader componentName={docSrc + 'CustomCarousel.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default SliderCarousel; diff --git a/front/odiparpack/app/containers/UiElements/Steppers.js b/front/odiparpack/app/containers/UiElements/Steppers.js new file mode 100644 index 0000000..358f641 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/Steppers.js @@ -0,0 +1,70 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { + HorizontalLinear, + HorizontalNonLinear, + StepperError, + VerticalStepper, + MobileSteppers, + StepperCarousel +} from './demos'; + +class Steppers extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/Steppers/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Horizontal Linear" desc="The Stepper can be controlled by passing the current step index (zero-based) as the activeStep property."> + <div> + <HorizontalLinear /> + <SourceReader componentName={docSrc + 'HorizontalLinear.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Horizontal Non-linear" desc="Non-linear steppers allow users to enter a multi-step flow at any point."> + <div> + <HorizontalNonLinear /> + <SourceReader componentName={docSrc + 'HorizontalNonLinear.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Horizontal Non Linear - Error Step" desc=""> + <div> + <StepperError /> + <SourceReader componentName={docSrc + 'StepperError.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Vertical Stepper" desc=""> + <div> + <VerticalStepper /> + <SourceReader componentName={docSrc + 'VerticalStepper.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Mobile Stepper" desc="Use a progress bar or dots when there are many steps, or if there are steps that need to be inserted during the process (based on responses to earlier steps)."> + <div> + <MobileSteppers /> + <SourceReader componentName={docSrc + 'MobileSteppers.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Carousel Stepper" desc="This is essentially a back/next button positioned correctly. You must implement the textual description yourself, however, an example is provided below for reference."> + <div> + <StepperCarousel /> + <SourceReader componentName={docSrc + 'StepperCarousel.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default Steppers; diff --git a/front/odiparpack/app/containers/UiElements/Tabs.js b/front/odiparpack/app/containers/UiElements/Tabs.js new file mode 100644 index 0000000..0782462 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/Tabs.js @@ -0,0 +1,120 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { withStyles } from '@material-ui/core/styles'; +import PropTypes from 'prop-types'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Grid } from '@material-ui/core'; +import { + SimpleTabs, + LongTextTabs, + FixedTabs, + CenteredTabs, + IconTabs, + ScrollTabs, + ScrollIconTabs, + DisabledTab, + CustomTabs, + BottomNav +} from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class Tabs extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const { classes } = this.props; + const docSrc = 'containers/UiElements/demos/Tabs/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <div className={classes.root}> + <PapperBlock title="Simple Tabs" desc="A simple example with no frills."> + <div> + <SimpleTabs /> + <SourceReader componentName={docSrc + 'SimpleTabs.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Wrapped Labels" desc="Long labels will automatically wrap on tabs. If the label is too long for the tab, it will overflow and the text will not be visible."> + <div> + <LongTextTabs /> + <SourceReader componentName={docSrc + 'LongTextTabs.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Fixed Tabs" desc="Fixed tabs should be used with a limited number of tabs and when consistent placement will aid muscle memory."> + <div> + <FixedTabs /> + <SourceReader componentName={docSrc + 'FixedTabs.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Centered" desc="The centered property should be used for larger views."> + <div> + <CenteredTabs /> + <SourceReader componentName={docSrc + 'CenteredTabs.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Icon Tabs" desc="Tab labels may be either all icons or all text."> + <div> + <IconTabs /> + <SourceReader componentName={docSrc + 'IconTabs.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Scrollable Tabs" desc="Left and right scroll buttons will automatically be presented on desktop and hidden on mobile. (based on viewport width)"> + <div> + <ScrollTabs /> + <SourceReader componentName={docSrc + 'ScrollTabs.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Scrollable Icon Tabs" desc=""> + <div> + <ScrollIconTabs /> + <SourceReader componentName={docSrc + 'ScrollIconTabs.js'} /> + </div> + </PapperBlock> + <Grid container spacing={3}> + <Grid item md={6}> + <PapperBlock title="Disabled Tab" desc="Tab may be disabled by setting disabled property."> + <div> + <DisabledTab /> + <SourceReader componentName={docSrc + 'DisabledTab.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={6}> + <PapperBlock title="Customized Tabs" desc="If you have been reading the overrides documentation page but you are not confident jumping in, here's an example of how you can change the main color of the Tabs."> + <div> + <CustomTabs /> + <SourceReader componentName={docSrc + 'CustomTabs.js'} /> + </div> + </PapperBlock> + </Grid> + </Grid> + <PapperBlock title="Bottom Navigation" desc="Bottom navigation bars make it easy to explore and switch between top-level views in a single tap."> + <div> + <BottomNav /> + <SourceReader componentName={docSrc + 'BottomNav.js'} /> + </div> + </PapperBlock> + </div> + </div> + ); + } +} + +Tabs.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Tabs); diff --git a/front/odiparpack/app/containers/UiElements/Tags.js b/front/odiparpack/app/containers/UiElements/Tags.js new file mode 100644 index 0000000..e8a5571 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/Tags.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { BasicTags, ArrayTags } from './demos'; + +class Tags extends React.Component { + render() { + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/Tags/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <PapperBlock title="Basic Tags" desc="Tags represent complex entities in small blocks, such as a contact."> + <div> + <BasicTags /> + <SourceReader componentName={docSrc + 'BasicTags.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Array Tags" desc="An example of rendering multiple Tags from an array of values. "> + <div> + <ArrayTags /> + <SourceReader componentName={docSrc + 'ArrayTags.js'} /> + </div> + </PapperBlock> + </div> + ); + } +} + +export default Tags; diff --git a/front/odiparpack/app/containers/UiElements/Typography.js b/front/odiparpack/app/containers/UiElements/Typography.js new file mode 100644 index 0000000..e1a88ba --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/Typography.js @@ -0,0 +1,92 @@ +import React from 'react'; +import { Helmet } from 'react-helmet'; +import brand from 'ba-api/brand'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { SourceReader, PapperBlock } from 'ba-components'; +import { Grid } from '@material-ui/core'; +import { + GeneralTypo, + Heading, + ListTypo, + AlignTypo, + ColouredTypo, + QuotesDemo +} from './demos'; + +const styles = ({ + root: { + flexGrow: 1, + } +}); + +class Typography extends React.Component { + render() { + const { classes } = this.props; + const title = brand.name + ' - UI Elements'; + const description = brand.desc; + const docSrc = 'containers/UiElements/demos/Typography/'; + return ( + <div> + <Helmet> + <title>{title}</title> + <meta name="description" content={description} /> + <meta property="og:title" content={title} /> + <meta property="og:description" content={description} /> + <meta property="twitter:title" content={title} /> + <meta property="twitter:description" content={description} /> + </Helmet> + <div className={classes.root}> + <Grid container spacing={3}> + <Grid item md={7} xs={12}> + <PapperBlock title="Genereal Typo" desc="The Roboto font will not be automatically loaded by Material-UI. The developer is responsible for loading all fonts used in their application. Roboto Font has a few easy ways to get started."> + <div> + <GeneralTypo /> + <SourceReader componentName={docSrc + 'GeneralTypo.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Text Alignment" desc=""> + <div> + <AlignTypo /> + <SourceReader componentName={docSrc + 'AlignTypo.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Coloured Text" desc=""> + <div> + <ColouredTypo /> + <SourceReader componentName={docSrc + 'ColouredTypo.js'} /> + </div> + </PapperBlock> + </Grid> + <Grid item md={5} xs={12}> + <PapperBlock title="Fuente - Roboto" desc=""> + <div> + <Heading /> + {/* <SourceReader componentName={docSrc + 'Heading.js'} /> */} + </div> + </PapperBlock> + <PapperBlock title="List" desc=""> + <div> + <ListTypo /> + <SourceReader componentName={docSrc + 'ListTypo.js'} /> + </div> + </PapperBlock> + <PapperBlock title="Quotes" desc=""> + <div> + <QuotesDemo /> + <SourceReader componentName={docSrc + 'QuotesDemo.js'} /> + </div> + </PapperBlock> + </Grid> + </Grid> + </div> + </div> + ); + } +} + +Typography.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(Typography); diff --git a/front/odiparpack/app/containers/UiElements/demos/Accordion/AdvancedAccordion.js b/front/odiparpack/app/containers/UiElements/demos/Accordion/AdvancedAccordion.js new file mode 100644 index 0000000..87cd84e --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Accordion/AdvancedAccordion.js @@ -0,0 +1,97 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + +import { + ExpansionPanel, + ExpansionPanelDetails, + ExpansionPanelSummary, + ExpansionPanelActions, + Typography, + Chip, + Button, + Divider, +} from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '100%', + }, + heading: { + fontSize: theme.typography.pxToRem(15), + }, + secondaryHeading: { + fontSize: theme.typography.pxToRem(15), + color: theme.palette.text.secondary, + }, + icon: { + verticalAlign: 'bottom', + height: 20, + width: 20, + }, + details: { + alignItems: 'center', + }, + column: { + flexBasis: '33.33%', + }, + helper: { + borderLeft: `2px solid ${theme.palette.divider}`, + padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`, + }, + link: { + color: theme.palette.secondary.main, + textDecoration: 'none', + '&:hover': { + textDecoration: 'underline', + }, + }, +}); + +function AdvancedAccordion(props) { + const { classes } = props; + return ( + <div className={classes.root}> + <ExpansionPanel defaultExpanded> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <div className={classes.column}> + <Typography className={classes.heading}>Location</Typography> + </div> + <div className={classes.column}> + <Typography className={classes.secondaryHeading}>Select trip destination</Typography> + </div> + </ExpansionPanelSummary> + <ExpansionPanelDetails className={classes.details}> + <div className={classes.column} /> + <div className={classes.column}> + <Chip label="Barbados" className={classes.chip} onDelete={() => {}} /> + </div> + <div className={classNames(classes.column, classes.helper)}> + <Typography variant="caption"> + Select your destination of choice + <br /> + <a href="#sub-labels-and-columns" className={classes.link}> + Learn more + </a> + </Typography> + </div> + </ExpansionPanelDetails> + <Divider /> + <ExpansionPanelActions> + <Button size="small">Cancel</Button> + <Button size="small" color="secondary"> + Save + </Button> + </ExpansionPanelActions> + </ExpansionPanel> + </div> + ); +} + +AdvancedAccordion.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(AdvancedAccordion); diff --git a/front/odiparpack/app/containers/UiElements/demos/Accordion/ControlledAccordion.js b/front/odiparpack/app/containers/UiElements/demos/Accordion/ControlledAccordion.js new file mode 100644 index 0000000..6bcc2c8 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Accordion/ControlledAccordion.js @@ -0,0 +1,100 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + +import { ExpansionPanel, ExpansionPanelSummary, ExpansionPanelDetails, Typography } from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '100%', + }, + heading: { + fontSize: theme.typography.pxToRem(15), + flexBasis: '33.33%', + flexShrink: 0, + }, + secondaryHeading: { + fontSize: theme.typography.pxToRem(15), + color: theme.palette.text.secondary, + }, +}); + +class ControlledAccordion extends React.Component { + state = { + expanded: null, + }; + + handleChange = panel => (event, expanded) => { + this.setState({ + expanded: expanded ? panel : false, + }); + }; + + render() { + const { classes } = this.props; + const { expanded } = this.state; + + return ( + <div className={classes.root}> + <ExpansionPanel expanded={expanded === 'panel1'} onChange={this.handleChange('panel1')}> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>General settings</Typography> + <Typography className={classes.secondaryHeading}>I am an expansion panel</Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + Nulla facilisi. Phasellus sollicitudin nulla et quam mattis feugiat. Aliquam eget + maximus est, id dignissim quam. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + <ExpansionPanel expanded={expanded === 'panel2'} onChange={this.handleChange('panel2')}> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Users</Typography> + <Typography className={classes.secondaryHeading}> + You are currently not an owner + </Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + Donec placerat, lectus sed mattis semper, neque lectus feugiat lectus, varius pulvinar + diam eros in elit. Pellentesque convallis laoreet laoreet. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + <ExpansionPanel expanded={expanded === 'panel3'} onChange={this.handleChange('panel3')}> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Advanced settings</Typography> + <Typography className={classes.secondaryHeading}> + Filtering has been entirely disabled for whole web server + </Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + Nunc vitae orci ultricies, auctor nunc in, volutpat nisl. Integer sit amet egestas + eros, vitae egestas augue. Duis vel est augue. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + <ExpansionPanel expanded={expanded === 'panel4'} onChange={this.handleChange('panel4')}> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Personal data</Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + Nunc vitae orci ultricies, auctor nunc in, volutpat nisl. Integer sit amet egestas + eros, vitae egestas augue. Duis vel est augue. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + </div> + ); + } +} + +ControlledAccordion.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ControlledAccordion); diff --git a/front/odiparpack/app/containers/UiElements/demos/Accordion/SimpleAccordion.js b/front/odiparpack/app/containers/UiElements/demos/Accordion/SimpleAccordion.js new file mode 100644 index 0000000..a88f772 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Accordion/SimpleAccordion.js @@ -0,0 +1,57 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + +import { ExpansionPanel, ExpansionPanelDetails, ExpansionPanelSummary, Typography } from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '100%', + }, + heading: { + fontSize: theme.typography.pxToRem(15), + fontWeight: theme.typography.fontWeightRegular, + }, +}); + +function SimpleAccordion(props) { + const { classes } = props; + return ( + <div className={classes.root}> + <ExpansionPanel> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Expansion Panel 1</Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, + sit amet blandit leo lobortis eget. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + <ExpansionPanel> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Expansion Panel 2</Typography> + </ExpansionPanelSummary> + <ExpansionPanelDetails> + <Typography> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex, + sit amet blandit leo lobortis eget. + </Typography> + </ExpansionPanelDetails> + </ExpansionPanel> + <ExpansionPanel disabled> + <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}> + <Typography className={classes.heading}>Disabled Expansion Panel</Typography> + </ExpansionPanelSummary> + </ExpansionPanel> + </div> + ); +} + +SimpleAccordion.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(SimpleAccordion); diff --git a/front/odiparpack/app/containers/UiElements/demos/Avatars/AvatarsDemo.js b/front/odiparpack/app/containers/UiElements/demos/Avatars/AvatarsDemo.js new file mode 100644 index 0000000..e21391e --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Avatars/AvatarsDemo.js @@ -0,0 +1,112 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { withStyles } from '@material-ui/core/styles'; +import FolderIcon from '@material-ui/icons/Folder'; +import PageviewIcon from '@material-ui/icons/Pageview'; +import AssignmentIcon from '@material-ui/icons/Assignment'; +import avatarApi from 'ba-api/avatars'; + +import { pink, green, deepOrange, deepPurple } from '@material-ui/core/colors'; + +import { Typography, Avatar, Grid } from '@material-ui/core'; + +const styles = ({ + row: { + display: 'flex', + justifyContent: 'flex-start', + }, + avatar: { + margin: 10, + }, + bigAvatar: { + width: 60, + height: 60, + }, + pinkAvatar: { + margin: 10, + color: '#fff', + backgroundColor: pink[500], + }, + greenAvatar: { + margin: 10, + color: '#fff', + backgroundColor: green[500], + }, + orangeAvatar: { + margin: 10, + color: '#fff', + backgroundColor: deepOrange[500], + }, + purpleAvatar: { + margin: 10, + color: '#fff', + backgroundColor: deepPurple[500], + }, +}); + +class AvatarsDemo extends PureComponent { + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid + item + md={4} + > + <Typography variant="button" className={classes.divider}>Image Avatars</Typography> + <div className={classes.row}> + <Avatar alt="Remy Sharp" src={avatarApi[7]} className={classes.avatar} /> + <Avatar + alt="Adelle Charles" + src={avatarApi[5]} + className={classNames(classes.avatar, classes.bigAvatar)} + /> + </div> + </Grid> + <Grid + item + md={4} + > + <Typography variant="button" className={classes.divider}>Icon Avatars</Typography> + <div className={classes.row}> + <Avatar className={classes.avatar}> + <FolderIcon /> + </Avatar> + <Avatar className={classes.pinkAvatar}> + <PageviewIcon /> + </Avatar> + <Avatar className={classes.greenAvatar}> + <AssignmentIcon /> + </Avatar> + </div> + </Grid> + <Grid + item + md={4} + > + <Typography variant="button" className={classes.divider}>Icon Avatars</Typography> + <div className={classes.row}> + <Avatar className={classes.avatar}>H</Avatar> + <Avatar className={classes.orangeAvatar}>N</Avatar> + <Avatar className={classes.purpleAvatar}>OP</Avatar> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +AvatarsDemo.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(AvatarsDemo); diff --git a/front/odiparpack/app/containers/UiElements/demos/Avatars/AvatarsPractice.js b/front/odiparpack/app/containers/UiElements/demos/Avatars/AvatarsPractice.js new file mode 100644 index 0000000..a95b702 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Avatars/AvatarsPractice.js @@ -0,0 +1,212 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import ImageIcon from '@material-ui/icons/Image'; +import FavoriteIcon from '@material-ui/icons/Favorite'; +import WorkIcon from '@material-ui/icons/Work'; +import BeachAccessIcon from '@material-ui/icons/BeachAccess'; +import ShareIcon from '@material-ui/icons/Share'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import FaceIcon from '@material-ui/icons/Face'; +import imgApi from 'ba-api/images'; + +import { red, green, amber } from '@material-ui/core/colors'; + +import { + Typography, + Card, + CardHeader, + CardMedia, + CardContent, + CardActions, + IconButton, + Avatar, + List, + ListItem, + ListItemText, + ListItemAvatar, + Grid, + Chip, + Divider, +} from '@material-ui/core'; + +const styles = theme => ({ + row: { + display: 'flex', + justifyContent: 'flex-start', + }, + chip: { + margin: theme.spacing(1), + }, + card: { + maxWidth: 400, + }, + media: { + height: 0, + paddingTop: '56.25%', // 16:9 + }, + actions: { + display: 'flex', + }, + expand: { + transform: 'rotate(0deg)', + transition: theme.transitions.create('transform', { + duration: theme.transitions.duration.shortest, + }), + marginLeft: 'auto', + }, + expandOpen: { + transform: 'rotate(180deg)', + }, + root: { + width: '100%', + maxWidth: 360, + backgroundColor: theme.palette.background.paper, + }, + avatarRed: { + backgroundColor: red[500], + }, + avatarGreen: { + backgroundColor: green[500], + }, + avatarAmber: { + backgroundColor: amber[500], + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + } +}); + +function handleDelete() { + alert('You clicked the delete icon.'); // eslint-disable-line no-alert +} + +function handleClick() { + alert('You clicked the Chip.'); // eslint-disable-line no-alert +} + +class AvatarsDemo extends PureComponent { + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid item md={6}> + <Typography variant="button" className={classes.divider}>Avatars in Tag(Chip)</Typography> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={1} + > + <Chip + avatar={<Avatar>MB</Avatar>} + label="Clickable Chip" + onClick={handleClick} + className={classes.chip} + /> + <Chip + avatar={<Avatar src="/images/pp_boy.svg" />} + label="Deletable Chip" + onDelete={handleDelete} + className={classes.chip} + /> + <Chip + avatar={( + <Avatar> + <FaceIcon /> + </Avatar> + )} + label="Clickable Deletable Chip" + onClick={handleClick} + onDelete={handleDelete} + className={classes.chip} + /> + </Grid> + <Divider className={classes.divider} /> + <Typography variant="button" className={classes.divider}>Avatars in List Menu</Typography> + <div className={classes.root}> + <List> + <ListItem> + <ListItemAvatar> + <Avatar className={classes.avatarRed}> + <ImageIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Photos" secondary="Jan 9, 2014" /> + </ListItem> + <ListItem> + <ListItemAvatar> + <Avatar className={classes.avatarGreen}> + <WorkIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Work" secondary="Jan 7, 2014" /> + </ListItem> + <ListItem> + <ListItemAvatar> + <Avatar className={classes.avatarAmber}> + <BeachAccessIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Vacation" secondary="July 20, 2014" /> + </ListItem> + </List> + </div> + </Grid> + <Grid item md={6}> + <Typography variant="button" className={classes.divider}>Avatars in Social Media</Typography> + <div> + <Card className={classes.card}> + <CardHeader + avatar={ + <Avatar src="/images/pp_girl.svg" /> + } + action={( + <IconButton> + <MoreVertIcon /> + </IconButton> + )} + title="Aliquam nec ex aliquet" + subheader="September 14, 2018" + /> + <CardMedia + className={classes.media} + image={imgApi[7]} + title="Image" + /> + <CardContent> + <Typography component="p"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum. + </Typography> + </CardContent> + <CardActions className={classes.actions}> + <IconButton aria-label="Add to favorites"> + <FavoriteIcon /> + </IconButton> + <IconButton aria-label="Share"> + <ShareIcon /> + </IconButton> + </CardActions> + </Card> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +AvatarsDemo.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(AvatarsDemo); diff --git a/front/odiparpack/app/containers/UiElements/demos/Badges/CommonBadges.js b/front/odiparpack/app/containers/UiElements/demos/Badges/CommonBadges.js new file mode 100644 index 0000000..b9dca43 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Badges/CommonBadges.js @@ -0,0 +1,121 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles, MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'; +import { pink, teal } from '@material-ui/core/colors'; + +import { Badge, Typography, Grid, Button } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + alone: { + position: 'relative', + margin: 20 + }, + field: { + margin: '10px', + position: 'relative' + }, + cssRoot: { + '& span': { + backgroundColor: pink[700], + color: theme.palette.getContrastText(pink[500]), + }, + }, +}); + +const theme = createMuiTheme({ + palette: { + primary: teal, + secondary: pink + }, +}); + +class CommonBadges extends PureComponent { + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid + item + md={6} + > + <Typography variant="button" className={classes.divider}>Button Badges</Typography> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={3} + > + <div className={classes.field}> + <Badge color="primary" badgeContent={4} className={classes.margin}> + <Button variant="contained">Button</Button> + </Badge> + </div> + <div className={classes.field}> + <Badge color="secondary" badgeContent={4} className={classes.margin}> + <Button variant="contained" color="primary">Button</Button> + </Badge> + </div> + <div className={classes.field}> + <Badge color="primary" badgeContent={4} className={classes.margin}> + <Button variant="contained" color="secondary">Button</Button> + </Badge> + </div> + <div className={classes.field}> + <MuiThemeProvider theme={theme}> + <Badge color="primary" badgeContent={4} className={classes.margin}> + <Button variant="contained" color="secondary">Button</Button> + </Badge> + </MuiThemeProvider> + </div> + </Grid> + </Grid> + <Grid + item + md={6} + > + <Typography variant="button" className={classes.divider}>Text Badges</Typography> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={5} + > + <div className={classes.field}> + <Badge color="primary" badgeContent={4} className={classes.margin}> + <Typography className={classes.padding}>Badge Text</Typography> + </Badge> + </div> + <div className={classes.field}> + <Badge color="secondary" badgeContent={4} className={classes.margin}> + <Typography variant="button" className={classes.padding}>Badges Bold Text</Typography> + </Badge> + </div> + </Grid> + </Grid> + </Grid> + </Fragment> + ); + } +} + +CommonBadges.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CommonBadges); diff --git a/front/odiparpack/app/containers/UiElements/demos/Badges/VariantBadges.js b/front/odiparpack/app/containers/UiElements/demos/Badges/VariantBadges.js new file mode 100644 index 0000000..8dcc485 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Badges/VariantBadges.js @@ -0,0 +1,117 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import MailIcon from '@material-ui/icons/Mail'; +import { LimitedBadges } from 'ba-components'; + +import { Badge, Typography, Grid, IconButton, AppBar, Tabs, Tab } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + margin: `${theme.spacing(3)}px 0`, + }, + alone: { + position: 'relative', + margin: 20 + }, + field: { + margin: '10px', + position: 'relative' + }, + margin: { + margin: theme.spacing(2), + }, + padding: { + padding: `0 ${theme.spacing(2)}px`, + }, + autoscale: { + '& span': { + width: 'auto', + padding: 2 + } + } +}); + +class CommonBadges extends PureComponent { + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid + item + md={3} + > + <Typography variant="button" className={classes.divider}>Icon Badges</Typography> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + > + <div className={classes.field}> + <Badge className={classes.margin} badgeContent={4} color="secondary"> + <MailIcon /> + </Badge> + </div> + <div className={classes.field}> + <IconButton> + <Badge badgeContent={10} color="primary"> + <MailIcon /> + </Badge> + </IconButton> + </div> + <div className={classes.field}> + <LimitedBadges className={classes.margin} value={300} limit={99} color="secondary"> + <MailIcon /> + </LimitedBadges> + </div> + <div className={classNames(classes.padding, classes.autoscale)}> + <IconButton> + <LimitedBadges className={classes.margin} value={3000} limit={999} color="primary"> + <MailIcon /> + </LimitedBadges> + </IconButton> + </div> + </Grid> + </Grid> + <Grid + item + md={7} + > + <Typography variant="button" className={classes.divider}>Tab Badges</Typography> + <AppBar position="static"> + <Tabs value={0}> + <Tab + label={( + <Badge className={classes.padding} color="secondary" badgeContent={4}> + Item One + </Badge> + )} + /> + <Tab label="Item Two" /> + <Tab label="Item Three" /> + </Tabs> + </AppBar> + </Grid> + </Grid> + </Fragment> + ); + } +} + +CommonBadges.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CommonBadges); diff --git a/front/odiparpack/app/containers/UiElements/demos/Breadcrumbs/ClassicBreadcrumbs.js b/front/odiparpack/app/containers/UiElements/demos/Breadcrumbs/ClassicBreadcrumbs.js new file mode 100644 index 0000000..786a07b --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Breadcrumbs/ClassicBreadcrumbs.js @@ -0,0 +1,83 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { BreadCrumb } from 'ba-components'; + +import { Typography, Grid } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + margin: `${theme.spacing(3)}px 0`, + }, + field: { + margin: '10px', + position: 'relative' + }, +}); + +class ClassicBreadcrumbs extends PureComponent { + render() { + const { classes } = this.props; + const location = { pathname: '/grand-parent/parent/children' }; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid + item + md={4} + container + alignItems="center" + justify="center" + direction="column" + > + <Typography variant="button" className={classes.divider}>Arrow Separator</Typography> + <div className={classes.field}> + <BreadCrumb theme="dark" separator=" › " location={location} /> + </div> + </Grid> + <Grid + item + md={4} + container + alignItems="center" + justify="center" + direction="column" + > + <Typography variant="button" className={classes.divider}>Slash Separator</Typography> + <div className={classes.field}> + <BreadCrumb theme="dark" separator=" / " location={location} /> + </div> + </Grid> + <Grid + item + md={4} + container + alignItems="center" + justify="center" + direction="column" + > + <Typography variant="button" className={classes.divider}>Greater Than Separator</Typography> + <div className={classes.field}> + <BreadCrumb theme="dark" separator=" > " location={location} /> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +ClassicBreadcrumbs.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ClassicBreadcrumbs); diff --git a/front/odiparpack/app/containers/UiElements/demos/Breadcrumbs/PaperBreadcrumbs.js b/front/odiparpack/app/containers/UiElements/demos/Breadcrumbs/PaperBreadcrumbs.js new file mode 100644 index 0000000..70effb0 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Breadcrumbs/PaperBreadcrumbs.js @@ -0,0 +1,97 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { BreadCrumb } from 'ba-components'; + +import { Typography, Grid, Paper } from '@material-ui/core'; + +const styles = theme => ({ + demo: { + height: 'auto', + }, + divider: { + margin: `${theme.spacing(3)}px 0`, + }, + alone: { + position: 'relative', + margin: 20 + }, + field: { + margin: '10px', + position: 'relative' + }, + paper: { + padding: '5px 10px 1px', + borderRadius: 5 + } +}); + +class ClassicBreadcrumbs extends PureComponent { + render() { + const { classes } = this.props; + const location = { pathname: '/grand-parent/parent/children' }; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={1} + > + <Grid + item + md={6} + container + alignItems="center" + justify="center" + direction="column" + > + <Typography variant="button" className={classes.divider}>Arrow Separator</Typography> + <div className={classes.field}> + <Paper className={classes.paper}> + <BreadCrumb theme="dark" separator=" › " location={location} /> + </Paper> + </div> + </Grid> + <Grid + item + md={6} + container + alignItems="center" + justify="center" + direction="column" + > + <Typography variant="button" className={classes.divider}>Slash Separator</Typography> + <div className={classes.field}> + <Paper className={classes.paper}> + <BreadCrumb theme="dark" separator=" / " location={location} /> + </Paper> + </div> + </Grid> + <Grid + item + md={12} + container + alignItems="center" + justify="center" + direction="column" + > + <Typography variant="button" className={classes.divider}>Greater Than Separator</Typography> + <div className={classes.field}> + <Paper className={classes.paper}> + <BreadCrumb theme="dark" separator=" > " location={location} /> + </Paper> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +ClassicBreadcrumbs.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ClassicBreadcrumbs); diff --git a/front/odiparpack/app/containers/UiElements/demos/Cards/ControlCards.js b/front/odiparpack/app/containers/UiElements/demos/Cards/ControlCards.js new file mode 100644 index 0000000..1ddc92b --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Cards/ControlCards.js @@ -0,0 +1,204 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classnames from 'classnames'; +import FavoriteIcon from '@material-ui/icons/Favorite'; +import ShareIcon from '@material-ui/icons/Share'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import imgApi from 'ba-api/images'; +import { PlayerCard, VideoCard } from 'ba-components'; +import { red } from '@material-ui/core/colors'; + +import { + Typography, + Grid, + Card, + CardHeader, + CardMedia, + CardContent, + CardActions, + IconButton, + Collapse, + Avatar, +} from '@material-ui/core'; + +const styles = theme => ({ + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + card: { + display: 'flex', + justifyContent: 'space-between' + }, + details: { + display: 'flex', + flexDirection: 'column', + }, + content: { + flex: '1 0 auto', + }, + cover: { + width: 150, + height: 150, + }, + controls: { + display: 'flex', + alignItems: 'center', + paddingLeft: theme.spacing(1), + paddingBottom: theme.spacing(1), + }, + playIcon: { + height: 38, + width: 38, + }, + cardSocmed: { + maxWidth: 400, + }, + media: { + height: 0, + paddingTop: '56.25%', // 16:9 + }, + actions: { + display: 'flex', + }, + expand: { + transform: 'rotate(0deg)', + transition: theme.transitions.create('transform', { + duration: theme.transitions.duration.shortest, + }), + marginLeft: 'auto', + }, + expandOpen: { + transform: 'rotate(180deg)', + }, + avatar: { + backgroundColor: red[500], + }, +}); + +class ControlCard extends React.Component { + state = { expanded: false }; + + handleExpandClick = () => { + this.setState({ expanded: !this.state.expanded }); + }; + + render() { + const { classes } = this.props; + + return ( + <Grid + container + alignItems="center" + justify="flex-start" + spacing={2} + > + <Grid item md={6}> + <Typography variant="button" className={classes.divider}>UI Controls</Typography> + <div> + <PlayerCard + title="Live From Space" + artist="Mac Miller" + cover={imgApi[32]} + /> + </div> + </Grid> + <Grid item md={6}> + <Typography variant="button" className={classes.divider}>Video Thumb</Typography> + <VideoCard + title="Live From Space" + cover={imgApi[42]} + date="September 14, 2016" + /> + </Grid> + <Grid item md={12} container alignItems="center" direction="column"> + <Typography variant="button" className={classes.divider}>Complex Interaction</Typography> + <div> + <Card className={classes.cardSocmed}> + <CardHeader + avatar={( + <Avatar aria-label="Recipe" className={classes.avatar}> + R + </Avatar> + )} + action={( + <IconButton> + <MoreVertIcon /> + </IconButton> + )} + title="Shrimp and Chorizo Paella" + subheader="September 14, 2016" + /> + <CardMedia + className={classes.media} + image={imgApi[3]} + title="Contemplative Reptile" + /> + <CardContent> + <Typography component="p"> + This impressive paella is a perfect party dish and a fun meal to cook together with + your guests. Add 1 cup of frozen peas along with the mussels, if you like. + </Typography> + </CardContent> + <CardActions className={classes.actions}> + <IconButton aria-label="Add to favorites"> + <FavoriteIcon /> + </IconButton> + <IconButton aria-label="Share"> + <ShareIcon /> + </IconButton> + <IconButton + className={classnames(classes.expand, { + [classes.expandOpen]: this.state.expanded, + })} + onClick={this.handleExpandClick} + aria-expanded={this.state.expanded} + aria-label="Show more" + > + <ExpandMoreIcon /> + </IconButton> + </CardActions> + <Collapse in={this.state.expanded} timeout="auto" unmountOnExit> + <CardContent> + <Typography paragraph variant="body1"> + Method: + </Typography> + <Typography paragraph> + Heat 1/2 cup of the broth in a pot until simmering, add saffron and set aside for 10 + minutes. + </Typography> + <Typography paragraph> + Heat oil in a (14- to 16-inch) paella pan or a large, deep skillet over medium-high + heat. Add chicken, shrimp and chorizo, and cook, stirring occasionally until lightly + browned, 6 to 8 minutes. Transfer shrimp to a large plate and set aside, leaving + chicken and chorizo in the pan. Add pimentón, bay leaves, garlic, tomatoes, onion, + salt and pepper, and cook, stirring often until thickened and fragrant, about 10 + minutes. Add saffron broth and remaining 4 1/2 cups chicken broth; bring to a boil. + </Typography> + <Typography paragraph> + Add rice and stir very gently to distribute. Top with artichokes and peppers, and + cook without stirring, until most of the liquid is absorbed, 15 to 18 minutes. + Reduce heat to medium-low, add reserved shrimp and mussels, tucking them down into + the rice, and cook again without stirring, until mussels have opened and rice is + just tender, 5 to 7 minutes more. (Discard any mussels that don’t open.) + </Typography> + <Typography> + Set aside off of the heat to let rest for 10 minutes, and then serve. + </Typography> + </CardContent> + </Collapse> + </Card> + </div> + </Grid> + </Grid> + ); + } +} + +ControlCard.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ControlCard); diff --git a/front/odiparpack/app/containers/UiElements/demos/Cards/EcommerceCards.js b/front/odiparpack/app/containers/UiElements/demos/Cards/EcommerceCards.js new file mode 100644 index 0000000..6a5a275 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Cards/EcommerceCards.js @@ -0,0 +1,92 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import imgApi from 'ba-api/images'; +import { ProductCard } from 'ba-components'; + +import { Typography, Grid } from '@material-ui/core'; + +const styles = theme => ({ + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, +}); + +class EcommerceCard extends React.Component { + render() { + const { classes } = this.props; + return ( + <Grid + container + alignItems="flex-start" + justify="center" + direction="row" + spacing={2} + > + <Grid item md={4}> + <Typography variant="button" className={classes.divider}>Product Card</Typography> + <ProductCard + thumbnail={imgApi[21]} + name="Lorem ipsum dolor sit amet" + desc="Sed imperdiet enim ligula, vitae viverra justo porta vel. Duis eget felis bibendum, pretium mi sed, placerat ante." + ratting={5} + price={30} + /> + </Grid> + <Grid item md={4}> + <Typography variant="button" className={classes.divider}>Product with discount</Typography> + <ProductCard + discount="10%" + thumbnail={imgApi[22]} + name="Cras convallis lacus orci" + desc="Phasellus ante massa, aliquam non ante at" + ratting={3} + price={18} + prevPrice={20} + /> + </Grid> + <Grid item md={4}> + <Typography variant="button" className={classes.divider}>Sold Out Product</Typography> + <ProductCard + soldout + thumbnail={imgApi[23]} + name="Lorem ipsum dolor sit amet" + desc="Sed imperdiet enim ligula, vitae viverra justo porta vel. Duis eget felis bibendum, pretium mi sed, placerat ante." + ratting={4} + price={44} + /> + </Grid> + <Grid item md={12}> + <Typography variant="button" className={classes.divider}>List Mode</Typography> + <ProductCard + discount="10%" + thumbnail={imgApi[24]} + name="Lorem ipsum dolor sit amet" + desc="Sed imperdiet enim ligula, vitae viverra justo porta vel. Duis eget felis bibendum, pretium mi sed, placerat ante." + ratting={5} + price={30} + prevPrice={20} + list + /> + </Grid> + <Grid item md={12}> + <ProductCard + thumbnail={imgApi[25]} + name="Lorem ipsum dolor sit amet" + desc="Sed imperdiet enim ligula, vitae viverra justo porta vel. Duis eget felis bibendum, pretium mi sed, placerat ante." + ratting={0} + price={20} + list + /> + </Grid> + </Grid> + ); + } +} + +EcommerceCard.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(EcommerceCard); diff --git a/front/odiparpack/app/containers/UiElements/demos/Cards/PaperSheet.js b/front/odiparpack/app/containers/UiElements/demos/Cards/PaperSheet.js new file mode 100644 index 0000000..2867e38 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Cards/PaperSheet.js @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Paper, Typography } from '@material-ui/core'; + +const styles = theme => ({ + root: theme.mixins.gutters({ + paddingTop: 16, + paddingBottom: 16, + marginTop: theme.spacing(3), + }), +}); + +function PaperSheet(props) { + const { classes } = props; + return ( + <div> + <Paper className={classes.root} elevation={4}> + <Typography variant="h5" component="h3"> + This is a sheet of paper. + </Typography> + <Typography component="p"> + Paper can be used to build surface or other elements for your application. + </Typography> + </Paper> + </div> + ); +} + +PaperSheet.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(PaperSheet); diff --git a/front/odiparpack/app/containers/UiElements/demos/Cards/SocialCards.js b/front/odiparpack/app/containers/UiElements/demos/Cards/SocialCards.js new file mode 100644 index 0000000..7208926 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Cards/SocialCards.js @@ -0,0 +1,75 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import imgApi from 'ba-api/images'; +import avatarApi from 'ba-api/avatars'; +import { ProfileCard, PostCard } from 'ba-components'; + +import { Typography, Grid } from '@material-ui/core'; + +const styles = theme => ({ + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, +}); + +class StandardCard extends React.Component { + render() { + const { classes } = this.props; + + return ( + <Grid + container + alignItems="flex-start" + justify="center" + direction="row" + spacing={2} + > + <Grid item md={4}> + <Typography variant="button" className={classes.divider}>Profile Card</Typography> + <ProfileCard + cover={imgApi[42]} + avatar={avatarApi[6]} + name="John Doe" + title="UX designer" + connection={10} + btnText="Connect" + isVerified + /> + </Grid> + <Grid item md={4}> + <Typography variant="button" className={classes.divider}>Post Card</Typography> + <PostCard + liked={1} + shared={20} + commented={15} + date="Sept, 25 2018" + content="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum." + image={imgApi[11]} + avatar={avatarApi[10]} + name="Jim Doe" + /> + </Grid> + <Grid item md={4}> + <Typography variant="button" className={classes.divider}>Post Card (Without Image)</Typography> + <PostCard + liked={90} + shared={10} + commented={22} + date="Sept, 15 2018" + content="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum." + avatar={avatarApi[2]} + name="Jane Doe" + /> + </Grid> + </Grid> + ); + } +} + +StandardCard.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(StandardCard); diff --git a/front/odiparpack/app/containers/UiElements/demos/Cards/StandardCards.js b/front/odiparpack/app/containers/UiElements/demos/Cards/StandardCards.js new file mode 100644 index 0000000..37fdf39 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Cards/StandardCards.js @@ -0,0 +1,120 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import dummy from 'ba-api/dummyContents'; +import imgApi from 'ba-api/images'; +import { GeneralCard, NewsCard, Quote, IdentityCard } from 'ba-components'; + +import { Typography, Grid } from '@material-ui/core'; + +const styles = theme => ({ + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + card: { + minWidth: 275, + }, + bullet: { + display: 'inline-block', + margin: '0 2px', + transform: 'scale(0.8)', + }, + title: { + marginBottom: 16, + fontSize: 14, + }, + pos: { + marginBottom: 12, + }, + cardMedia: { + maxWidth: 345, + }, + media: { + height: 0, + paddingTop: '56.25%', // 16:9 + }, +}); + +class StandardCard extends React.Component { + render() { + const { classes } = this.props; + const bull = <span className={classes.bullet}>•</span>; + + return ( + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid item md={6}> + <Typography variant="button" className={classes.divider}>Simple Card</Typography> + <div> + <GeneralCard liked={1} shared={20} commented={15}> + <Typography className={classes.title} color="textSecondary"> + Word of the Day + </Typography> + <Typography variant="h5" component="h2"> + be + {bull} +nev + {bull} +o + {bull} +lent + </Typography> + <Typography className={classes.pos} color="textSecondary"> + adjective + </Typography> + <Typography component="p"> + well meaning and kindly. + <br /> + {'"a benevolent smile"'} + </Typography> + </GeneralCard> + </div> + <Typography variant="button" className={classes.divider}>Media</Typography> + <div> + <NewsCard + image={imgApi[8]} + title="Contemplative Reptile" + > + <Typography gutterBottom variant="h5" component="h2"> + Lorem ipsum + </Typography> + <Typography component="p"> + Aliquam venenatis magna et odio lobortis maximus. Nullam in tortor ligula. Proin maximus risus nunc + </Typography> + </NewsCard> + </div> + </Grid> + <Grid item md={6}> + <Typography variant="button" className={classes.divider}>Quoted Card</Typography> + <div> + <GeneralCard liked={1} shared={20} commented={15}> + <Quote align="left" content="Imagine all the people living life in peace. You may say I'm a dreamer, but I'm not the only one. I hope someday you'll join us, and the world will be as one." footnote="John Lennon" /> + </GeneralCard> + </div> + <div> + <Typography variant="button" className={classes.divider}>Identity Card</Typography> + <IdentityCard + title="Contact and Address Card" + name={dummy.user.name} + avatar={dummy.user.avatar} + phone="(+8543201213)" + address="Town Hall Building no.45 Block C - ABC Street" + /> + </div> + </Grid> + </Grid> + ); + } +} + +StandardCard.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(StandardCard); diff --git a/front/odiparpack/app/containers/UiElements/demos/DialogModal/AlertDialog.js b/front/odiparpack/app/containers/UiElements/demos/DialogModal/AlertDialog.js new file mode 100644 index 0000000..68f099a --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DialogModal/AlertDialog.js @@ -0,0 +1,127 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; + +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Grid, + Typography, + Slide, +} from '@material-ui/core'; + +const styles = theme => ({ + title: { + display: 'block', + margin: `${theme.spacing(4)}px 0 ${theme.spacing(2)}px`, + }, +}); + +const Transition = React.forwardRef(function Transition(props, ref) { // eslint-disable-line + return <Slide direction="up" ref={ref} {...props} />; +}); + +class AlertDialog extends React.Component { // eslint-disable-line + state = { + open: false, + openSlide: false, + }; + + handleClickOpen = () => { + this.setState({ open: true }); + }; + + handleClose = () => { + this.setState({ open: false }); + }; + + handleClickOpenSlide = () => { + this.setState({ openSlide: true }); + }; + + handleCloseSlide = () => { + this.setState({ openSlide: false }); + }; + + render() { + const { classes } = this.props; + return ( + <div> + <Grid container spacing={2}> + <Grid item md={6}> + <Typography variant="button" className={classes.title}> + Fade Transition + </Typography> + <Button color="secondary" onClick={this.handleClickOpen}>Open alert dialog</Button> + <Dialog + open={this.state.open} + onClose={this.handleClose} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + <DialogTitle id="alert-dialog-title"> + {"Use Google's location service?"} + </DialogTitle> + <DialogContent> + <DialogContentText id="alert-dialog-description"> + Let Google help apps determine location. This means sending anonymous location data to + Google, even when no apps are running. + </DialogContentText> + </DialogContent> + <DialogActions> + <Button onClick={this.handleClose} color="primary"> + Disagree + </Button> + <Button onClick={this.handleClose} color="primary" autoFocus> + Agree + </Button> + </DialogActions> + </Dialog> + </Grid> + <Grid item md={6}> + <Typography variant="button" className={classes.title}> + Slide Transition + </Typography> + <Button onClick={this.handleClickOpenSlide} color="primary">Slide in alert dialog</Button> + <Dialog + open={this.state.openSlide} + TransitionComponent={Transition} + keepMounted + onClose={this.handleCloseSlide} + aria-labelledby="alert-dialog-slide-title" + aria-describedby="alert-dialog-slide-description" + > + <DialogTitle id="alert-dialog-slide-title"> + {"Use Google's location service?"} + </DialogTitle> + <DialogContent> + <DialogContentText id="alert-dialog-slide-description"> + Let Google help apps determine location. This means sending anonymous location data to + Google, even when no apps are running. + </DialogContentText> + </DialogContent> + <DialogActions> + <Button onClick={this.handleClose} color="primary"> + Disagree + </Button> + <Button onClick={this.handleClose} color="primary"> + Agree + </Button> + </DialogActions> + </Dialog> + </Grid> + </Grid> + </div> + ); + } +} + +AlertDialog.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(AlertDialog); diff --git a/front/odiparpack/app/containers/UiElements/demos/DialogModal/ConfirmationDialog.js b/front/odiparpack/app/containers/UiElements/demos/DialogModal/ConfirmationDialog.js new file mode 100644 index 0000000..1f49788 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DialogModal/ConfirmationDialog.js @@ -0,0 +1,111 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { + Button, + DialogTitle, + DialogContent, + DialogActions, + Dialog, + Radio, + RadioGroup, + FormControlLabel, +} from '@material-ui/core'; + +const options = [ + 'None', + 'Atria', + 'Callisto', + 'Dione', + 'Ganymede', + 'Hangouts Call', + 'Luna', + 'Oberon', + 'Phobos', + 'Pyxis', + 'Sedna', + 'Titania', + 'Triton', + 'Umbriel', +]; + +class ConfirmationDialog extends React.Component { + constructor(props, context) { + super(props, context); + + this.state.value = this.props.value; + } + + state = {}; + + componentWillReceiveProps(nextProps) { + if (nextProps.value !== this.props.value) { + this.setState({ value: nextProps.value }); + } + } + + radioGroup = null; + + handleEntering = () => { + this.radioGroup.focus(); + }; + + handleCancel = () => { + this.props.onClose(this.props.value); + }; + + handleOk = () => { + this.props.onClose(this.state.value); + }; + + handleChange = (event, value) => { + this.setState({ value }); + }; + + render() { + const { value, ...other } = this.props; + + return ( + <Dialog + disableBackdropClick + disableEscapeKeyDown + maxWidth="xs" + onEntering={this.handleEntering} + aria-labelledby="confirmation-dialog-title" + {...other} + > + <DialogTitle id="confirmation-dialog-title">Phone Ringtone</DialogTitle> + <DialogContent> + <RadioGroup + ref={node => { + this.radioGroup = node; + }} + aria-label="ringtone" + name="ringtone" + value={this.state.value} + onChange={this.handleChange} + > + {options.map(option => ( + <FormControlLabel value={option} key={option} control={<Radio />} label={option} /> + ))} + </RadioGroup> + </DialogContent> + <DialogActions> + <Button onClick={this.handleCancel} color="primary"> + Cancel + </Button> + <Button onClick={this.handleOk} color="primary"> + Ok + </Button> + </DialogActions> + </Dialog> + ); + } +} + +ConfirmationDialog.propTypes = { + onClose: PropTypes.func.isRequired, + value: PropTypes.string.isRequired, +}; + +export default ConfirmationDialog; diff --git a/front/odiparpack/app/containers/UiElements/demos/DialogModal/FormDialog.js b/front/odiparpack/app/containers/UiElements/demos/DialogModal/FormDialog.js new file mode 100644 index 0000000..58bf1aa --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DialogModal/FormDialog.js @@ -0,0 +1,65 @@ +import React from 'react'; + +import { + Button, + TextField, + Grid, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, +} from '@material-ui/core'; + +export default class FormDialog extends React.Component { + state = { + open: false, + }; + + handleClickOpen = () => { + this.setState({ open: true }); + }; + + handleClose = () => { + this.setState({ open: false }); + }; + + render() { + return ( + <div> + <Grid container justify="center" direction="column"> + <Button variant="contained" color="secondary" onClick={this.handleClickOpen}>Open form dialog</Button> + <Dialog + open={this.state.open} + onClose={this.handleClose} + aria-labelledby="form-dialog-title" + > + <DialogTitle id="form-dialog-title">Subscribe</DialogTitle> + <DialogContent> + <DialogContentText> + To subscribe to this website, please enter your email address here. We will send + updates occasionally. + </DialogContentText> + <TextField + autoFocus + margin="dense" + id="name" + label="Email Address" + type="email" + fullWidth + /> + </DialogContent> + <DialogActions> + <Button onClick={this.handleClose} color="primary"> + Cancel + </Button> + <Button onClick={this.handleClose} color="primary"> + Subscribe + </Button> + </DialogActions> + </Dialog> + </Grid> + </div> + ); + } +} diff --git a/front/odiparpack/app/containers/UiElements/demos/DialogModal/FullScreenDialog.js b/front/odiparpack/app/containers/UiElements/demos/DialogModal/FullScreenDialog.js new file mode 100644 index 0000000..935ed4d --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DialogModal/FullScreenDialog.js @@ -0,0 +1,134 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import CloseIcon from '@material-ui/icons/Close'; + +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + List, + ListItem, + ListItemText, + Divider, + Grid, + AppBar, + Toolbar, + IconButton, + Typography, + Slide, +} from '@material-ui/core'; + +const styles = { + appBar: { + position: 'relative', + }, + flex: { + flex: 1, + }, +}; + +const Transition = React.forwardRef(function Transition(props, ref) { // eslint-disable-line + return <Slide direction="up" ref={ref} {...props} />; +}); + +class FullScreenDialog extends React.Component { // eslint-disable-line + state = { + open: false, + open2: false, + }; + + handleClickOpen = () => { + this.setState({ open: true }); + }; + + handleClose = () => { + this.setState({ open: false }); + }; + + handleClickOpen2 = () => { + this.setState({ open2: true }); + }; + + handleClose2 = () => { + this.setState({ open2: false }); + }; + + render() { + const { classes } = this.props; + return ( + <div> + <Grid container spacing={2}> + <Grid item container alignItems="center" justify="center" md={6}> + <Button variant="contained" color="primary" onClick={this.handleClickOpen}>Open full-screen dialog</Button> + <Dialog + fullScreen + open={this.state.open} + onClose={this.handleClose} + TransitionComponent={Transition} + > + <AppBar className={classes.appBar}> + <Toolbar> + <IconButton color="inherit" onClick={this.handleClose} aria-label="Close"> + <CloseIcon /> + </IconButton> + <Typography variant="h6" color="inherit" className={classes.flex}> + Sound + </Typography> + <Button color="inherit" onClick={this.handleClose}> + save + </Button> + </Toolbar> + </AppBar> + <List> + <ListItem button> + <ListItemText primary="Phone ringtone" secondary="Titania" /> + </ListItem> + <Divider /> + <ListItem button> + <ListItemText primary="Default notification ringtone" secondary="Tethys" /> + </ListItem> + </List> + </Dialog> + </Grid> + <Grid item container alignItems="center" justify="center" md={6}> + <Button variant="contained" color="secondary" onClick={this.handleClickOpen2}>Open responsive dialog</Button> + <Dialog + fullScreen + open={this.state.open2} + onClose={this.handleClose} + aria-labelledby="responsive-dialog-title" + > + <DialogTitle id="responsive-dialog-title"> + {'Use location service?'} + </DialogTitle> + <DialogContent> + <DialogContentText> + Let Google help apps determine location. This means sending anonymous location data to + Google, even when no apps are running. + </DialogContentText> + </DialogContent> + <DialogActions> + <Button onClick={this.handleClose2} color="primary"> + Disagree + </Button> + <Button onClick={this.handleClose2} color="primary" autoFocus> + Agree + </Button> + </DialogActions> + </Dialog> + </Grid> + </Grid> + </div> + ); + } +} + +FullScreenDialog.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(FullScreenDialog); diff --git a/front/odiparpack/app/containers/UiElements/demos/DialogModal/ImagePopup.js b/front/odiparpack/app/containers/UiElements/demos/DialogModal/ImagePopup.js new file mode 100644 index 0000000..169c5ec --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DialogModal/ImagePopup.js @@ -0,0 +1,51 @@ +import React, { Component } from 'react'; +import 'ba-styles/vendors/image-lightbox/image-lightbox.css'; +import images from 'ba-api/imgData'; +import { ImageLightbox } from 'ba-components'; + +import { Button, Grid } from '@material-ui/core'; + +export default class ImagePopup extends Component { + constructor(props) { + super(props); + + this.state = { + photoIndex: 0, + isOpen: false, + }; + } + + render() { + const { photoIndex, isOpen } = this.state; + + return ( + <Grid + container + alignItems="center" + justify="center" + direction="column" + > + <Button variant="contained" color="secondary" onClick={() => this.setState({ isOpen: true })}> + Open Image Lightbox + </Button> + + {isOpen && ( + <ImageLightbox + mainSrc={images[photoIndex].img} + nextSrc={images[(photoIndex + 1) % images.length].img} + prevSrc={images[(photoIndex + (images.length - 1)) % images.length].img} + onCloseRequest={() => this.setState({ isOpen: false })} + onMovePrevRequest={() => this.setState({ + photoIndex: (photoIndex + (images.length - 1)) % images.length, + }) + } + onMoveNextRequest={() => this.setState({ + photoIndex: (photoIndex + 1) % images.length, + }) + } + /> + )} + </Grid> + ); + } +} diff --git a/front/odiparpack/app/containers/UiElements/demos/DialogModal/ModalDemo.js b/front/odiparpack/app/containers/UiElements/demos/DialogModal/ModalDemo.js new file mode 100644 index 0000000..c18a5e1 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DialogModal/ModalDemo.js @@ -0,0 +1,73 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Typography, Modal, Button, Grid } from '@material-ui/core'; + +function getModalStyle() { + return { + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + }; +} + +const styles = theme => ({ + paper: { + position: 'absolute', + width: theme.spacing(50), + backgroundColor: theme.palette.background.paper, + boxShadow: theme.shadows[5], + padding: theme.spacing(4), + }, +}); + +class ModalDemo extends React.Component { + state = { + open: false, + }; + + handleOpen = () => { + this.setState({ open: true }); + }; + + handleClose = () => { + this.setState({ open: false }); + }; + + render() { + const { classes } = this.props; + + return ( + <Grid + container + alignItems="center" + justify="center" + direction="column" + > + <Typography gutterBottom>Click to get the full Modal experience!</Typography> + <Button variant="contained" color="secondary" onClick={this.handleOpen}>Open Modal</Button> + <Modal + aria-labelledby="simple-modal-title" + aria-describedby="simple-modal-description" + open={this.state.open} + onClose={this.handleClose} + > + <div style={getModalStyle()} className={classes.paper}> + <Typography variant="h6" id="modal-title"> + Text in a modal + </Typography> + <Typography variant="subtitle1" id="simple-modal-description"> + Duis mollis, est non commodo luctus, nisi erat porttitor ligula. + </Typography> + </div> + </Modal> + </Grid> + ); + } +} + +ModalDemo.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ModalDemo); diff --git a/front/odiparpack/app/containers/UiElements/demos/DialogModal/ScrollDialog.js b/front/odiparpack/app/containers/UiElements/demos/DialogModal/ScrollDialog.js new file mode 100644 index 0000000..b7f2b74 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DialogModal/ScrollDialog.js @@ -0,0 +1,93 @@ +import React from 'react'; + +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, +} from '@material-ui/core'; + +class ScrollDialog extends React.Component { + state = { + open: false, + scroll: 'paper', + }; + + handleClickOpen = scroll => () => { + this.setState({ open: true, scroll }); + }; + + handleClose = () => { + this.setState({ open: false }); + }; + + render() { + return ( + <div> + <Button variant="contained" color="secondary" onClick={this.handleClickOpen('paper')}>scroll=paper</Button> + + <Button variant="contained" color="secondary" onClick={this.handleClickOpen('body')}>scroll=body</Button> + <Dialog + open={this.state.open} + onClose={this.handleClose} + scroll={this.state.scroll} + aria-labelledby="scroll-dialog-title" + > + <DialogTitle id="scroll-dialog-title">Subscribe</DialogTitle> + <DialogContent> + <DialogContentText> + Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac + facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum + at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus + sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean lacinia bibendum + nulla sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur + et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla. Cras + mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, + egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. + Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis + lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean lacinia bibendum nulla + sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. + Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla. Cras mattis + consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, + egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. + Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis + lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean lacinia bibendum nulla + sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. + Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla. Cras mattis + consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, + egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. + Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis + lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean lacinia bibendum nulla + sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. + Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla. Cras mattis + consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, + egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. + Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis + lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean lacinia bibendum nulla + sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. + Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla. Cras mattis + consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, + egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. + Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Vivamus sagittis + lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean lacinia bibendum nulla + sed consectetur. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. + Donec sed odio dui. Donec ullamcorper nulla non metus auctor fringilla. + </DialogContentText> + </DialogContent> + <DialogActions> + <Button onClick={this.handleClose} color="primary"> + Cancel + </Button> + <Button onClick={this.handleClose} color="primary"> + Subscribe + </Button> + </DialogActions> + </Dialog> + </div> + ); + } +} + +export default ScrollDialog; diff --git a/front/odiparpack/app/containers/UiElements/demos/DialogModal/SelectDialog.js b/front/odiparpack/app/containers/UiElements/demos/DialogModal/SelectDialog.js new file mode 100644 index 0000000..f75b1be --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DialogModal/SelectDialog.js @@ -0,0 +1,120 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import PersonIcon from '@material-ui/icons/Person'; +import AddIcon from '@material-ui/icons/Add'; +import { blue } from '@material-ui/core/colors'; + +import { + Button, + Avatar, + List, + ListItem, + ListItemText, + ListItemAvatar, + Dialog, + DialogTitle, + Typography, + Grid, +} from '@material-ui/core'; + +const emails = ['[email protected]', '[email protected]']; +const styles = ({ + avatar: { + backgroundColor: blue[100], + color: blue[600], + }, +}); + +const SimpleDialog = props => { + const { + classes, + onClose, + selectedValue, + ...other + } = props; + + function handleClose() { + props.onClose(this.props.selectedValue); + } + + function handleListItemClick(value) { + props.onClose(value); + } + + return ( + <Dialog onClose={() => handleClose()} aria-labelledby="simple-dialog-title" {...other}> + <DialogTitle id="simple-dialog-title">Set backup account</DialogTitle> + <div> + <List> + {emails.map(email => ( + <ListItem button onClick={() => handleListItemClick(email)} key={email}> + <ListItemAvatar> + <Avatar className={classes.avatar}> + <PersonIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={email} /> + </ListItem> + ))} + <ListItem button onClick={() => handleListItemClick('addAccount')}> + <ListItemAvatar> + <Avatar> + <AddIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="add account" /> + </ListItem> + </List> + </div> + </Dialog> + ); +}; + +SimpleDialog.propTypes = { + classes: PropTypes.object.isRequired, + onClose: PropTypes.func.isRequired, + selectedValue: PropTypes.string.isRequired, +}; + +const SimpleDialogWrapped = withStyles(styles)(SimpleDialog); + +class SelectDialog extends React.Component { + state = { + open: false, + selectedValue: emails[1], + }; + + handleClickOpen = () => { + this.setState({ + open: true, + }); + }; + + handleClose = value => { + this.setState({ selectedValue: value, open: false }); + }; + + render() { + return ( + <div> + <Grid container justify="center" direction="column"> + <Typography variant="subtitle1"> +Selected: + <strong>{this.state.selectedValue}</strong> + </Typography> + <br /> + <Button variant="contained" color="primary" onClick={this.handleClickOpen}>Open simple dialog</Button> + <SimpleDialogWrapped + selectedValue={this.state.selectedValue} + open={this.state.open} + onClose={this.handleClose} + /> + </Grid> + </div> + ); + } +} + + +export default SelectDialog; diff --git a/front/odiparpack/app/containers/UiElements/demos/DialogModal/SelectRadioDialog.js b/front/odiparpack/app/containers/UiElements/demos/DialogModal/SelectRadioDialog.js new file mode 100644 index 0000000..ff30bd4 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DialogModal/SelectRadioDialog.js @@ -0,0 +1,68 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { List, ListItem, ListItemText } from '@material-ui/core'; +import ConfirmationDialog from './ConfirmationDialog'; + + +const styles = theme => ({ + root: { + width: '100%', + maxWidth: 360, + backgroundColor: theme.palette.background.paper, + }, + dialog: { + width: '80%', + maxHeight: 435, + }, +}); + +class SelectRadioDialog extends React.Component { + state = { + open: false, + value: 'Dione', + }; + + button = undefined; + + handleClickListItem = () => { + this.setState({ open: true }); + }; + + handleClose = value => { + this.setState({ value, open: false }); + }; + + render() { + const { classes } = this.props; + return ( + <div className={classes.root}> + <List> + <ListItem + button + aria-haspopup="true" + aria-controls="ringtone-menu" + aria-label="Phone ringtone" + onClick={this.handleClickListItem} + > + <ListItemText primary="Phone ringtone" secondary={this.state.value} /> + </ListItem> + <ConfirmationDialog + classes={{ + paper: classes.dialog, + }} + open={this.state.open} + onClose={this.handleClose} + value={this.state.value} + /> + </List> + </div> + ); + } +} + +SelectRadioDialog.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(SelectRadioDialog); diff --git a/front/odiparpack/app/containers/UiElements/demos/Dividers/CommonDividers.js b/front/odiparpack/app/containers/UiElements/demos/Dividers/CommonDividers.js new file mode 100644 index 0000000..db67c0f --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Dividers/CommonDividers.js @@ -0,0 +1,29 @@ +import React, { Fragment, PureComponent } from 'react'; +import Type from 'ba-styles/Typography.scss'; +import { + GradientDivider, + DashDivider, + ShadowDivider, + InsetDivider, +} from 'ba-components/Divider'; + +import { Typography } from '@material-ui/core'; + +class CommonDivider extends PureComponent { + render() { + return ( + <Fragment> + <Typography variant="button" className={Type.textCenter}>Gradient Divider</Typography> + <GradientDivider /> + <Typography variant="button" className={Type.textCenter}>Dash Divider</Typography> + <DashDivider /> + <Typography variant="button" className={Type.textCenter}>Shadow Divider</Typography> + <ShadowDivider /> + <Typography variant="button" className={Type.textCenter}>Inset Divider</Typography> + <InsetDivider /> + </Fragment> + ); + } +} + +export default CommonDivider; diff --git a/front/odiparpack/app/containers/UiElements/demos/Dividers/SpecialDividers.js b/front/odiparpack/app/containers/UiElements/demos/Dividers/SpecialDividers.js new file mode 100644 index 0000000..d857e06 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Dividers/SpecialDividers.js @@ -0,0 +1,23 @@ +import React, { Fragment, PureComponent } from 'react'; +import Type from 'ba-styles/Typography.scss'; +import { + FlairedEdgesDivider, + ContentDivider, +} from 'ba-components/Divider'; + +import { Typography } from '@material-ui/core'; + +class CommonDivider extends PureComponent { + render() { + return ( + <Fragment> + <Typography variant="button" className={Type.textCenter}>Flaired Edges Divider</Typography> + <FlairedEdgesDivider /> + <Typography variant="button" className={Type.textCenter}>Content Text Divider</Typography> + <ContentDivider content="OR" /> + </Fragment> + ); + } +} + +export default CommonDivider; diff --git a/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/BasicMenu.js b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/BasicMenu.js new file mode 100644 index 0000000..24039db --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/BasicMenu.js @@ -0,0 +1,74 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; + +import { Button, Menu, MenuItem, MenuList, Grid, Paper } from '@material-ui/core'; + +const styles = theme => ({ + root: { + display: 'flex', + }, + paper: { + marginRight: theme.spacing(2), + }, + popperClose: { + pointerEvents: 'none', + }, +}); + +class BasicMenu extends React.Component { + state = { + anchorEl: null, + }; + + handleClick = event => { + this.setState({ anchorEl: event.currentTarget }); + }; + + handleClose = () => { + this.setState({ anchorEl: null }); + }; + + render() { + const { anchorEl } = this.state; + const { classes } = this.props; + return ( + <Grid container spacing={2}> + <Grid item md={6}> + <Paper className={classes.paper}> + <MenuList> + <MenuItem>Profile</MenuItem> + <MenuItem>My account</MenuItem> + <MenuItem>Logout</MenuItem> + </MenuList> + </Paper> + </Grid> + <Grid item md={6}> + <Button + aria-owns={anchorEl ? 'simple-menu' : null} + aria-haspopup="true" + onClick={this.handleClick} + > + Open Menu + </Button> + <Menu + id="simple-menu" + anchorEl={anchorEl} + open={Boolean(anchorEl)} + onClose={this.handleClose} + > + <MenuItem onClick={this.handleClose}>Profile</MenuItem> + <MenuItem onClick={this.handleClose}>My account</MenuItem> + <MenuItem onClick={this.handleClose}>Logout</MenuItem> + </Menu> + </Grid> + </Grid> + ); + } +} + +BasicMenu.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(BasicMenu); diff --git a/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/DropdownMenu.js b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/DropdownMenu.js new file mode 100644 index 0000000..0e5bce7 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/DropdownMenu.js @@ -0,0 +1,148 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; + +import { IconButton, List, ListItem, ListItemText, Menu, MenuItem, Grid } from '@material-ui/core'; + +const styles = { + root: { + width: '100%', + }, +}; + +const options = [ + 'Show some love to Material-UI', + 'Show all notification content', + 'Hide sensitive notification content', + 'Hide all notification content', +]; + +const optionsOpt = [ + 'None', + 'Atria', + 'Callisto', + 'Dione', + 'Ganymede', + 'Hangouts Call', + 'Luna', + 'Oberon', + 'Phobos', + 'Pyxis', + 'Sedna', + 'Titania', + 'Triton', + 'Umbriel', +]; + +const ITEM_HEIGHT = 48; + +class DropdownMenu extends React.Component { + state = { + anchorEl: null, + anchorElOpt: null, + selectedIndex: 1, + }; + + button = undefined; + + handleClickListItem = event => { + this.setState({ anchorEl: event.currentTarget }); + }; + + handleMenuItemClick = (event, index) => { + this.setState({ selectedIndex: index, anchorEl: null }); + }; + + handleClose = () => { + this.setState({ anchorEl: null }); + }; + + handleClickOpt = event => { + this.setState({ anchorElOpt: event.currentTarget }); + }; + + handleCloseOpt = () => { + this.setState({ anchorElOpt: null }); + }; + + + render() { + const { classes } = this.props; + const { anchorEl, anchorElOpt } = this.state; + + return ( + <div className={classes.root}> + <Grid container spacing={2}> + <Grid item md={8}> + <List component="nav"> + <ListItem + button + aria-haspopup="true" + aria-controls="lock-menu" + aria-label="When device is locked" + onClick={this.handleClickListItem} + > + <ListItemText + primary="When device is locked" + secondary={options[this.state.selectedIndex]} + /> + </ListItem> + </List> + <Menu + id="lock-menu" + anchorEl={anchorEl} + open={Boolean(anchorEl)} + onClose={this.handleClose} + > + {options.map((option, index) => ( + <MenuItem + key={option} + disabled={index === 0} + selected={index === this.state.selectedIndex} + onClick={event => this.handleMenuItemClick(event, index)} + > + {option} + </MenuItem> + ))} + </Menu> + </Grid> + <Grid item md={4}> + <IconButton + aria-label="More" + aria-owns={anchorEl ? 'long-menu' : null} + aria-haspopup="true" + onClick={this.handleClickOpt} + > + <MoreVertIcon /> + </IconButton> + <Menu + id="long-menu" + anchorEl={anchorElOpt} + open={Boolean(anchorElOpt)} + onClose={this.handleCloseOpt} + PaperProps={{ + style: { + maxHeight: ITEM_HEIGHT * 4.5, + width: 200, + }, + }} + > + {optionsOpt.map(option => ( + <MenuItem key={option} selected={option === 'Pyxis'} onClick={this.handleCloseOpt}> + {option} + </MenuItem> + ))} + </Menu> + </Grid> + </Grid> + </div> + ); + } +} + +DropdownMenu.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(DropdownMenu); diff --git a/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/MenuTransition.js b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/MenuTransition.js new file mode 100644 index 0000000..e8cfbc6 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/MenuTransition.js @@ -0,0 +1,108 @@ +import React from 'react'; +import Button from '@material-ui/core/Button'; +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; +import Grow from '@material-ui/core/Grow'; +import Fade from '@material-ui/core/Fade'; +import Zoom from '@material-ui/core/Zoom'; +import Grid from '@material-ui/core/Grid'; + + +class MenuTransition extends React.Component { + state = { + anchorFade: null, + anchorGrow: null, + anchorCollapse: null, + anchorZoom: null, + }; + + handleClick = (event, type) => { + this.setState({ [type]: event.currentTarget }); + }; + + handleClose = type => { + this.setState({ [type]: null }); + }; + + handleToggle = type => { + // eslint-disable-next-line + this.setState({ [type]: !this.state[type] }); + }; + + render() { + const { + anchorFade, + anchorGrow, + anchorZoom + } = this.state; + + return ( + <Grid container spacing={2}> + <Grid item md={4}> + <Button + aria-owns={anchorFade ? 'fade-menu' : null} + aria-haspopup="true" + onClick={(e) => this.handleClick(e, 'anchorFade')} + > + Open with fade transition + </Button> + <Menu + id="fade-menu" + anchorEl={anchorFade} + open={Boolean(anchorFade)} + onClose={() => this.handleClose('anchorFade')} + TransitionComponent={Fade} + > + <MenuItem onClick={() => this.handleClose('anchorFade')}>Profile</MenuItem> + <MenuItem onClick={() => this.handleClose('anchorFade')}>My account</MenuItem> + <MenuItem onClick={() => this.handleClose('anchorFade')}>Logout</MenuItem> + </Menu> + </Grid> + <Grid item md={4}> + <Button + aria-owns={anchorGrow ? 'grow-menu' : null} + aria-haspopup="true" + onClick={(e) => this.handleClick(e, 'anchorGrow')} + > + Open with grow transition + </Button> + <Menu + id="grow-menu" + anchorEl={anchorGrow} + open={Boolean(anchorGrow)} + onClose={() => this.handleClose('anchorGrow')} + TransitionComponent={Grow} + > + <MenuItem onClick={() => this.handleClose('anchorGrow')}>Profile</MenuItem> + <MenuItem onClick={() => this.handleClose('anchorGrow')}>My account</MenuItem> + <MenuItem onClick={() => this.handleClose('anchorGrow')}>Logout</MenuItem> + </Menu> + </Grid> + <Grid item md={4}> + <div style={{ position: 'relative' }}> + <Button + aria-owns={anchorZoom ? 'zoom-menu' : null} + aria-haspopup="true" + onClick={(e) => this.handleClick(e, 'anchorZoom')} + > + Open with zoom transition + </Button> + <Menu + id="zoom-menu" + anchorEl={anchorZoom} + open={Boolean(anchorZoom)} + onClose={() => this.handleClose('anchorZoom')} + TransitionComponent={Zoom} + > + <MenuItem onClick={() => this.handleClose('anchorZoom')}>Profile</MenuItem> + <MenuItem onClick={() => this.handleClose('anchorZoom')}>My account</MenuItem> + <MenuItem onClick={() => this.handleClose('anchorZoom')}>Logout</MenuItem> + </Menu> + </div> + </Grid> + </Grid> + ); + } +} + +export default MenuTransition; diff --git a/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/MiniDrawer.js b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/MiniDrawer.js new file mode 100644 index 0000000..4a07e14 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/MiniDrawer.js @@ -0,0 +1,235 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import MenuIcon from '@material-ui/icons/Menu'; +import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; +import ChevronRightIcon from '@material-ui/icons/ChevronRight'; +import { + Drawer, + AppBar, + Toolbar, + List, + Typography, + MenuItem, + Divider, + TextField, + IconButton, +} from '@material-ui/core'; +import { mailFolderListItems, otherMailFolderListItems } from './menuData'; + + +const drawerWidth = 240; + +const styles = theme => ({ + root: { + flexGrow: 1, + }, + appFrame: { + height: 430, + zIndex: 1, + overflow: 'hidden', + position: 'relative', + display: 'flex', + width: '100%', + }, + appBar: { + zIndex: theme.zIndex.drawer + 1, + padding: '0 24px', + transition: theme.transitions.create(['width', 'margin'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + }, + appBarShift: { + marginLeft: drawerWidth, + width: `calc(100% - ${drawerWidth}px)`, + transition: theme.transitions.create(['width', 'margin'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + }, + 'appBarShift-left': { + marginLeft: drawerWidth, + }, + 'appBarShift-right': { + marginRight: drawerWidth, + }, + menuButton: { + marginLeft: 3, + marginRight: 3, + }, + hide: { + display: 'none', + }, + drawerPaper: { + position: 'relative', + whiteSpace: 'nowrap', + width: drawerWidth, + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + }, + drawerPaperClose: { + overflowX: 'hidden', + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + width: theme.spacing(7), + [theme.breakpoints.up('sm')]: { + width: theme.spacing(9), + }, + }, + toolbar: { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + padding: '0 8px', + ...theme.mixins.toolbar, + }, + content: { + flexGrow: 1, + backgroundColor: theme.palette.background.default, + padding: theme.spacing(3), + }, + 'content-left': { + marginLeft: -drawerWidth, + }, + 'content-right': { + marginRight: -drawerWidth, + }, + contentShift: { + transition: theme.transitions.create('margin', { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + }, + 'contentShift-left': { + marginLeft: 0, + }, + 'contentShift-right': { + marginRight: 0, + }, + title: { + flex: 1, + } +}); + +class MiniDrawer extends React.Component { + state = { + open: false, + anchor: 'left', + }; + + handleDrawerOpen = () => { + this.setState({ open: true }); + }; + + handleDrawerClose = () => { + this.setState({ open: false }); + }; + + handleChangeAnchor = event => { + this.setState({ + anchor: event.target.value, + }); + }; + + render() { + const { classes, theme } = this.props; + const { anchor, open } = this.state; + const drawer = ( + <Drawer + variant="permanent" + classes={{ + paper: classNames(classes.drawerPaper, !open && classes.drawerPaperClose), + }} + open={open} + > + <div className={classes.toolbar}> + <IconButton onClick={this.handleDrawerClose}> + {theme.direction === 'rtl' ? <ChevronRightIcon /> : <ChevronLeftIcon />} + </IconButton> + </div> + <Divider /> + <List>{mailFolderListItems}</List> + <Divider /> + <List>{otherMailFolderListItems}</List> + </Drawer> + ); + + const menuBtn = ( + <IconButton + color="inherit" + aria-label="open drawer" + onClick={this.handleDrawerOpen} + className={classNames(classes.menuButton, open && classes.hide)} + > + <MenuIcon /> + </IconButton> + ); + + let before = null; + let after = null; + let beforeBtn = null; + let afterBtn = null; + + if (anchor === 'left') { + before = drawer; + beforeBtn = menuBtn; + } else { + after = drawer; + afterBtn = menuBtn; + } + + return ( + <div className={classes.root}> + <TextField + id="persistent-anchor" + select + label="Anchor" + value={anchor} + onChange={this.handleChangeAnchor} + margin="normal" + > + <MenuItem value="left">left</MenuItem> + <MenuItem value="right">right</MenuItem> + </TextField> + <div className={classes.appFrame}> + <AppBar + position="absolute" + className={classNames(classes.appBar, { + [classes.appBarShift]: open, + [classes[`appBarShift-${anchor}`]]: open, + })} + > + <Toolbar disableGutters> + {beforeBtn} + <Typography className={classes.title} variant="h6" color="inherit" noWrap> + Mini variant drawer + </Typography> + {afterBtn} + </Toolbar> + </AppBar> + {before} + <main className={classes.content}> + <div className={classes.toolbar} /> + <Typography noWrap> + {'You think water moves fast? You should see ice.'} + </Typography> + </main> + {after} + </div> + </div> + ); + } +} + +MiniDrawer.propTypes = { + classes: PropTypes.object.isRequired, + theme: PropTypes.object.isRequired, +}; + +export default withStyles(styles, { withTheme: true })(MiniDrawer); diff --git a/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/PermanentDrawer.js b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/PermanentDrawer.js new file mode 100644 index 0000000..9538873 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/PermanentDrawer.js @@ -0,0 +1,126 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { withStyles } from '@material-ui/core/styles'; +import { Drawer, AppBar, Toolbar, List, MenuItem, TextField, Typography, Divider } from '@material-ui/core'; +import { mailFolderListItems, otherMailFolderListItems } from './menuData'; + + +const drawerWidth = 240; + +const styles = theme => ({ + root: { + flexGrow: 1, + }, + appFrame: { + height: 430, + zIndex: 1, + overflow: 'hidden', + position: 'relative', + display: 'flex', + width: '100%', + }, + appBar: { + width: `calc(100% - ${drawerWidth}px)`, + }, + 'appBar-left': { + marginLeft: drawerWidth, + }, + 'appBar-right': { + marginRight: drawerWidth, + }, + drawerPaper: { + position: 'relative', + width: drawerWidth, + }, + toolbar: theme.mixins.toolbar, + content: { + flexGrow: 1, + backgroundColor: theme.palette.background.default, + padding: theme.spacing(3), + }, +}); + +class PermanentDrawer extends React.Component { + state = { + anchor: 'left', + }; + + handleChange = event => { + this.setState({ + anchor: event.target.value, + }); + }; + + render() { + const { classes } = this.props; + const { anchor } = this.state; + + const drawer = ( + <Drawer + variant="permanent" + classes={{ + paper: classes.drawerPaper, + }} + anchor={anchor} + > + <div className={classes.toolbar} /> + <Divider /> + <List>{mailFolderListItems}</List> + <Divider /> + <List>{otherMailFolderListItems}</List> + </Drawer> + ); + + let before = null; + let after = null; + + if (anchor === 'left') { + before = drawer; + } else { + after = drawer; + } + + return ( + <div className={classes.root}> + <TextField + id="permanent-anchor" + select + label="Anchor" + value={anchor} + onChange={this.handleChange} + margin="normal" + > + <MenuItem value="left">left</MenuItem> + <MenuItem value="right">right</MenuItem> + </TextField> + <div className={classes.appFrame}> + <AppBar + position="absolute" + className={classNames(classes.appBar, classes[`appBar-${anchor}`])} + > + <Toolbar> + <Typography variant="h6" color="inherit" noWrap> + Permanent drawer + </Typography> + </Toolbar> + </AppBar> + {before} + <main className={classes.content}> + <div className={classes.toolbar} /> + <Typography> + {'You think water moves fast? You should see ice.'} + </Typography> + </main> + {after} + </div> + </div> + ); + } +} + +PermanentDrawer.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(PermanentDrawer); diff --git a/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/PersistentDrawer.js b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/PersistentDrawer.js new file mode 100644 index 0000000..cd529d5 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/PersistentDrawer.js @@ -0,0 +1,228 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import MenuIcon from '@material-ui/icons/Menu'; +import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; +import ChevronRightIcon from '@material-ui/icons/ChevronRight'; +import { + Drawer, + AppBar, + Toolbar, + List, + MenuItem, + Typography, + TextField, + Divider, + IconButton, +} from '@material-ui/core'; +import { mailFolderListItems, otherMailFolderListItems } from './menuData'; + + +const drawerWidth = 240; + +const styles = theme => ({ + root: { + flexGrow: 1, + }, + appFrame: { + height: 430, + zIndex: 1, + overflow: 'hidden', + position: 'relative', + display: 'flex', + width: '100%', + }, + appBar: { + position: 'absolute', + padding: '0 24px', + transition: theme.transitions.create(['margin', 'width'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + }, + appBarShift: { + width: `calc(100% - ${drawerWidth}px)`, + transition: theme.transitions.create(['margin', 'width'], { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + }, + 'appBarShift-left': { + marginLeft: drawerWidth, + }, + 'appBarShift-right': { + marginRight: drawerWidth, + }, + menuButton: { + marginLeft: 3, + marginRight: 3, + }, + hide: { + display: 'none', + }, + drawerPaper: { + position: 'relative', + width: drawerWidth, + }, + drawerHeader: { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + padding: '0 8px', + ...theme.mixins.toolbar, + }, + content: { + flexGrow: 1, + backgroundColor: theme.palette.background.default, + padding: theme.spacing(3), + transition: theme.transitions.create('margin', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + }, + 'content-left': { + marginLeft: -drawerWidth, + }, + 'content-right': { + marginRight: -drawerWidth, + }, + contentShift: { + transition: theme.transitions.create('margin', { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + }, + 'contentShift-left': { + marginLeft: 0, + }, + 'contentShift-right': { + marginRight: 0, + }, + title: { + flex: 1, + } +}); + +class PersistentDrawer extends React.Component { + state = { + open: false, + anchor: 'left', + }; + + handleDrawerOpen = () => { + this.setState({ open: true }); + }; + + handleDrawerClose = () => { + this.setState({ open: false }); + }; + + handleChangeAnchor = event => { + this.setState({ + anchor: event.target.value, + }); + }; + + render() { + const { classes, theme } = this.props; + const { anchor, open } = this.state; + + const drawer = ( + <Drawer + variant="persistent" + anchor={anchor} + open={open} + classes={{ + paper: classes.drawerPaper, + }} + > + <div className={classes.drawerHeader}> + <IconButton onClick={this.handleDrawerClose}> + {theme.direction === 'rtl' ? <ChevronRightIcon /> : <ChevronLeftIcon />} + </IconButton> + </div> + <Divider /> + <List>{mailFolderListItems}</List> + <Divider /> + <List>{otherMailFolderListItems}</List> + </Drawer> + ); + + const menuBtn = ( + <IconButton + color="inherit" + aria-label="open drawer" + onClick={this.handleDrawerOpen} + className={classNames(classes.menuButton, open && classes.hide)} + > + <MenuIcon /> + </IconButton> + ); + + let before = null; + let after = null; + let beforeBtn = null; + let afterBtn = null; + + if (anchor === 'left') { + before = drawer; + beforeBtn = menuBtn; + } else { + after = drawer; + afterBtn = menuBtn; + } + + return ( + <div className={classes.root}> + <TextField + id="persistent-anchor" + select + label="Anchor" + value={anchor} + onChange={this.handleChangeAnchor} + margin="normal" + > + <MenuItem value="left">left</MenuItem> + <MenuItem value="right">right</MenuItem> + </TextField> + <div className={classes.appFrame}> + <AppBar + className={classNames(classes.appBar, { + [classes.appBarShift]: open, + [classes[`appBarShift-${anchor}`]]: open, + })} + > + <Toolbar disableGutters> + {beforeBtn} + <Typography className={classes.title} variant="h6" color="inherit" noWrap> + Persistent drawer + </Typography> + {afterBtn} + </Toolbar> + </AppBar> + {before} + <main + className={classNames(classes.content, classes[`content-${anchor}`], { + [classes.contentShift]: open, + [classes[`contentShift-${anchor}`]]: open, + })} + > + <div className={classes.drawerHeader} /> + <Typography> + {'You think water moves fast? You should see ice.'} + </Typography> + </main> + {after} + </div> + </div> + ); + } +} + +PersistentDrawer.propTypes = { + classes: PropTypes.object.isRequired, + theme: PropTypes.object.isRequired, +}; + +export default withStyles(styles, { withTheme: true })(PersistentDrawer); diff --git a/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/StyledMenu.js b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/StyledMenu.js new file mode 100644 index 0000000..ed6ac4a --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/StyledMenu.js @@ -0,0 +1,60 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import InboxIcon from '@material-ui/icons/MoveToInbox'; +import DraftsIcon from '@material-ui/icons/Drafts'; +import SendIcon from '@material-ui/icons/Send'; + +import { MenuList, MenuItem, Paper, ListItemIcon, ListItemText } from '@material-ui/core'; + +const styles = theme => ({ + menu: { + maxWidth: 400, + margin: '20 auto' + }, + menuItem: { + '&:focus': { + backgroundColor: theme.palette.primary.main, + '& $primary, & $icon': { + color: theme.palette.common.white, + }, + }, + }, + primary: {}, + icon: {}, +}); + +function StyledMenu(props) { + const { classes } = props; + + return ( + <Paper className={classes.menu}> + <MenuList> + <MenuItem className={classes.menuItem}> + <ListItemIcon className={classes.icon}> + <SendIcon /> + </ListItemIcon> + <ListItemText classes={{ primary: classes.primary }} variant="inset" primary="Sent mail" /> + </MenuItem> + <MenuItem className={classes.menuItem}> + <ListItemIcon className={classes.icon}> + <DraftsIcon /> + </ListItemIcon> + <ListItemText classes={{ primary: classes.primary }} variant="inset" primary="Drafts" /> + </MenuItem> + <MenuItem className={classes.menuItem}> + <ListItemIcon className={classes.icon}> + <InboxIcon /> + </ListItemIcon> + <ListItemText classes={{ primary: classes.primary }} variant="inset" primary="Inbox" /> + </MenuItem> + </MenuList> + </Paper> + ); +} + +StyledMenu.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(StyledMenu); diff --git a/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/SwipeDrawer.js b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/SwipeDrawer.js new file mode 100644 index 0000000..a91feb5 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/SwipeDrawer.js @@ -0,0 +1,124 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { SwipeableDrawer, Button, List, Divider, Grid } from '@material-ui/core'; +import { mailFolderListItems, otherMailFolderListItems } from './menuData'; + + +const styles = { + list: { + width: 250, + }, + fullList: { + width: 'auto', + }, +}; + +class SwipeDrawer extends React.Component { + state = { + top: false, + left: false, + bottom: false, + right: false, + }; + + toggleDrawer = (side, open) => () => { + this.setState({ + [side]: open, + }); + }; + + render() { + const { classes } = this.props; + + const sideList = ( + <div className={classes.list}> + <List>{mailFolderListItems}</List> + <Divider /> + <List>{otherMailFolderListItems}</List> + </div> + ); + + const fullList = ( + <div className={classes.fullList}> + <List>{mailFolderListItems}</List> + <Divider /> + <List>{otherMailFolderListItems}</List> + </div> + ); + + return ( + <Grid container justify="center" direction="row"> + <Button onClick={this.toggleDrawer('left', true)}>Open Left</Button> + <Button onClick={this.toggleDrawer('right', true)}>Open Right</Button> + <Button onClick={this.toggleDrawer('top', true)}>Open Top</Button> + <Button onClick={this.toggleDrawer('bottom', true)}>Open Bottom</Button> + <SwipeableDrawer + open={this.state.left} + onClose={this.toggleDrawer('left', false)} + onOpen={this.toggleDrawer('left', true)} + > + <div + tabIndex={0} + role="button" + onClick={this.toggleDrawer('left', false)} + onKeyDown={this.toggleDrawer('left', false)} + > + {sideList} + </div> + </SwipeableDrawer> + <SwipeableDrawer + anchor="top" + open={this.state.top} + onClose={this.toggleDrawer('top', false)} + onOpen={this.toggleDrawer('top', true)} + > + <div + tabIndex={0} + role="button" + onClick={this.toggleDrawer('top', false)} + onKeyDown={this.toggleDrawer('top', false)} + > + {fullList} + </div> + </SwipeableDrawer> + <SwipeableDrawer + anchor="bottom" + open={this.state.bottom} + onClose={this.toggleDrawer('bottom', false)} + onOpen={this.toggleDrawer('bottom', true)} + > + <div + tabIndex={0} + role="button" + onClick={this.toggleDrawer('bottom', false)} + onKeyDown={this.toggleDrawer('bottom', false)} + > + {fullList} + </div> + </SwipeableDrawer> + <SwipeableDrawer + anchor="right" + open={this.state.right} + onClose={this.toggleDrawer('right', false)} + onOpen={this.toggleDrawer('right', true)} + > + <div + tabIndex={0} + role="button" + onClick={this.toggleDrawer('right', false)} + onKeyDown={this.toggleDrawer('right', false)} + > + {sideList} + </div> + </SwipeableDrawer> + </Grid> + ); + } +} + +SwipeDrawer.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(SwipeDrawer); diff --git a/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/TemporaryDrawer.js b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/TemporaryDrawer.js new file mode 100644 index 0000000..6af2a24 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/TemporaryDrawer.js @@ -0,0 +1,109 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Drawer, Button, List, Divider, Grid } from '@material-ui/core'; +import { mailFolderListItems, otherMailFolderListItems } from './menuData'; + + +const styles = { + list: { + width: 250, + }, + fullList: { + width: 'auto', + }, +}; + +class TemporaryDrawer extends React.Component { + state = { + top: false, + left: false, + bottom: false, + right: false, + }; + + toggleDrawer = (side, open) => () => { + this.setState({ + [side]: open, + }); + }; + + render() { + const { classes } = this.props; + + const sideList = ( + <div className={classes.list}> + <List>{mailFolderListItems}</List> + <Divider /> + <List>{otherMailFolderListItems}</List> + </div> + ); + + const fullList = ( + <div className={classes.fullList}> + <List>{mailFolderListItems}</List> + <Divider /> + <List>{otherMailFolderListItems}</List> + </div> + ); + + return ( + <Grid container justify="center" direction="row"> + <Button onClick={this.toggleDrawer('left', true)}>Open Left</Button> + <Button onClick={this.toggleDrawer('right', true)}>Open Right</Button> + <Button onClick={this.toggleDrawer('top', true)}>Open Top</Button> + <Button onClick={this.toggleDrawer('bottom', true)}>Open Bottom</Button> + <Drawer open={this.state.left} onClose={this.toggleDrawer('left', false)}> + <div + tabIndex={0} + role="button" + onClick={this.toggleDrawer('left', false)} + onKeyDown={this.toggleDrawer('left', false)} + > + {sideList} + </div> + </Drawer> + <Drawer anchor="top" open={this.state.top} onClose={this.toggleDrawer('top', false)}> + <div + tabIndex={0} + role="button" + onClick={this.toggleDrawer('top', false)} + onKeyDown={this.toggleDrawer('top', false)} + > + {fullList} + </div> + </Drawer> + <Drawer + anchor="bottom" + open={this.state.bottom} + onClose={this.toggleDrawer('bottom', false)} + > + <div + tabIndex={0} + role="button" + onClick={this.toggleDrawer('bottom', false)} + onKeyDown={this.toggleDrawer('bottom', false)} + > + {fullList} + </div> + </Drawer> + <Drawer anchor="right" open={this.state.right} onClose={this.toggleDrawer('right', false)}> + <div + tabIndex={0} + role="button" + onClick={this.toggleDrawer('right', false)} + onKeyDown={this.toggleDrawer('right', false)} + > + {sideList} + </div> + </Drawer> + </Grid> + ); + } +} + +TemporaryDrawer.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(TemporaryDrawer); diff --git a/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/menuData.js b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/menuData.js new file mode 100644 index 0000000..cd2d653 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/DrawerMenu/menuData.js @@ -0,0 +1,64 @@ +// This file is shared across the demos. + +import React from 'react'; +import InboxIcon from '@material-ui/icons/MoveToInbox'; +import DraftsIcon from '@material-ui/icons/Drafts'; +import StarIcon from '@material-ui/icons/Star'; +import SendIcon from '@material-ui/icons/Send'; +import MailIcon from '@material-ui/icons/Mail'; +import DeleteIcon from '@material-ui/icons/Delete'; +import ReportIcon from '@material-ui/icons/Report'; + +import { ListItem, ListItemIcon, ListItemText } from '@material-ui/core'; + +export const mailFolderListItems = ( + <div> + <ListItem button> + <ListItemIcon> + <InboxIcon /> + </ListItemIcon> + <ListItemText primary="Inbox" /> + </ListItem> + <ListItem button> + <ListItemIcon> + <StarIcon /> + </ListItemIcon> + <ListItemText primary="Starred" /> + </ListItem> + <ListItem button> + <ListItemIcon> + <SendIcon /> + </ListItemIcon> + <ListItemText primary="Send mail" /> + </ListItem> + <ListItem button> + <ListItemIcon> + <DraftsIcon /> + </ListItemIcon> + <ListItemText primary="Drafts" /> + </ListItem> + </div> +); + +export const otherMailFolderListItems = ( + <div> + <ListItem button> + <ListItemIcon> + <MailIcon /> + </ListItemIcon> + <ListItemText primary="All mail" /> + </ListItem> + <ListItem button> + <ListItemIcon> + <DeleteIcon /> + </ListItemIcon> + <ListItemText primary="Trash" /> + </ListItem> + <ListItem button> + <ListItemIcon> + <ReportIcon /> + </ListItemIcon> + <ListItemText primary="Spam" /> + </ListItem> + </div> +); diff --git a/front/odiparpack/app/containers/UiElements/demos/ImageGrid/AdvancedGridList.js b/front/odiparpack/app/containers/UiElements/demos/ImageGrid/AdvancedGridList.js new file mode 100644 index 0000000..5d11f0d --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/ImageGrid/AdvancedGridList.js @@ -0,0 +1,85 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import StarBorderIcon from '@material-ui/icons/StarBorder'; +import imgData from 'ba-api/imgData'; + +import { GridList, GridListTile, GridListTileBar, IconButton } from '@material-ui/core'; + +const styles = theme => ({ + root: { + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'space-around', + overflow: 'hidden', + backgroundColor: theme.palette.background.paper, + }, + gridList: { + width: 500, + height: 450, + // Promote the list into his own layer on Chrome. This cost memory but helps keeping high FPS. + transform: 'translateZ(0)', + }, + titleBar: { + background: + 'linear-gradient(to bottom, rgba(0,0,0,0.7) 0%, ' + + 'rgba(0,0,0,0.3) 70%, rgba(0,0,0,0) 100%)', + }, + icon: { + color: 'white', + }, + img: { + maxWidth: 'none' + } +}); + +/** + * The example data is structured as follows: + * + * import image from 'path/to/image.jpg'; + * [etc...] + * + * const tileData = [ + * { + * img: image, + * title: 'Image', + * author: 'author', + * featured: true, + * }, + * { + * [etc...] + * }, + * ]; + */ +function AdvancedGridList(props) { + const { classes } = props; + + return ( + <div className={classes.root}> + <GridList cellHeight={200} spacing={1} className={classes.gridList}> + {imgData.map((tile, index) => ( + <GridListTile key={index.toString()} cols={tile.featured ? 2 : 1} rows={tile.featured ? 2 : 1}> + <img src={tile.img} className={classes.img} alt={tile.title} /> + <GridListTileBar + title={tile.title} + titlePosition="top" + actionIcon={( + <IconButton className={classes.icon}> + <StarBorderIcon /> + </IconButton> + )} + actionPosition="left" + className={classes.titleBar} + /> + </GridListTile> + ))} + </GridList> + </div> + ); +} + +AdvancedGridList.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(AdvancedGridList); diff --git a/front/odiparpack/app/containers/UiElements/demos/ImageGrid/ImageGridList.js b/front/odiparpack/app/containers/UiElements/demos/ImageGrid/ImageGridList.js new file mode 100644 index 0000000..ce785ac --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/ImageGrid/ImageGridList.js @@ -0,0 +1,66 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import imgData from 'ba-api/imgData'; + +import { GridList, GridListTile } from '@material-ui/core'; + +const styles = theme => ({ + root: { + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'space-around', + overflow: 'hidden', + backgroundColor: theme.palette.background.paper, + }, + gridList: { + width: 500, + height: 450, + }, + subheader: { + width: '100%', + }, + img: { + maxWidth: 'none' + } +}); + +/** + * The example data is structured as follows: + * + * import image from 'path/to/image.jpg'; + * [etc...] + * + * const tileData = [ + * { + * img: image, + * title: 'Image', + * author: 'author', + * cols: 2, + * }, + * { + * [etc...] + * }, + * ]; + */ +function ImageGridList(props) { + const { classes } = props; + + return ( + <div className={classes.root}> + <GridList cellHeight={160} className={classes.gridList} cols={3}> + {imgData.map((tile, index) => ( + <GridListTile key={index.toString()} cols={tile.cols || 1}> + <img src={tile.img} className={classes.img} alt={tile.title} /> + </GridListTile> + ))} + </GridList> + </div> + ); +} + +ImageGridList.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ImageGridList); diff --git a/front/odiparpack/app/containers/UiElements/demos/ImageGrid/SingleLineGridList.js b/front/odiparpack/app/containers/UiElements/demos/ImageGrid/SingleLineGridList.js new file mode 100644 index 0000000..ac30e6f --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/ImageGrid/SingleLineGridList.js @@ -0,0 +1,83 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import StarBorderIcon from '@material-ui/icons/StarBorder'; +import imgData from 'ba-api/imgData'; + +import { GridList, GridListTile, GridListTileBar, IconButton } from '@material-ui/core'; + +const styles = theme => ({ + root: { + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'space-around', + overflow: 'hidden', + backgroundColor: theme.palette.background.paper, + }, + gridList: { + flexWrap: 'nowrap', + // Promote the list into his own layer on Chrome. This cost memory but helps keeping high FPS. + transform: 'translateZ(0)', + }, + title: { + color: theme.palette.primary.light, + }, + titleBar: { + background: + 'linear-gradient(to top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.3) 70%, rgba(0,0,0,0) 100%)', + }, + img: { + maxWidth: 'none' + } +}); + +/** + * The example data is structured as follows: + * + * import image from 'path/to/image.jpg'; + * [etc...] + * + * const tileData = [ + * { + * img: image, + * title: 'Image', + * author: 'author', + * }, + * { + * [etc...] + * }, + * ]; + */ +function SingleLineGridList(props) { + const { classes } = props; + + return ( + <div className={classes.root}> + <GridList className={classes.gridList} cols={2.5}> + {imgData.map((tile, index) => ( + <GridListTile key={index.toString()}> + <img src={tile.img} alt={tile.title} className={classes.img} /> + <GridListTileBar + title={tile.title} + classes={{ + root: classes.titleBar, + title: classes.title, + }} + actionIcon={( + <IconButton> + <StarBorderIcon className={classes.title} /> + </IconButton> + )} + /> + </GridListTile> + ))} + </GridList> + </div> + ); +} + +SingleLineGridList.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(SingleLineGridList); diff --git a/front/odiparpack/app/containers/UiElements/demos/ImageGrid/TitlebarGridList.js b/front/odiparpack/app/containers/UiElements/demos/ImageGrid/TitlebarGridList.js new file mode 100644 index 0000000..41f8d1a --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/ImageGrid/TitlebarGridList.js @@ -0,0 +1,89 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import InfoIcon from '@material-ui/icons/Info'; +import imgData from 'ba-api/imgData'; + +import { + GridList, + GridListTile, + GridListTileBar, + ListSubheader as Subheader, + IconButton, +} from '@material-ui/core'; + +const styles = theme => ({ + root: { + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'space-around', + overflow: 'hidden', + backgroundColor: theme.palette.background.paper, + }, + gridList: { + width: 500, + height: 450, + }, + icon: { + color: 'rgba(255, 255, 255, 0.54)', + }, + img: { + maxWidth: 'none' + } +}); + +/** + * The example data is structured as follows: + * + * import image from 'path/to/image.jpg'; + * [etc...] + * + * const tileData = [ + * { + * img: image, + * title: 'Image', + * author: 'author', + * }, + * { + * [etc...] + * }, + * ]; + */ +function TitlebarGridList(props) { + const { classes } = props; + + return ( + <div className={classes.root}> + <GridList cellHeight={180} className={classes.gridList}> + <GridListTile key="Subheader" cols={2} style={{ height: 'auto' }}> + <Subheader component="div">December</Subheader> + </GridListTile> + {imgData.map((tile, index) => ( + <GridListTile key={index.toString()}> + <img src={tile.img} className={classes.img} alt={tile.title} /> + <GridListTileBar + title={tile.title} + subtitle={( + <span> +by: + {tile.author} + </span> + )} + actionIcon={( + <IconButton className={classes.icon}> + <InfoIcon /> + </IconButton> + )} + /> + </GridListTile> + ))} + </GridList> + </div> + ); +} + +TitlebarGridList.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(TitlebarGridList); diff --git a/front/odiparpack/app/containers/UiElements/demos/List/ListBasic.js b/front/odiparpack/app/containers/UiElements/demos/List/ListBasic.js new file mode 100644 index 0000000..ba4f13a --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/List/ListBasic.js @@ -0,0 +1,116 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +// import classNames from 'classnames'; +import { withStyles } from '@material-ui/core/styles'; +import ImageIcon from '@material-ui/icons/Image'; +import WorkIcon from '@material-ui/icons/Work'; +import BeachAccessIcon from '@material-ui/icons/BeachAccess'; + +import { red, green, amber } from '@material-ui/core/colors'; + +import { + Typography, Grid, List, + ListItem, ListItemText, ListItemAvatar, + Avatar, Divider +} from '@material-ui/core'; + +const styles = theme => ({ + root: { + maxWidth: '360px', + backgroundColor: theme.palette.background.paper, + margin: 10 + }, + avatarRed: { + backgroundColor: red[500], + }, + avatarGreen: { + backgroundColor: green[500], + }, + avatarAmber: { + backgroundColor: amber[500], + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + } +}); + +class ListBasic extends PureComponent { + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid item md={6} xs={12}> + <Typography variant="button" className={classes.divider}>Simple List Divider</Typography> + <div className={classes.root}> + <List component="nav"> + <ListItem button> + <ListItemText primary="Inbox" /> + </ListItem> + <Divider /> + <ListItem button divider> + <ListItemText primary="Drafts" /> + </ListItem> + <ListItem button> + <ListItemText primary="Trash" /> + </ListItem> + <Divider light /> + <ListItem button> + <ListItemText primary="Spam" /> + </ListItem> + </List> + </div> + </Grid> + <Grid item md={6} xs={12}> + <Typography variant="button" className={classes.divider}>Inset Divider</Typography> + <div className={classes.root}> + <List> + <ListItem> + <ListItemAvatar> + <Avatar className={classes.avatarRed}> + <ImageIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Photos" secondary="Jan 9, 2014" /> + </ListItem> + <li> + <Divider variant="inset" /> + </li> + <ListItem> + <ListItemAvatar> + <Avatar className={classes.avatarGreen}> + <WorkIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Work" secondary="Jan 7, 2014" /> + </ListItem> + <Divider variant="inset" component="li" /> + <ListItem> + <ListItemAvatar> + <Avatar className={classes.avatarAmber}> + <BeachAccessIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Vacation" secondary="July 20, 2014" /> + </ListItem> + </List> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +ListBasic.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ListBasic); diff --git a/front/odiparpack/app/containers/UiElements/demos/List/ListControl.js b/front/odiparpack/app/containers/UiElements/demos/List/ListControl.js new file mode 100644 index 0000000..2743d4b --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/List/ListControl.js @@ -0,0 +1,159 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import CommentIcon from '@material-ui/icons/Comment'; +import WifiIcon from '@material-ui/icons/Wifi'; +import BluetoothIcon from '@material-ui/icons/Bluetooth'; + +import { + List, + ListItem, + ListItemIcon, + ListItemSecondaryAction, + ListItemText, + ListSubheader, + ListItemAvatar, + Checkbox, + Switch, + IconButton, + Grid, + Typography, + Avatar, +} from '@material-ui/core'; + +const styles = theme => ({ + root: { + maxWidth: 360, + backgroundColor: theme.palette.background.paper, + margin: 10 + }, +}); + +class ListControl extends React.Component { + state = { + checked: [0], + checked2: [1], + checked3: ['wifi'], + }; + + handleToggle = value => () => { + const { checked } = this.state; + const currentIndex = checked.indexOf(value); + const newChecked = [...checked]; + + if (currentIndex === -1) { + newChecked.push(value); + } else { + newChecked.splice(currentIndex, 1); + } + + this.setState({ + checked: newChecked, + checked2: newChecked, + checked3: newChecked, + }); + }; + + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid item md={4} xs={12}> + <Typography variant="button" className={classes.divider}>Checkbox</Typography> + <div className={classes.root}> + <List> + {[0, 1, 2, 3].map(value => ( + <ListItem + key={value} + role={undefined} + dense + button + onClick={this.handleToggle(value)} + className={classes.listItem} + > + <Checkbox + checked={this.state.checked.indexOf(value) !== -1} + tabIndex={-1} + disableRipple + /> + <ListItemText primary={`Line item ${value + 1}`} /> + <ListItemSecondaryAction> + <IconButton aria-label="Comments"> + <CommentIcon /> + </IconButton> + </ListItemSecondaryAction> + </ListItem> + ))} + </List> + </div> + </Grid> + <Grid item md={4} xs={12}> + <Typography variant="button" className={classes.divider}>Checkbox</Typography> + <div className={classes.root}> + <List> + {[0, 1, 2, 3].map(value => ( + <ListItem key={value} dense button className={classes.listItem}> + <ListItemAvatar> + <Avatar alt="Remy Sharp" src="/images/pp_boy.svg" /> + </ListItemAvatar> + <ListItemText primary={`Line item ${value + 1}`} /> + <ListItemSecondaryAction> + <Checkbox + onChange={this.handleToggle(value)} + checked={this.state.checked2.indexOf(value) !== -1} + /> + </ListItemSecondaryAction> + </ListItem> + ))} + </List> + </div> + </Grid> + <Grid item md={4} xs={12}> + <Typography variant="button" className={classes.divider}>Switch</Typography> + <div className={classes.root}> + <List subheader={<ListSubheader>Settings</ListSubheader>}> + <ListItem> + <ListItemIcon> + <WifiIcon /> + </ListItemIcon> + <ListItemText primary="Wi-Fi" /> + <ListItemSecondaryAction> + <Switch + onChange={this.handleToggle('wifi')} + checked={this.state.checked3.indexOf('wifi') !== -1} + /> + </ListItemSecondaryAction> + </ListItem> + <ListItem> + <ListItemIcon> + <BluetoothIcon /> + </ListItemIcon> + <ListItemText primary="Bluetooth" /> + <ListItemSecondaryAction> + <Switch + onChange={this.handleToggle('bluetooth')} + checked={this.state.checked3.indexOf('bluetooth') !== -1} + /> + </ListItemSecondaryAction> + </ListItem> + </List> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +ListControl.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ListControl); diff --git a/front/odiparpack/app/containers/UiElements/demos/List/ListInteractive.js b/front/odiparpack/app/containers/UiElements/demos/List/ListInteractive.js new file mode 100644 index 0000000..f8d3fa8 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/List/ListInteractive.js @@ -0,0 +1,183 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import FolderIcon from '@material-ui/icons/Folder'; +import DeleteIcon from '@material-ui/icons/Delete'; +import { cyan } from '@material-ui/core/colors'; + +import { + List, + ListItem, + ListItemAvatar, + ListItemIcon, + ListItemSecondaryAction, + ListItemText, + Avatar, + IconButton, + FormGroup, + FormControlLabel, + Checkbox, + Grid, + Typography, +} from '@material-ui/core'; + +const styles = theme => ({ + root: { + flexGrow: 1, + maxWidth: 752, + }, + demo: { + backgroundColor: theme.palette.background.paper, + }, + title: { + margin: `${theme.spacing(4)}px 0 ${theme.spacing(2)}px`, + }, + iconCyan: { + color: cyan[300] + }, + avatarCyan: { + background: cyan[300] + } +}); + +function generate(element) { + return [0, 1, 2].map(value => React.cloneElement(element, { + key: value, + }), + ); +} + +class ListInteractive extends React.Component { + state = { + dense: false, + secondary: false, + }; + + render() { + const { classes } = this.props; + const { dense, secondary } = this.state; + + return ( + <div className={classes.root}> + <FormGroup row> + <FormControlLabel + control={( + <Checkbox + checked={dense} + onChange={(event, checked) => this.setState({ dense: checked })} + value="dense" + /> + )} + label="Enable dense" + /> + <FormControlLabel + control={( + <Checkbox + checked={secondary} + onChange={(event, checked) => this.setState({ secondary: checked })} + value="secondary" + /> + )} + label="Enable secondary text" + /> + </FormGroup> + <Grid container spacing={2}> + <Grid item xs={12} md={6}> + <Typography variant="button" className={classes.title}> + Text only + </Typography> + <div className={classes.demo}> + <List dense={dense}> + {generate( + <ListItem> + <ListItemText + primary="Single-line item" + secondary={secondary ? 'Secondary text' : null} + /> + </ListItem>, + )} + </List> + </div> + </Grid> + <Grid item xs={12} md={6}> + <Typography variant="button" className={classes.title}> + Icon with text + </Typography> + <div className={classes.demo}> + <List dense={dense}> + {generate( + <ListItem> + <ListItemIcon> + <FolderIcon className={classes.iconCyan} /> + </ListItemIcon> + <ListItemText + primary="Single-line item" + secondary={secondary ? 'Secondary text' : null} + /> + </ListItem>, + )} + </List> + </div> + </Grid> + </Grid> + <Grid container spacing={2}> + <Grid item xs={12} md={6}> + <Typography variant="button" className={classes.title}> + Avatar with text + </Typography> + <div className={classes.demo}> + <List dense={dense}> + {generate( + <ListItem> + <ListItemAvatar> + <Avatar className={classes.avatarCyan}> + <FolderIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText + primary="Single-line item" + secondary={secondary ? 'Secondary text' : null} + /> + </ListItem>, + )} + </List> + </div> + </Grid> + <Grid item xs={12} md={6}> + <Typography variant="button" className={classes.title}> + Avatar with text and icon + </Typography> + <div className={classes.demo}> + <List dense={dense}> + {generate( + <ListItem> + <ListItemAvatar> + <Avatar className={classes.avatarCyan}> + <FolderIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText + primary="Single-line item" + secondary={secondary ? 'Secondary text' : null} + /> + <ListItemSecondaryAction> + <IconButton aria-label="Delete"> + <DeleteIcon /> + </IconButton> + </ListItemSecondaryAction> + </ListItem>, + )} + </List> + </div> + </Grid> + </Grid> + </div> + ); + } +} + +ListInteractive.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ListInteractive); diff --git a/front/odiparpack/app/containers/UiElements/demos/List/ListMenu.js b/front/odiparpack/app/containers/UiElements/demos/List/ListMenu.js new file mode 100644 index 0000000..f796f76 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/List/ListMenu.js @@ -0,0 +1,204 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import StarIcon from '@material-ui/icons/Star'; +import ImageIcon from '@material-ui/icons/Image'; +import WorkIcon from '@material-ui/icons/Work'; +import BeachAccessIcon from '@material-ui/icons/BeachAccess'; +import InboxIcon from '@material-ui/icons/Inbox'; +import DraftsIcon from '@material-ui/icons/Drafts'; +import SendIcon from '@material-ui/icons/Send'; +import ExpandLess from '@material-ui/icons/ExpandLess'; +import ExpandMore from '@material-ui/icons/ExpandMore'; +import StarBorder from '@material-ui/icons/StarBorder'; +import { red, green, amber, lightBlue, pink, teal } from '@material-ui/core/colors'; + +import { + Typography, + Grid, + List, + ListItem, + ListItemIcon, + ListItemText, + ListSubheader, + ListItemAvatar, + Divider, + Avatar, + Collapse, +} from '@material-ui/core'; + +const styles = theme => ({ + root: { + maxWidth: '360px', + backgroundColor: theme.palette.background.paper, + margin: 10 + }, + nested: { + paddingLeft: theme.spacing(4), + }, + avatarRed: { + backgroundColor: red[500], + }, + avatarGreen: { + backgroundColor: green[500], + }, + avatarAmber: { + backgroundColor: amber[500], + }, + iconBlue: { + color: lightBlue[500] + }, + iconPink: { + color: pink[500] + }, + iconAmber: { + color: amber[500] + }, + iconTeal: { + color: teal[500] + }, +}); + +class ListMenu extends PureComponent { + state = { open: true }; + + handleClick = () => { + this.setState({ open: !this.state.open }); + }; + + render() { + const { classes } = this.props; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid item md={6} xs={12}> + <Typography variant="button" className={classes.divider}>Menu List</Typography> + <div className={classes.root}> + <List component="nav"> + <ListItem button> + <ListItemIcon> + <InboxIcon className={classes.iconPink} /> + </ListItemIcon> + <ListItemText primary="Inbox" /> + </ListItem> + <ListItem button> + <ListItemIcon> + <DraftsIcon className={classes.iconBlue} /> + </ListItemIcon> + <ListItemText primary="Drafts" /> + </ListItem> + </List> + <Divider /> + <List component="nav"> + <ListItem button> + <ListItemText primary="Trash" /> + </ListItem> + <ListItem button component="a" href="#simple-list"> + <ListItemText primary="Spam" /> + </ListItem> + </List> + </div> + </Grid> + <Grid item md={6} xs={12}> + <Typography variant="button" className={classes.divider}>Folder List</Typography> + <div className={classes.root}> + <List> + <ListItem> + <ListItemAvatar> + <Avatar className={classes.avatarRed}> + <ImageIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Photos" secondary="Jan 9, 2014" /> + </ListItem> + <ListItem> + <ListItemAvatar> + <Avatar className={classes.avatarGreen}> + <WorkIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Work" secondary="Jan 7, 2014" /> + </ListItem> + <ListItem> + <ListItemAvatar> + <Avatar className={classes.avatarAmber}> + <BeachAccessIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary="Vacation" secondary="July 20, 2014" /> + </ListItem> + </List> + </div> + </Grid> + <Grid item md={6} xs={12}> + <Typography variant="button" className={classes.divider}>Inset List</Typography> + <div className={classes.root}> + <List component="nav"> + <ListItem button> + <ListItemIcon> + <StarIcon className={classes.iconAmber} /> + </ListItemIcon> + <ListItemText variant="inset" primary="Chelsea Otakan" /> + </ListItem> + <ListItem button> + <ListItemText variant="inset" primary="Eric Hoffman" /> + </ListItem> + </List> + </div> + </Grid> + <Grid item md={6} xs={12}> + <Typography variant="button" className={classes.divider}>Nested List</Typography> + <div className={classes.root}> + <List + component="nav" + subheader={<ListSubheader component="div">Nested List Items</ListSubheader>} + > + <ListItem button> + <ListItemIcon> + <SendIcon className={classes.iconTeal} /> + </ListItemIcon> + <ListItemText variant="inset" primary="Sent mail" /> + </ListItem> + <ListItem button> + <ListItemIcon> + <DraftsIcon className={classes.iconBlue} /> + </ListItemIcon> + <ListItemText variant="inset" primary="Drafts" /> + </ListItem> + <ListItem button onClick={this.handleClick}> + <ListItemIcon> + <InboxIcon className={classes.iconPink} /> + </ListItemIcon> + <ListItemText variant="inset" primary="Inbox" /> + {this.state.open ? <ExpandLess /> : <ExpandMore />} + </ListItem> + <Collapse in={this.state.open} timeout="auto" unmountOnExit> + <List component="div" disablePadding> + <ListItem button className={classes.nested}> + <ListItemIcon> + <StarBorder className={classes.iconAmber} /> + </ListItemIcon> + <ListItemText variant="inset" primary="Starred" /> + </ListItem> + </List> + </Collapse> + </List> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +ListMenu.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ListMenu); diff --git a/front/odiparpack/app/containers/UiElements/demos/List/PinnedList.js b/front/odiparpack/app/containers/UiElements/demos/List/PinnedList.js new file mode 100644 index 0000000..e770fd0 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/List/PinnedList.js @@ -0,0 +1,62 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Grid, ListSubheader, List, ListItem, ListItemText } from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '100%', + maxWidth: 360, + backgroundColor: theme.palette.background.paper, + position: 'relative', + overflow: 'auto', + maxHeight: 300, + }, + listSection: { + backgroundColor: 'inherit', + }, + ul: { + backgroundColor: 'inherit', + padding: 0, + }, + head: { + backgroundColor: theme.palette.secondary.light, + lineHeight: '30px', + height: 30, + textTransform: 'uppercase' + } +}); + +function PinnedList(props) { + const { classes } = props; + + return ( + <Grid + container + alignItems="center" + justify="center" + direction="row" + > + <List className={classes.root} subheader={<li />}> + {[0, 1, 2, 3, 4].map(sectionId => ( + <li key={`section-${sectionId}`} className={classes.listSection}> + <ul className={classes.ul}> + <ListSubheader className={classes.head}>{`I'm sticky ${sectionId}`}</ListSubheader> + {[0, 1, 2].map(item => ( + <ListItem key={`item-${sectionId}-${item}`}> + <ListItemText primary={`Item ${item}`} /> + </ListItem> + ))} + </ul> + </li> + ))} + </List> + </Grid> + ); +} + +PinnedList.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(PinnedList); diff --git a/front/odiparpack/app/containers/UiElements/demos/Notification/MobileNotif.js b/front/odiparpack/app/containers/UiElements/demos/Notification/MobileNotif.js new file mode 100644 index 0000000..60cdbbd --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Notification/MobileNotif.js @@ -0,0 +1,116 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { withStyles } from '@material-ui/core/styles'; +import MenuIcon from '@material-ui/icons/Menu'; +import AddIcon from '@material-ui/icons/Add'; +import { AppBar, Toolbar, IconButton, Typography, Button, Fab, Snackbar } from '@material-ui/core'; + +const styles = theme => ({ + root: { + position: 'relative', + overflow: 'hidden', + }, + appFrame: { + width: '100%', + height: 360, + backgroundColor: theme.palette.background.paper, + }, + menuButton: { + marginLeft: -12, + marginRight: 20, + }, + button: { + marginBottom: theme.spacing(1), + }, + fab: { + position: 'absolute', + bottom: theme.spacing(2), + right: theme.spacing(2), + }, + fabMoveUp: { + transform: 'translate3d(0, -46px, 0)', + transition: theme.transitions.create('transform', { + duration: theme.transitions.duration.enteringScreen, + easing: theme.transitions.easing.easeOut, + }), + }, + fabMoveDown: { + transform: 'translate3d(0, 0, 0)', + transition: theme.transitions.create('transform', { + duration: theme.transitions.duration.leavingScreen, + easing: theme.transitions.easing.sharp, + }), + }, + snackbar: { + position: 'absolute', + }, + snackbarContent: { + width: '100%', + }, +}); + +class MobileNotif extends React.Component { + state = { + open: false, + }; + + handleClick = () => { + this.setState({ open: true }); + }; + + handleClose = () => { + this.setState({ open: false }); + }; + + render() { + const { classes } = this.props; + const { open } = this.state; + const fabClassName = classNames(classes.fab, open ? classes.fabMoveUp : classes.fabMoveDown); + + return ( + <div className={classes.root}> + <Button className={classes.button} variant="outlined" color="primary" onClick={this.handleClick}> + Open snackbar + </Button> + <div className={classes.appFrame}> + <AppBar position="static" color="primary"> + <Toolbar> + <IconButton className={classes.menuButton} color="inherit" aria-label="Menu"> + <MenuIcon /> + </IconButton> + <Typography variant="h6" color="inherit"> + Out of my way! + </Typography> + </Toolbar> + </AppBar> + <Fab color="secondary" className={fabClassName}> + <AddIcon /> + </Fab> + <Snackbar + open={open} + autoHideDuration={4000} + onClose={this.handleClose} + ContentProps={{ + 'aria-describedby': 'snackbar-fab-message-id', + className: classes.snackbarContent, + }} + message={<span id="snackbar-fab-message-id">Archived</span>} + action={( + <Button color="inherit" size="small" onClick={this.handleClose}> + Undo + </Button> + )} + className={classes.snackbar} + /> + </div> + </div> + ); + } +} + +MobileNotif.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(MobileNotif); diff --git a/front/odiparpack/app/containers/UiElements/demos/Notification/SimpleNotif.js b/front/odiparpack/app/containers/UiElements/demos/Notification/SimpleNotif.js new file mode 100644 index 0000000..0b068b5 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Notification/SimpleNotif.js @@ -0,0 +1,132 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import CloseIcon from '@material-ui/icons/Close'; +import { Typography, Button, Snackbar, IconButton, Grid } from '@material-ui/core'; + +const styles = theme => ({ + close: { + width: theme.spacing(4) + }, + divider: { + margin: `${theme.spacing(3)}px 0`, + }, + button: { + margin: theme.spacing(1) + } +}); + +class SimpleNotif extends React.Component { + state = { + open: false, + open2: false, + vertical: 'bottom', + horizontal: 'left', + }; + + handleClick = () => { + this.setState({ open: true }); + }; + + handleClose = (event, reason) => { + if (reason === 'clickaway') { + return; + } + + this.setState({ open: false }); + }; + + handleClick2 = state => () => { + this.setState({ open2: true, ...state }); + }; + + handleClose2 = () => { + this.setState({ open2: false }); + }; + + render() { + const { classes } = this.props; + const { vertical, horizontal, open2 } = this.state; + return ( + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid item md={6}> + <Typography variant="button" className={classes.divider}>Simple Notification</Typography> + <div> + <Button variant="contained" onClick={this.handleClick}>Open simple snackbar</Button> + <Snackbar + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'left', + }} + open={this.state.open} + autoHideDuration={6000} + onClose={this.handleClose} + ContentProps={{ + 'aria-describedby': 'message-id', + }} + message={<span id="message-id">Note archived</span>} + action={[ + <Button key="undo" color="secondary" size="small" onClick={this.handleClose}> + UNDO + </Button>, + <IconButton + key="close" + aria-label="Close" + color="inherit" + className={classes.close} + onClick={this.handleClose} + > + <CloseIcon /> + </IconButton>, + ]} + /> + </div> + </Grid> + <Grid item md={6}> + <Typography variant="button" className={classes.divider}>Positioning</Typography> + <div> + <Button className={classes.button} variant="contained" onClick={this.handleClick2({ vertical: 'top', horizontal: 'center' })}> + Top-Center + </Button> + <Button className={classes.button} variant="contained" onClick={this.handleClick2({ vertical: 'top', horizontal: 'right' })}> + Top-Right + </Button> + <Button className={classes.button} variant="contained" onClick={this.handleClick2({ vertical: 'bottom', horizontal: 'right' })}> + Bottom-Right + </Button> + <Button className={classes.button} variant="contained" onClick={this.handleClick2({ vertical: 'bottom', horizontal: 'center' })}> + Bottom-Center + </Button> + <Button className={classes.button} variant="contained" onClick={this.handleClick2({ vertical: 'bottom', horizontal: 'left' })}> + Bottom-Left + </Button> + <Button className={classes.button} variant="contained" onClick={this.handleClick2({ vertical: 'top', horizontal: 'left' })}> + Top-Left + </Button> + <Snackbar + anchorOrigin={{ vertical, horizontal }} + open={open2} + onClose={this.handleClose2} + ContentProps={{ + 'aria-describedby': 'message-id', + }} + message={<span id="message-id">I love snacks</span>} + /> + </div> + </Grid> + </Grid> + ); + } +} + +SimpleNotif.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(SimpleNotif); diff --git a/front/odiparpack/app/containers/UiElements/demos/Notification/StyledNotif.js b/front/odiparpack/app/containers/UiElements/demos/Notification/StyledNotif.js new file mode 100644 index 0000000..2ad8dba --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Notification/StyledNotif.js @@ -0,0 +1,198 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; +import CheckCircleOutlinedIcon from '@material-ui/icons/CheckCircleOutlined'; +import ErrorOutlineOutlinedIcon from '@material-ui/icons/ErrorOutlineOutlined'; +import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'; +import CloseIcon from '@material-ui/icons/Close'; +import ReportProblemOutlinedIcon from '@material-ui/icons/ReportProblemOutlined'; +import messageStyles from 'ba-styles/Messages.scss'; + +import { Typography, Button, IconButton, Snackbar, SnackbarContent, Grid } from '@material-ui/core'; + +const variantIcon = { + success: CheckCircleOutlinedIcon, + warning: ReportProblemOutlinedIcon, + error: ErrorOutlineOutlinedIcon, + info: InfoOutlinedIcon, +}; + +const styles1 = theme => ({ + success: { + backgroundColor: '#b6f8c4', + }, + error: { + backgroundColor: '#faabab', + }, + info: { + backgroundColor: '#b2e7f5', + }, + warning: { + backgroundColor: '#f5ea9f', + }, + icon: { + fontSize: 20, + color: 'black' + }, + iconVariant: { + opacity: 0.9, + marginRight: theme.spacing(1), + }, + message: { + display: 'flex', + alignItems: 'center', + color: 'black' + }, +}); + +function MySnackbarContent(props) { + const { + classes, + className, + message, + onClose, + variant, + ...other + } = props; + const Icon = variantIcon[variant]; + + return ( + <SnackbarContent + className={classNames(classes[variant], className)} + aria-describedby="client-snackbar" + message={( + <span id="client-snackbar" className={classes.message}> + <Icon className={classNames(classes.icon, classes.iconVariant)} /> + {message} + </span> + )} + action={[ + <IconButton + key="close" + aria-label="Close" + color="inherit" + className={classes.close} + onClick={onClose} + > + <CloseIcon className={classes.icon} /> + </IconButton>, + ]} + {...other} + /> + ); +} + +MySnackbarContent.propTypes = { + classes: PropTypes.object.isRequired, + className: PropTypes.string.isRequired, + message: PropTypes.node.isRequired, + onClose: PropTypes.func, + variant: PropTypes.oneOf(['success', 'warning', 'error', 'info']).isRequired, +}; + +MySnackbarContent.defaultProps = { + onClose: () => {} +}; + +const MySnackbarContentWrapper = withStyles(styles1)(MySnackbarContent); + +const styles = theme => ({ + snackbar: { + margin: theme.spacing(1), + }, + divider: { + margin: `${theme.spacing(3)}px 0`, + }, + margin: { + margin: theme.spacing(1) + } +}); + +const action = ( + <Button color="secondary" size="small"> + Action + </Button> +); + +class StyledNotif extends React.Component { + state = { + openStyle: false, + }; + + handleClickStyle = () => { + this.setState({ openStyle: true }); + }; + + handleCloseStyle = (event, reason) => { + if (reason === 'clickaway') { + return; + } + this.setState({ openStyle: false }); + }; + + render() { + const { classes } = this.props; + return ( + <Grid container alignItems="flex-start" justify="center" direction="row" spacing={2}> + <Grid item md={6} xs={12}> + <Typography variant="button" className={classes.divider}>Default Styled Notification</Typography> + <Button className={classes.margin} variant="outlined" color="primary" onClick={this.handleClickStyle}> + Open success snackbar + </Button> + <Snackbar + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'left', + }} + open={this.state.openStyle} + autoHideDuration={6000} + onClose={this.handleCloseStyle} + > + <MySnackbarContentWrapper + onClose={this.handleCloseStyle} + variant="success" + message="This is a success message!" + /> + </Snackbar> + <MySnackbarContentWrapper + variant="error" + className={classes.margin} + message="This is an error message!" + /> + <MySnackbarContentWrapper + variant="warning" + className={classes.margin} + message="This is a warning message!" + /> + <MySnackbarContentWrapper + variant="info" + className={classes.margin} + message="This is an information message!" + /> + <MySnackbarContentWrapper + variant="success" + className={classes.margin} + message="This is a success message!" + /> + </Grid> + <Grid item md={6} xs={12}> + <Typography variant="button" className={classes.divider}>Custom Styled Notification with CSS</Typography> + <div> + <SnackbarContent className={classes.snackbar} message="Notification default" action={action} /> + <SnackbarContent className={classNames(classes.snackbar, messageStyles.bgInfo)} message="Notification Info" action={action} /> + <SnackbarContent className={classNames(classes.snackbar, messageStyles.bgSuccess)} message="Success Notification Message" /> + <SnackbarContent className={classNames(classes.snackbar, messageStyles.bgWarning)} message="I love candy. I love cookies. I love cupcakes." action={action} /> + <SnackbarContent className={classNames(classes.snackbar, messageStyles.bgError)} message="I love cheesecake. I love chocolate." action={action} /> + </div> + </Grid> + </Grid> + ); + } +} + +StyledNotif.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(StyledNotif); diff --git a/front/odiparpack/app/containers/UiElements/demos/Notification/TransitionNotif.js b/front/odiparpack/app/containers/UiElements/demos/Notification/TransitionNotif.js new file mode 100644 index 0000000..6ac8df5 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Notification/TransitionNotif.js @@ -0,0 +1,203 @@ +import React, { Fragment, PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import CloseIcon from '@material-ui/icons/Close'; + +import { Typography, Button, Grid, Snackbar, Slide, Fade, IconButton } from '@material-ui/core'; + +const styles = theme => ({ + row: { + display: 'flex', + justifyContent: 'flex-start', + }, + close: { + width: theme.spacing(4) + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + }, + button: { + margin: theme.spacing(1) + } +}); + +function TransitionLeft(props) { + return <Slide {...props} direction="left" />; +} + +function TransitionUp(props) { + return <Slide {...props} direction="up" />; +} + +function TransitionRight(props) { + return <Slide {...props} direction="right" />; +} + +function TransitionDown(props) { + return <Slide {...props} direction="down" />; +} + +class TransitionNotif extends PureComponent { + state = { + open: false, + open2: false, + open3: false, + transition: null, + messageInfo: {}, + }; + + queue = []; + + handleClickQueue = message => () => { + this.queue.push({ + message, + key: new Date().getTime(), + }); + + if (this.state.open3) { + // immediately begin dismissing current message + // to start showing new one + this.setState({ open3: false }); + } else { + this.processQueue(); + } + }; + + processQueue = () => { + if (this.queue.length > 0) { + this.setState({ + messageInfo: this.queue.shift(), + open3: true, + }); + } + }; + + handleCloseQueue = (event, reason) => { + if (reason === 'clickaway') { + return; + } + this.setState({ open3: false }); + }; + + handleExited = () => { + this.processQueue(); + }; + + handleClick = transition => () => { + this.setState({ open: true, transition }); + }; + + handleClose = () => { + this.setState({ open: false }); + }; + + handleClick2 = () => { + this.setState({ open2: true }); + }; + + handleClose2 = () => { + this.setState({ open2: false }); + }; + + render() { + const { classes } = this.props; + const { message, key } = this.state.messageInfo; + return ( + <Fragment> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid + item + md={4} + > + <Typography variant="button" className={classes.divider}>Transition</Typography> + <div> + <Button variant="contained" className={classes.button} onClick={this.handleClick(TransitionLeft)}>Right</Button> + <Button variant="contained" className={classes.button} onClick={this.handleClick(TransitionUp)}>Up</Button> + <Button variant="contained" className={classes.button} onClick={this.handleClick(TransitionRight)}>Left</Button> + <Button variant="contained" className={classes.button} onClick={this.handleClick(TransitionDown)}>Down</Button> + <Snackbar + open={this.state.open} + onClose={this.handleClose} + TransitionComponent={this.state.transition} + ContentProps={{ + 'aria-describedby': 'message-id', + }} + message={<span>I love snacks</span>} + /> + </div> + </Grid> + <Grid + item + md={4} + > + <Typography variant="button" className={classes.divider}>Change Transition</Typography> + <div> + <Button variant="contained" className={classes.button} onClick={this.handleClick2}>Open with Fade Transition</Button> + <Snackbar + open={this.state.open2} + onClose={this.handleClose2} + TransitionComponent={Fade} + ContentProps={{ + 'aria-describedby': 'message-id', + }} + message={<span>I love snacks</span>} + /> + </div> + </Grid> + <Grid + item + md={4} + > + <Typography variant="button" className={classes.divider}>Consecutive Snackbars</Typography> + <div> + <Button variant="contained" className={classes.button} onClick={this.handleClickQueue('message a')}>Show message A</Button> + <Button variant="contained" className={classes.button} onClick={this.handleClickQueue('message b')}>Show message B</Button> + <Snackbar + key={key} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'left', + }} + open={this.state.open3} + autoHideDuration={6000} + onClose={this.handleCloseQueue} + onExited={this.handleExited} + ContentProps={{ + 'aria-describedby': 'message-id', + }} + message={<span>{message}</span>} + action={[ + <Button key="undo" color="secondary" size="small" onClick={this.handleClose2}> + UNDO + </Button>, + <IconButton + key="close" + aria-label="Close" + color="inherit" + className={classes.close} + onClick={this.handleCloseQueue} + > + <CloseIcon /> + </IconButton> + ]} + /> + </div> + </Grid> + </Grid> + </Fragment> + ); + } +} + +TransitionNotif.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(TransitionNotif); diff --git a/front/odiparpack/app/containers/UiElements/demos/Pagination/GeneralPagination.js b/front/odiparpack/app/containers/UiElements/demos/Pagination/GeneralPagination.js new file mode 100644 index 0000000..884be0a --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Pagination/GeneralPagination.js @@ -0,0 +1,136 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Pagination } from 'ba-components'; +import { Paper } from '@material-ui/core'; + +const styles = theme => ({ + paper: theme.mixins.gutters({ + paddingTop: 16, + paddingBottom: 16, + marginTop: theme.spacing(3), + }), +}); + +class GeneralPagination extends React.Component { + constructor() { + super(); + this.state = { + page: 1, + content: [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', + 'Suspendisse sed urna in justo euismod condimentum', + 'Fusce placerat enim et odio molestie sagittis.', + 'Vestibulum dignissim orci vitae eros rutrum euismod.', + 'Vestibulum tempor, sem et molestie egestas, dui tortor laoreet tellus, id rhoncus mauris neque malesuada augue.', + 'Duis tristique metus magna, lobortis aliquam risus euismod sit amet.', + 'Suspendisse porttitor velit nisl, feugiat tincidunt nisl mattis ut.', + 'Nulla lobortis nunc vitae nisi semper semper.', + 'Sed mi neque, convallis at ipsum at, blandit pretium enim.', + 'Nunc quis sem quis velit tincidunt congue a sit amet ante.', + 'In hac habitasse platea dictumst.', + 'In mi nulla, fringilla vestibulum finibus et, vehicula non leo. Vivamus et luctus mauris.', + 'Maecenas nisl libero, tincidunt id odio id, feugiat vulputate quam. Vestibulum feugiat rhoncus metus.', + 'In non erat et ipsum molestie porta sit amet ut felis.', + 'Vestibulum a massa vestibulum, gravida odio id, fringilla ipsum.', + 'Ut sed eros finibus, placerat orci id, dapibus mauris.', + 'Proin varius, tortor faucibus tempor pharetra, nunc mi consectetur enim, nec posuere ante magna vitae quam.', + 'Cras convallis lacus orci, tristique tincidunt magna consequat in.', + 'Vestibulum consequat hendrerit lacus. In id nisi id neque venenatis molestie.', + 'Quisque lacinia purus ut libero facilisis, at vulputate sem maximus.', + 'Pellentesque ac bibendum tortor, vel blandit nulla.', + 'Nulla eget lobortis lacus.', + 'Aliquam venenatis magna et odio lobortis maximus.', + 'Nullam in tortor ligula.', + 'Proin maximus risus nunc, eu aliquam nibh tempus a.', + 'Interdum et malesuada fames ac ante ipsum primis in faucibus.', + ], + contentsPerPage: 3 + }; + this.onPageChange = this.onPageChange.bind(this); + this.onPrev = this.onPrev.bind(this); + this.onNext = this.onNext.bind(this); + this.onGoFirst = this.onGoFirst.bind(this); + this.onGoLast = this.onGoLast.bind(this); + } + + onPageChange(page) { + this.setState({ page }); + } + + onPrev() { + if (this.state.page > 1) { + this.setState({ page: this.state.page -= 1 }); + } else { + this.setState({ page: 1 }); + } + } + + onNext(totalPages) { + if (this.state.page < totalPages) { + this.setState({ page: this.state.page += 1 }); + } else { + this.setState({ page: totalPages }); + } + } + + onGoFirst() { + this.setState({ page: 1 }); + } + + onGoLast(totalPages) { + this.setState({ page: totalPages }); + } + + render() { + const { classes } = this.props; + const { page, content, contentsPerPage } = this.state; + + // Logic for displaying current todos + const indexOfLastTodo = page * contentsPerPage; + const indexOfFirstTodo = indexOfLastTodo - contentsPerPage; + const currentContent = content.slice(indexOfFirstTodo, indexOfLastTodo); + + const renderContent = currentContent.map((ctn, index) => ( + <p key={index.toString()}>{ctn}</p> + )); + + // Logic for displaying page numbers + const pageNumbers = []; + for (let i = 1; i <= Math.ceil(content.length / contentsPerPage); i += 1) { + pageNumbers.push(i); + } + + return ( + <div className={classes.root}> + <Paper className={classes.paper}> + <h3> +We are in page + {page} + </h3> + <article> + {renderContent} + </article> + </Paper> + <Pagination + curpage={page} + totpages={pageNumbers.length} + boundaryPagesRange={1} + onChange={this.onPageChange} + siblingPagesRange={1} + hideEllipsis={false} + onPrev={this.onPrev} + onNext={() => this.onNext(pageNumbers.length)} + onGoFirst={this.onGoFirst} + onGoLast={() => this.onGoLast(pageNumbers.length)} + /> + </div> + ); + } +} + +GeneralPagination.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(GeneralPagination); diff --git a/front/odiparpack/app/containers/UiElements/demos/Pagination/TbPagination.js b/front/odiparpack/app/containers/UiElements/demos/Pagination/TbPagination.js new file mode 100644 index 0000000..d1a0214 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Pagination/TbPagination.js @@ -0,0 +1,110 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Table, TableBody, TableCell, TableFooter, TablePagination, TableRow, Paper } from '@material-ui/core'; + +let counter = 0; +function createData(name, calories, fat) { + counter += 1; + return { + id: counter, + name, + calories, + fat + }; +} + +const styles = theme => ({ + root: { + width: '100%', + marginTop: theme.spacing(3), + }, + table: { + minWidth: 500, + }, + tableWrapper: { + overflowX: 'auto', + }, +}); + +class TbPagination extends React.Component { + constructor(props, context) { + super(props, context); + + this.state = { + data: [ + createData('Cupcake', 305, 3.7), + createData('Donut', 452, 25.0), + createData('Eclair', 262, 16.0), + createData('Frozen yoghurt', 159, 6.0), + createData('Gingerbread', 356, 16.0), + createData('Honeycomb', 408, 3.2), + createData('Ice cream sandwich', 237, 9.0), + createData('Jelly Bean', 375, 0.0), + createData('KitKat', 518, 26.0), + createData('Lollipop', 392, 0.2), + createData('Marshmallow', 318, 0), + createData('Nougat', 360, 19.0), + createData('Oreo', 437, 18.0), + ].sort((a, b) => (a.calories < b.calories ? -1 : 1)), + page: 0, + rowsPerPage: 5, + }; + } + + handleChangePage = (event, page) => { + this.setState({ page }); + }; + + handleChangeRowsPerPage = event => { + this.setState({ rowsPerPage: event.target.value }); + }; + + render() { + const { classes } = this.props; + const { data, rowsPerPage, page } = this.state; + const emptyRows = rowsPerPage - Math.min(rowsPerPage, data.length - (page * rowsPerPage)); + + return ( + <Paper className={classes.root}> + <div className={classes.tableWrapper}> + <Table className={classes.table}> + <TableBody> + {data.slice(page * rowsPerPage, (page * rowsPerPage) + rowsPerPage).map(n => ( + <TableRow key={n.id}> + <TableCell>{n.name}</TableCell> + <TableCell align="right">{n.calories}</TableCell> + <TableCell align="right">{n.fat}</TableCell> + </TableRow> + ))} + {emptyRows > 0 && ( + <TableRow style={{ height: 48 * emptyRows }}> + <TableCell colSpan={6} /> + </TableRow> + )} + </TableBody> + <TableFooter> + <TableRow> + <TablePagination + colSpan={3} + rowsPerPageOptions={[5, 10, 25]} + count={data.length} + rowsPerPage={rowsPerPage} + page={page} + onChangePage={this.handleChangePage} + onChangeRowsPerPage={this.handleChangeRowsPerPage} + /> + </TableRow> + </TableFooter> + </Table> + </div> + </Paper> + ); + } +} + +TbPagination.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(TbPagination); diff --git a/front/odiparpack/app/containers/UiElements/demos/Pagination/TbPaginationActions.js b/front/odiparpack/app/containers/UiElements/demos/Pagination/TbPaginationActions.js new file mode 100644 index 0000000..3f56fa0 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Pagination/TbPaginationActions.js @@ -0,0 +1,93 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import FirstPageIcon from '@material-ui/icons/FirstPage'; +import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft'; +import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight'; +import LastPageIcon from '@material-ui/icons/LastPage'; +import { IconButton } from '@material-ui/core'; + +const actionsStyles = theme => ({ + root: { + flexShrink: 0, + color: theme.palette.text.secondary, + marginLeft: theme.spacing(2.5), + }, +}); + +class TbPaginationActions extends React.Component { + handleFirstPageButtonClick = event => { + this.props.onChangePage(event, 0); + }; + + handleBackButtonClick = event => { + this.props.onChangePage(event, this.props.page - 1); + }; + + handleNextButtonClick = event => { + this.props.onChangePage(event, this.props.page + 1); + }; + + handleLastPageButtonClick = event => { + this.props.onChangePage( + event, + Math.max(0, Math.ceil(this.props.count / this.props.rowsPerPage) - 1), + ); + }; + + render() { + const { + classes, + count, + page, + rowsPerPage, + theme + } = this.props; + + return ( + <div className={classes.root}> + <IconButton + onClick={this.handleFirstPageButtonClick} + disabled={page === 0} + aria-label="First Page" + > + {theme.direction === 'rtl' ? <LastPageIcon /> : <FirstPageIcon />} + </IconButton> + <IconButton + onClick={this.handleBackButtonClick} + disabled={page === 0} + aria-label="Previous Page" + > + {theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />} + </IconButton> + <IconButton + onClick={this.handleNextButtonClick} + disabled={page >= Math.ceil(count / rowsPerPage) - 1} + aria-label="Next Page" + > + {theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />} + </IconButton> + <IconButton + onClick={this.handleLastPageButtonClick} + disabled={page >= Math.ceil(count / rowsPerPage) - 1} + aria-label="Last Page" + > + {theme.direction === 'rtl' ? <FirstPageIcon /> : <LastPageIcon />} + </IconButton> + </div> + ); + } +} + +TbPaginationActions.propTypes = { + classes: PropTypes.object.isRequired, + count: PropTypes.number.isRequired, + onChangePage: PropTypes.func.isRequired, + page: PropTypes.number.isRequired, + rowsPerPage: PropTypes.number.isRequired, + theme: PropTypes.object.isRequired, +}; + +export default withStyles(actionsStyles, { withTheme: true })( + TbPaginationActions, +); diff --git a/front/odiparpack/app/containers/UiElements/demos/Pagination/TbPaginationCustom.js b/front/odiparpack/app/containers/UiElements/demos/Pagination/TbPaginationCustom.js new file mode 100644 index 0000000..49494dc --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Pagination/TbPaginationCustom.js @@ -0,0 +1,115 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Table, TableBody, TableCell, TablePagination, TableRow, TableFooter, Paper } from '@material-ui/core'; +import TbPaginationActions from './TbPaginationActions'; + + +let counter = 0; +function createData(name, calories, fat) { + counter += 1; + return { + id: counter, + name, + calories, + fat + }; +} + +const styles = theme => ({ + root: { + width: '100%', + marginTop: theme.spacing(3), + }, + table: { + minWidth: 500, + }, + tableWrapper: { + overflowX: 'auto', + }, +}); + +class TbPaginationCustom extends React.Component { + constructor(props, context) { + super(props, context); + + this.state = { + data: [ + createData('Cupcake', 305, 3.7), + createData('Donut', 452, 25.0), + createData('Eclair', 262, 16.0), + createData('Frozen yoghurt', 159, 6.0), + createData('Gingerbread', 356, 16.0), + createData('Honeycomb', 408, 3.2), + createData('Ice cream sandwich', 237, 9.0), + createData('Jelly Bean', 375, 0.0), + createData('KitKat', 518, 26.0), + createData('Lollipop', 392, 0.2), + createData('Marshmallow', 318, 0), + createData('Nougat', 360, 19.0), + createData('Oreo', 437, 18.0), + ].sort((a, b) => (a.calories < b.calories ? -1 : 1)), + page: 0, + rowsPerPage: 5, + }; + } + + handleChangePage = (event, page) => { + this.setState({ page }); + }; + + handleChangeRowsPerPage = event => { + this.setState({ rowsPerPage: event.target.value }); + }; + + render() { + const { classes } = this.props; + const { data, rowsPerPage, page } = this.state; + const emptyRows = rowsPerPage - Math.min(rowsPerPage, data.length - (page * rowsPerPage)); + + return ( + <Paper className={classes.root}> + <div className={classes.tableWrapper}> + <Table className={classes.table}> + <TableBody> + {data.slice(page * rowsPerPage, (page * rowsPerPage) + rowsPerPage).map(n => ( + <TableRow key={n.id}> + <TableCell component="th" scope="row"> + {n.name} + </TableCell> + <TableCell align="right">{n.calories}</TableCell> + <TableCell align="right">{n.fat}</TableCell> + </TableRow> + ))} + {emptyRows > 0 && ( + <TableRow style={{ height: 48 * emptyRows }}> + <TableCell colSpan={6} /> + </TableRow> + )} + </TableBody> + <TableFooter> + <TableRow> + <TablePagination + rowsPerPageOptions={[5, 10, 25]} + colSpan={3} + count={data.length} + rowsPerPage={rowsPerPage} + page={page} + onChangePage={this.handleChangePage} + onChangeRowsPerPage={this.handleChangeRowsPerPage} + ActionsComponent={TbPaginationActions} + /> + </TableRow> + </TableFooter> + </Table> + </div> + </Paper> + ); + } +} + +TbPaginationCustom.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(TbPaginationCustom); diff --git a/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/CustomizedTooltips.js b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/CustomizedTooltips.js new file mode 100644 index 0000000..f0f9111 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/CustomizedTooltips.js @@ -0,0 +1,125 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Button, Tooltip } from '@material-ui/core'; + +const styles = theme => ({ + lightTooltip: { + background: theme.palette.common.white, + color: theme.palette.text.primary, + boxShadow: theme.shadows[1], + fontSize: 11, + }, + arrowPopper: { + '&[x-placement*="bottom"] $arrowArrow': { + top: 0, + left: 0, + marginTop: '-0.9em', + width: '3em', + height: '1em', + '&::before': { + borderWidth: '0 1em 1em 1em', + borderColor: `transparent transparent ${theme.palette.grey[700]} transparent`, + }, + }, + '&[x-placement*="top"] $arrowArrow': { + bottom: 0, + left: 0, + marginBottom: '-0.9em', + width: '3em', + height: '1em', + '&::before': { + borderWidth: '1em 1em 0 1em', + borderColor: `${theme.palette.grey[700]} transparent transparent transparent`, + }, + }, + '&[x-placement*="right"] $arrowArrow': { + left: 0, + marginLeft: '-0.9em', + height: '3em', + width: '1em', + '&::before': { + borderWidth: '1em 1em 1em 0', + borderColor: `transparent ${theme.palette.grey[700]} transparent transparent`, + }, + }, + '&[x-placement*="left"] $arrowArrow': { + right: 0, + marginRight: '-0.9em', + height: '3em', + width: '1em', + '&::before': { + borderWidth: '1em 0 1em 1em', + borderColor: `transparent transparent transparent ${theme.palette.grey[700]}`, + }, + }, + }, + arrowArrow: { + position: 'absolute', + fontSize: 7, + width: '3em', + height: '3em', + '&::before': { + content: '""', + margin: 'auto', + display: 'block', + width: 0, + height: 0, + borderStyle: 'solid', + }, + }, +}); + +class CustomizedTooltips extends React.Component { + state = { + arrowRef: null, + }; + + handleArrowRef = node => { + this.setState({ + arrowRef: node, + }); + }; + + render() { + const { classes } = this.props; + + return ( + <div> + <Tooltip title="Add"> + <Button>Default</Button> + </Tooltip> + <Tooltip title="Add" classes={{ tooltip: classes.lightTooltip }}> + <Button>Light</Button> + </Tooltip> + <Tooltip + title={( + <React.Fragment> + Add + <span className={classes.arrowArrow} ref={this.handleArrowRef} /> + </React.Fragment> + )} + classes={{ popper: classes.arrowPopper }} + PopperProps={{ + popperOptions: { + modifiers: { + arrow: { + enabled: Boolean(this.state.arrowRef), + element: this.state.arrowRef, + }, + }, + }, + }} + > + <Button>Arrow</Button> + </Tooltip> + </div> + ); + } +} + +CustomizedTooltips.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CustomizedTooltips); diff --git a/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/DelayTooltips.js b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/DelayTooltips.js new file mode 100644 index 0000000..786dd4f --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/DelayTooltips.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { Button, Tooltip } from '@material-ui/core'; + +function DelayTooltips() { + return ( + <Tooltip title="Add" enterDelay={500} leaveDelay={200}> + <Button>[500ms, 200ms]</Button> + </Tooltip> + ); +} + +export default DelayTooltips; diff --git a/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/PopoverPlayground.js b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/PopoverPlayground.js new file mode 100644 index 0000000..7878df1 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/PopoverPlayground.js @@ -0,0 +1,340 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import SyntaxHighlighter, { registerLanguage } from 'react-syntax-highlighter/prism-light'; +import jsx from 'react-syntax-highlighter/languages/prism/jsx'; +import themeSource from 'react-syntax-highlighter/styles/prism/prism'; +import { green } from '@material-ui/core/colors'; + +import { + FormControl, + FormLabel, + FormControlLabel, + Radio, + RadioGroup, + Grid, + Typography, + Button, + Popover, + Input, + InputLabel, +} from '@material-ui/core'; + +const styles = theme => ({ + buttonWrapper: { + position: 'relative', + marginBottom: theme.spacing(4), + }, + anchor: { + backgroundColor: green[500], + width: 10, + height: 10, + borderRadius: '50%', + position: 'absolute', + }, + radioAnchor: { + color: green[600], + '&$checked': { + color: green[500], + }, + }, + checked: {}, + typography: { + margin: theme.spacing(2), + }, +}); + +const inlineStyles = { + anchorVertical: { + top: { + top: -5, + }, + center: { + top: 'calc(50% - 5px)', + }, + bottom: { + bottom: -5, + }, + }, + anchorHorizontal: { + left: { + left: -5, + }, + center: { + left: 'calc(50% - 5px)', + }, + right: { + right: -5, + }, + }, +}; + +class PopoverPlayground extends React.Component { + state = { + open: false, + anchorOriginVertical: 'top', + anchorOriginHorizontal: 'left', + transformOriginVertical: 'top', + transformOriginHorizontal: 'left', + positionTop: 200, // Just so the popover can be spotted more easily + positionLeft: 400, // Same as above + anchorReference: 'anchorEl', + }; + + handleChange = key => (event, value) => { + this.setState({ + [key]: value, + }); + }; + + handleNumberInputChange = key => event => { + this.setState({ + [key]: parseInt(event.target.value, 10), + }); + }; + + handleClickButton = () => { + this.setState({ + open: true, + }); + }; + + handleClose = () => { + this.setState({ + open: false, + }); + }; + + anchorEl = null; + + render() { + const { classes } = this.props; + registerLanguage('jsx', jsx); + const { + open, + anchorOriginVertical, + anchorOriginHorizontal, + transformOriginVertical, + transformOriginHorizontal, + positionTop, + positionLeft, + anchorReference, + } = this.state; + + let mode = ''; + + if (anchorReference === 'anchorPosition') { + mode = ` + anchorReference="${anchorReference}" + anchorPosition={{ top: ${positionTop}, left: ${positionLeft} }}`; + } + + const code = ` +<Popover ${mode} + anchorOrigin={{ + vertical: '${anchorOriginVertical}', + horizontal: '${anchorOriginHorizontal}', + }} + transformOrigin={{ + vertical: '${transformOriginVertical}', + horizontal: '${transformOriginHorizontal}', + }} +> +`; + + const radioAnchorClasses = { root: classes.radioAnchor, checked: classes.checked }; + + return ( + <div> + <Grid container justify="center" spacing={0}> + <Grid item className={classes.buttonWrapper}> + <Button + buttonRef={node => { + this.anchorEl = node; + }} + variant="contained" + onClick={this.handleClickButton} + > + Open Popover + </Button> + {anchorReference === 'anchorEl' && ( + <div + className={classes.anchor} + style={{ + ...inlineStyles.anchorVertical[anchorOriginVertical], + ...inlineStyles.anchorHorizontal[anchorOriginHorizontal], + }} + /> + )} + </Grid> + </Grid> + <Popover + open={open} + anchorEl={this.anchorEl} + anchorReference={anchorReference} + anchorPosition={{ top: positionTop, left: positionLeft }} + onClose={this.handleClose} + anchorOrigin={{ + vertical: anchorOriginVertical, + horizontal: anchorOriginHorizontal, + }} + transformOrigin={{ + vertical: transformOriginVertical, + horizontal: transformOriginHorizontal, + }} + > + <Typography className={classes.typography}>The content of the Popover.</Typography> + </Popover> + <Grid container spacing={2}> + <Grid item xs={12} sm={6}> + <FormControl component="fieldset"> + <FormLabel component="legend">anchorReference</FormLabel> + <RadioGroup + row + aria-label="anchorReference" + name="anchorReference" + value={this.state.anchorReference} + onChange={this.handleChange('anchorReference')} + > + <FormControlLabel value="anchorEl" control={<Radio />} label="anchorEl" /> + <FormControlLabel + value="anchorPosition" + control={<Radio />} + label="anchorPosition" + /> + </RadioGroup> + </FormControl> + </Grid> + <Grid item xs={12} sm={6}> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="position-top">anchorPosition.top</InputLabel> + <Input + id="position-top" + type="number" + value={this.state.positionTop} + onChange={this.handleNumberInputChange('positionTop')} + /> + </FormControl> + + <FormControl className={classes.formControl}> + <InputLabel htmlFor="position-left">anchorPosition.left</InputLabel> + <Input + id="position-left" + type="number" + value={this.state.positionLeft} + onChange={this.handleNumberInputChange('positionLeft')} + /> + </FormControl> + </Grid> + <Grid item xs={12} sm={6}> + <FormControl component="fieldset"> + <FormLabel component="legend">anchorOrigin.vertical</FormLabel> + <RadioGroup + aria-label="anchorOriginVertical" + name="anchorOriginVertical" + value={this.state.anchorOriginVertical} + onChange={this.handleChange('anchorOriginVertical')} + > + <FormControlLabel + value="top" + control={<Radio classes={radioAnchorClasses} />} + label="Top" + /> + <FormControlLabel + value="center" + control={<Radio classes={radioAnchorClasses} />} + label="Center" + /> + <FormControlLabel + value="bottom" + control={<Radio classes={radioAnchorClasses} />} + label="Bottom" + /> + </RadioGroup> + </FormControl> + </Grid> + <Grid item xs={12} sm={6}> + <FormControl component="fieldset"> + <FormLabel component="legend">transformOrigin.vertical</FormLabel> + <RadioGroup + aria-label="transformOriginVertical" + name="transformOriginVertical" + value={this.state.transformOriginVertical} + onChange={this.handleChange('transformOriginVertical')} + > + <FormControlLabel value="top" control={<Radio color="primary" />} label="Top" /> + <FormControlLabel + value="center" + control={<Radio color="primary" />} + label="Center" + /> + <FormControlLabel + value="bottom" + control={<Radio color="primary" />} + label="Bottom" + /> + </RadioGroup> + </FormControl> + </Grid> + <Grid item xs={12} sm={6}> + <FormControl component="fieldset"> + <FormLabel component="legend">anchorOrigin.horizontal</FormLabel> + <RadioGroup + row + aria-label="anchorOriginHorizontal" + name="anchorOriginHorizontal" + value={this.state.anchorOriginHorizontal} + onChange={this.handleChange('anchorOriginHorizontal')} + > + <FormControlLabel + value="left" + control={<Radio classes={radioAnchorClasses} />} + label="Left" + /> + <FormControlLabel + value="center" + control={<Radio classes={radioAnchorClasses} />} + label="Center" + /> + <FormControlLabel + value="right" + control={<Radio classes={radioAnchorClasses} />} + label="Right" + /> + </RadioGroup> + </FormControl> + </Grid> + <Grid item xs={12} sm={6}> + <FormControl component="fieldset"> + <FormLabel component="legend">transformOrigin.horizontal</FormLabel> + <RadioGroup + row + aria-label="transformOriginHorizontal" + name="transformOriginHorizontal" + value={this.state.transformOriginHorizontal} + onChange={this.handleChange('transformOriginHorizontal')} + > + <FormControlLabel value="left" control={<Radio color="primary" />} label="Left" /> + <FormControlLabel + value="center" + control={<Radio color="primary" />} + label="Center" + /> + <FormControlLabel value="right" control={<Radio color="primary" />} label="Right" /> + </RadioGroup> + </FormControl> + </Grid> + </Grid> + <SyntaxHighlighter language="jsx" style={themeSource} showLineNumbers="true"> + {code} + </SyntaxHighlighter> + </div> + ); + } +} + +PopoverPlayground.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(PopoverPlayground); diff --git a/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/PositionedTooltips.js b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/PositionedTooltips.js new file mode 100644 index 0000000..336d932 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/PositionedTooltips.js @@ -0,0 +1,83 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Grid, Button, Tooltip } from '@material-ui/core'; + +const styles = { + root: { + width: 500, + margin: '0 auto' + }, +}; + +function PositionedTooltips(props) { + const { classes } = props; + return ( + <div className={classes.root}> + <Grid container justify="center"> + <Grid item> + <Tooltip id="tooltip-top-start" title="Add" placement="top-start"> + <Button>top-start</Button> + </Tooltip> + <Tooltip id="tooltip-top" title="Add" placement="top"> + <Button>top</Button> + </Tooltip> + <Tooltip id="tooltip-top-end" title="Add" placement="top-end"> + <Button>top-end</Button> + </Tooltip> + </Grid> + </Grid> + <Grid container justify="center"> + <Grid item xs={6}> + <Tooltip id="tooltip-left-start" title="Add" placement="left-start"> + <Button>left-start</Button> + </Tooltip> + <br /> + <Tooltip id="tooltip-left" title="Add" placement="left"> + <Button>left</Button> + </Tooltip> + <br /> + <Tooltip id="tooltip-left-end" title="Add" placement="left-end"> + <Button>left-end</Button> + </Tooltip> + </Grid> + <Grid item container xs={6} alignItems="flex-end" direction="column" spacing={0}> + <Grid item> + <Tooltip id="tooltip-right-start" title="Add" placement="right-start"> + <Button>right-start</Button> + </Tooltip> + </Grid> + <Grid item> + <Tooltip id="tooltip-right" title="Add" placement="right"> + <Button>right</Button> + </Tooltip> + </Grid> + <Grid item> + <Tooltip id="tooltip-right-end" title="Add" placement="right-end"> + <Button>right-end</Button> + </Tooltip> + </Grid> + </Grid> + </Grid> + <Grid container justify="center"> + <Grid item> + <Tooltip id="tooltip-bottom-start" title="Add" placement="bottom-start"> + <Button>bottom-start</Button> + </Tooltip> + <Tooltip id="tooltip-bottom" title="Add" placement="bottom"> + <Button>bottom</Button> + </Tooltip> + <Tooltip id="tooltip-bottom-end" title="Add" placement="bottom-end"> + <Button>bottom-end</Button> + </Tooltip> + </Grid> + </Grid> + </div> + ); +} + +PositionedTooltips.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(PositionedTooltips); diff --git a/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/SimplePopover.js b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/SimplePopover.js new file mode 100644 index 0000000..23d2462 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/SimplePopover.js @@ -0,0 +1,79 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Button, Popover, Typography, Grid } from '@material-ui/core'; + +const styles = theme => ({ + typography: { + margin: theme.spacing(2), + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + textAlign: 'center' + }, + button: { + margin: theme.spacing(1), + }, +}); + +class SimplePopover extends React.Component { + state = { + anchorEl: null, + }; + + handleClick = event => { + this.setState({ + anchorEl: event.currentTarget, + }); + }; + + handleClose = () => { + this.setState({ + anchorEl: null, + }); + }; + + render() { + const { classes } = this.props; + const { anchorEl } = this.state; + return ( + <div> + <Grid + container + alignItems="center" + justify="center" + direction="row" + spacing={2} + > + <Grid item md={6}> + <Button className={classes.button} variant="contained" onClick={this.handleClick}> + Open Simple Popover + </Button> + <Popover + open={Boolean(anchorEl)} + anchorEl={anchorEl} + onClose={this.handleClose} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'center', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'center', + }} + > + <Typography className={classes.typography}>The content of the Popover.</Typography> + </Popover> + </Grid> + </Grid> + </div> + ); + } +} + +SimplePopover.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(SimplePopover); diff --git a/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/SimpleTooltips.js b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/SimpleTooltips.js new file mode 100644 index 0000000..0714e6c --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/SimpleTooltips.js @@ -0,0 +1,116 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import AddIcon from '@material-ui/icons/Add'; +import DeleteIcon from '@material-ui/icons/Delete'; +import { Fab, IconButton, Tooltip, Typography, Grid } from '@material-ui/core'; + +const styles = theme => ({ + fab: { + margin: theme.spacing(2), + }, + fixed: { + position: 'fixed', + bottom: theme.spacing(2), + right: theme.spacing(3), + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + textAlign: 'center' + }, +}); + +class SimpleTooltips extends React.Component { + state = { + open: false, + }; + + handleTooltipClose = () => { + this.setState({ open: false }); + }; + + handleTooltipOpen = () => { + this.setState({ open: true }); + }; + + render() { + const { classes } = this.props; + return ( + <div> + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid + item + md={6} + > + <Typography variant="button" className={classes.divider}>Simple Tooltips</Typography> + <Grid + container + alignItems="center" + justify="center" + direction="row" + spacing={2} + > + <Tooltip title="Delete"> + <IconButton aria-label="Delete"> + <DeleteIcon /> + </IconButton> + </Tooltip> + <Tooltip title="Add"> + <Fab color="primary" aria-label="Add" className={classes.fab}> + <AddIcon /> + </Fab> + </Tooltip> + <br /> + <br /> + <Tooltip title="FAB 'position: absolute;'"> + <Fab color="secondary" className={classes.fixed}> + <AddIcon /> + </Fab> + </Tooltip> + </Grid> + </Grid> + <Grid + item + md={6} + > + <Typography variant="button" className={classes.divider}>Delayed Tooltips</Typography> + <Grid + container + alignItems="center" + justify="center" + direction="row" + spacing={2} + > + <Tooltip + enterDelay={300} + leaveDelay={300} + onClose={this.handleTooltipClose} + onOpen={this.handleTooltipOpen} + open={this.state.open} + placement="bottom" + title="Delete" + > + <IconButton aria-label="Delete"> + <DeleteIcon /> + </IconButton> + </Tooltip> + </Grid> + </Grid> + </Grid> + </div> + ); + } +} + +SimpleTooltips.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(SimpleTooltips); diff --git a/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/TransitionsTooltips.js b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/TransitionsTooltips.js new file mode 100644 index 0000000..7b8adc1 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/TransitionsTooltips.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { Button, Tooltip, Fade, Zoom } from '@material-ui/core'; + +function TransitionsTooltips() { + return ( + <div> + <Tooltip title="Add"> + <Button>Grow</Button> + </Tooltip> + <Tooltip TransitionComponent={Fade} TransitionProps={{ timeout: 600 }} title="Add"> + <Button>Fade</Button> + </Tooltip> + <Tooltip TransitionComponent={Zoom} title="Add"> + <Button>Zoom</Button> + </Tooltip> + </div> + ); +} + +export default TransitionsTooltips; diff --git a/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/TriggersTooltips.js b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/TriggersTooltips.js new file mode 100644 index 0000000..bd4db5b --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/PopoverTooltip/TriggersTooltips.js @@ -0,0 +1,61 @@ +import React from 'react'; +import { Grid, Button, Tooltip, ClickAwayListener } from '@material-ui/core'; + +class TriggersTooltips extends React.Component { + state = { + open: false, + }; + + handleTooltipClose = () => { + this.setState({ open: false }); + }; + + handleTooltipOpen = () => { + this.setState({ open: true }); + }; + + render() { + return ( + <div> + <Grid container justify="center"> + <Grid item> + <Tooltip disableFocusListener title="Add"> + <Button>Hover or touch</Button> + </Tooltip> + </Grid> + <Grid item> + <Tooltip disableHoverListener title="Add"> + <Button>Focus or touch</Button> + </Tooltip> + </Grid> + <Grid item> + <Tooltip disableFocusListener disableTouchListener title="Add"> + <Button>Hover</Button> + </Tooltip> + </Grid> + <Grid item> + <ClickAwayListener onClickAway={this.handleTooltipClose}> + <div> + <Tooltip + PopperProps={{ + disablePortal: true, + }} + onClose={this.handleTooltipClose} + open={this.state.open} + disableFocusListener + disableHoverListener + disableTouchListener + title="Add" + > + <Button onClick={this.handleTooltipOpen}>Click</Button> + </Tooltip> + </div> + </ClickAwayListener> + </Grid> + </Grid> + </div> + ); + } +} + +export default TriggersTooltips; diff --git a/front/odiparpack/app/containers/UiElements/demos/Progress/CircularDeterminate.js b/front/odiparpack/app/containers/UiElements/demos/Progress/CircularDeterminate.js new file mode 100644 index 0000000..0b1312b --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Progress/CircularDeterminate.js @@ -0,0 +1,69 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { CircularProgress } from '@material-ui/core'; + +const styles = theme => ({ + progress: { + margin: theme.spacing(2), + }, +}); + +class CircularDeterminate extends React.Component { + state = { + completed: 0, + }; + + componentDidMount() { + this.timer = setInterval(this.progress, 20); + } + + componentWillUnmount() { + clearInterval(this.timer); + } + + timer; + + progress = () => { + const { completed } = this.state; + this.setState({ completed: completed === 100 ? 0 : completed + 1 }); + }; + + render() { + const { classes } = this.props; + return ( + <div> + <CircularProgress + className={classes.progress} + variant="determinate" + value={this.state.completed} + /> + <CircularProgress + className={classes.progress} + variant="determinate" + size={50} + value={this.state.completed} + /> + <CircularProgress + className={classes.progress} + color="secondary" + variant="determinate" + value={this.state.completed} + /> + <CircularProgress + className={classes.progress} + color="secondary" + variant="determinate" + size={50} + value={this.state.completed} + /> + </div> + ); + } +} + +CircularDeterminate.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CircularDeterminate); diff --git a/front/odiparpack/app/containers/UiElements/demos/Progress/CircularIndeterminate.js b/front/odiparpack/app/containers/UiElements/demos/Progress/CircularIndeterminate.js new file mode 100644 index 0000000..cc37de8 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Progress/CircularIndeterminate.js @@ -0,0 +1,29 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { purple } from '@material-ui/core/colors'; +import { CircularProgress } from '@material-ui/core'; + +const styles = theme => ({ + progress: { + margin: theme.spacing(2), + }, +}); + +function CircularIndeterminate(props) { + const { classes } = props; + return ( + <div> + <CircularProgress className={classes.progress} /> + <CircularProgress className={classes.progress} size={50} /> + <CircularProgress className={classes.progress} color="secondary" /> + <CircularProgress className={classes.progress} style={{ color: purple[500] }} thickness={7} /> + </div> + ); +} + +CircularIndeterminate.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CircularIndeterminate); diff --git a/front/odiparpack/app/containers/UiElements/demos/Progress/CircularIntegration.js b/front/odiparpack/app/containers/UiElements/demos/Progress/CircularIntegration.js new file mode 100644 index 0000000..ff19f5c --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Progress/CircularIntegration.js @@ -0,0 +1,114 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { withStyles } from '@material-ui/core/styles'; +import CheckIcon from '@material-ui/icons/Check'; +import SaveIcon from '@material-ui/icons/Save'; +import { green } from '@material-ui/core/colors'; + +import { CircularProgress, Button, Fab } from '@material-ui/core'; + +const styles = theme => ({ + root: { + display: 'flex', + alignItems: 'center', + }, + wrapper: { + margin: theme.spacing(1), + position: 'relative', + }, + buttonSuccess: { + backgroundColor: green[500], + '&:hover': { + backgroundColor: green[700], + }, + }, + fabProgress: { + color: green[500], + position: 'absolute', + top: -6, + left: -6, + zIndex: 1, + }, + buttonProgress: { + color: green[500], + position: 'absolute', + top: '50%', + left: '50%', + marginTop: -12, + marginLeft: -12, + }, +}); + +class CircularIntegration extends React.Component { + state = { + loading: false, + success: false, + }; + + componentWillUnmount() { + clearTimeout(this.timer); + } + + handleButtonClick = () => { + if (!this.state.loading) { + this.setState( + { + success: false, + loading: true, + }, + () => { + this.timer = setTimeout(() => { + this.setState({ + loading: false, + success: true, + }); + }, 2000); + }, + ); + } + }; + + timer = undefined; + + render() { + const { loading, success } = this.state; + const { classes } = this.props; + const buttonClassname = classNames({ + [classes.buttonSuccess]: success, + }); + + return ( + <div className={classes.root}> + <div className={classes.wrapper}> + <Fab + color="primary" + className={buttonClassname} + onClick={this.handleButtonClick} + > + {success ? <CheckIcon /> : <SaveIcon />} + </Fab> + {loading && <CircularProgress size={68} className={classes.fabProgress} />} + </div> + <div className={classes.wrapper}> + <Button + variant="contained" + color="primary" + className={buttonClassname} + disabled={loading} + onClick={this.handleButtonClick} + > + Accept terms + </Button> + {loading && <CircularProgress size={24} className={classes.buttonProgress} />} + </div> + </div> + ); + } +} + +CircularIntegration.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CircularIntegration); diff --git a/front/odiparpack/app/containers/UiElements/demos/Progress/CircularStatic.js b/front/odiparpack/app/containers/UiElements/demos/Progress/CircularStatic.js new file mode 100644 index 0000000..bfa931a --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Progress/CircularStatic.js @@ -0,0 +1,29 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { CircularProgress } from '@material-ui/core'; + +const styles = theme => ({ + progress: { + margin: theme.spacing(2), + }, +}); + +function CircularStatic(props) { + const { classes } = props; + return ( + <div> + <CircularProgress className={classes.progress} variant="static" value={5} /> + <CircularProgress className={classes.progress} variant="static" value={25} /> + <CircularProgress className={classes.progress} variant="static" value={50} /> + <CircularProgress className={classes.progress} variant="static" value={75} /> + <CircularProgress className={classes.progress} variant="static" value={100} /> + </div> + ); +} + +CircularStatic.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CircularStatic); diff --git a/front/odiparpack/app/containers/UiElements/demos/Progress/LinearBuffer.js b/front/odiparpack/app/containers/UiElements/demos/Progress/LinearBuffer.js new file mode 100644 index 0000000..44bca71 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Progress/LinearBuffer.js @@ -0,0 +1,56 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { LinearProgress } from '@material-ui/core'; + +const styles = { + root: { + flexGrow: 1, + }, +}; + +class LinearBuffer extends React.Component { + state = { + completed: 0, + buffer: 10, + }; + + componentDidMount() { + this.timer = setInterval(this.progress, 500); + } + + componentWillUnmount() { + clearInterval(this.timer); + } + + timer = null; + + progress = () => { + const { completed } = this.state; + if (completed > 100) { + this.setState({ completed: 0, buffer: 10 }); + } else { + const diff = Math.random() * 10; + const diff2 = Math.random() * 10; + this.setState({ completed: completed + diff, buffer: completed + diff + diff2 }); + } + }; + + render() { + const { classes } = this.props; + const { completed, buffer } = this.state; + return ( + <div className={classes.root}> + <LinearProgress variant="buffer" value={completed} valueBuffer={buffer} /> + <br /> + <LinearProgress color="secondary" variant="buffer" value={completed} valueBuffer={buffer} /> + </div> + ); + } +} + +LinearBuffer.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(LinearBuffer); diff --git a/front/odiparpack/app/containers/UiElements/demos/Progress/LinearDeterminate.js b/front/odiparpack/app/containers/UiElements/demos/Progress/LinearDeterminate.js new file mode 100644 index 0000000..08c718d --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Progress/LinearDeterminate.js @@ -0,0 +1,53 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { LinearProgress } from '@material-ui/core'; + +const styles = { + root: { + flexGrow: 1, + }, +}; + +class LinearDeterminate extends React.Component { + state = { + completed: 0, + }; + + componentDidMount() { + this.timer = setInterval(this.progress, 500); + } + + componentWillUnmount() { + clearInterval(this.timer); + } + + timer = null; + + progress = () => { + const { completed } = this.state; + if (completed === 100) { + this.setState({ completed: 0 }); + } else { + const diff = Math.random() * 10; + this.setState({ completed: Math.min(completed + diff, 100) }); + } + }; + + render() { + const { classes } = this.props; + return ( + <div className={classes.root}> + <LinearProgress variant="determinate" value={this.state.completed} /> + <br /> + <LinearProgress color="secondary" variant="determinate" value={this.state.completed} /> + </div> + ); + } +} + +LinearDeterminate.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(LinearDeterminate); diff --git a/front/odiparpack/app/containers/UiElements/demos/Progress/LinearIndeterminate.js b/front/odiparpack/app/containers/UiElements/demos/Progress/LinearIndeterminate.js new file mode 100644 index 0000000..de5071a --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Progress/LinearIndeterminate.js @@ -0,0 +1,27 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { LinearProgress } from '@material-ui/core'; + +const styles = { + root: { + flexGrow: 1, + }, +}; + +function LinearIndeterminate(props) { + const { classes } = props; + return ( + <div className={classes.root}> + <LinearProgress /> + <br /> + <LinearProgress color="secondary" /> + </div> + ); +} + +LinearIndeterminate.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(LinearIndeterminate); diff --git a/front/odiparpack/app/containers/UiElements/demos/Progress/LinearQuery.js b/front/odiparpack/app/containers/UiElements/demos/Progress/LinearQuery.js new file mode 100644 index 0000000..6e51da7 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Progress/LinearQuery.js @@ -0,0 +1,27 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { LinearProgress } from '@material-ui/core'; + +const styles = { + root: { + flexGrow: 1, + }, +}; + +function LinearQuery(props) { + const { classes } = props; + return ( + <div className={classes.root}> + <LinearProgress variant="query" /> + <br /> + <LinearProgress color="secondary" variant="query" /> + </div> + ); +} + +LinearQuery.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(LinearQuery); diff --git a/front/odiparpack/app/containers/UiElements/demos/Progress/LinearStatic.js b/front/odiparpack/app/containers/UiElements/demos/Progress/LinearStatic.js new file mode 100644 index 0000000..55ad2a1 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Progress/LinearStatic.js @@ -0,0 +1,27 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { LinearProgress } from '@material-ui/core'; + +const styles = { + root: { + flexGrow: 1, + }, +}; + +function LinearIndeterminate(props) { + const { classes } = props; + return ( + <div className={classes.root}> + <LinearProgress variant="determinate" value={20} /> + <br /> + <LinearProgress variant="determinate" value={60} color="secondary" /> + </div> + ); +} + +LinearIndeterminate.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(LinearIndeterminate); diff --git a/front/odiparpack/app/containers/UiElements/demos/Progress/ProgressDelay.js b/front/odiparpack/app/containers/UiElements/demos/Progress/ProgressDelay.js new file mode 100644 index 0000000..6417795 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Progress/ProgressDelay.js @@ -0,0 +1,105 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Fade, Button, CircularProgress, Typography } from '@material-ui/core'; + +const styles = theme => ({ + root: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + }, + button: { + margin: theme.spacing(2), + }, + placeholder: { + height: 40, + }, +}); + +class ProgressDelay extends React.Component { + state = { + loading: false, + query: 'idle', + }; + + componentWillUnmount() { + clearTimeout(this.timer); + } + + timer = null; + + handleClickLoading = () => { + this.setState({ + loading: !this.state.loading, + }); + }; + + handleClickQuery = () => { + clearTimeout(this.timer); + + if (this.state.query !== 'idle') { + this.setState({ + query: 'idle', + }); + return; + } + + this.setState({ + query: 'progress', + }); + this.timer = setTimeout(() => { + this.setState({ + query: 'success', + }); + }, 2e3); + }; + + render() { + const { classes } = this.props; + const { loading, query } = this.state; + + return ( + <div className={classes.root}> + <div className={classes.placeholder}> + <Fade + in={loading} + style={{ + transitionDelay: loading ? '800ms' : '0ms', + }} + unmountOnExit + > + <CircularProgress /> + </Fade> + </div> + <Button onClick={this.handleClickLoading} className={classes.button}> + {loading ? 'Stop loading' : 'Loading'} + </Button> + <div className={classes.placeholder}> + {query === 'success' ? ( + <Typography>Success!</Typography> + ) : ( + <Fade + in={query === 'progress'} + style={{ + transitionDelay: query === 'progress' ? '800ms' : '0ms', + }} + unmountOnExit + > + <CircularProgress /> + </Fade> + )} + </div> + <Button onClick={this.handleClickQuery} className={classes.button}> + {query !== 'idle' ? 'Reset' : 'Simulate a load'} + </Button> + </div> + ); + } +} + +ProgressDelay.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ProgressDelay); diff --git a/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/AnimatedSlider.js b/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/AnimatedSlider.js new file mode 100644 index 0000000..57fb5e4 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/AnimatedSlider.js @@ -0,0 +1,72 @@ +import React from 'react'; +import Type from 'ba-styles/Typography.scss'; +import Slider from 'react-animated-slider'; +import imgApi from 'ba-api/images'; +import avatarApi from 'ba-api/avatars'; +import 'ba-styles/vendors/react-animated-slider/react-animated-slider.css'; + +import { Button, Typography, Hidden } from '@material-ui/core'; + +const content = [ + { + title: 'Vulputate Mollis Ultricies Fermentum Parturient', + description: + 'Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Cras justo odio, dapibus ac facilisis.', + button: 'Read More', + image: imgApi[38], + user: 'Luanda Gjokaj', + userProfile: avatarApi[1] + }, + { + title: 'Tortor Dapibus Commodo Aenean Quam', + description: + 'Nullam id dolor id nibh ultricies vehicula ut id elit. Cras mattis consectetur purus sit amet fermentum. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Donec sed odio dui.', + button: 'Discover', + image: imgApi[2], + user: 'Erich Behrens', + userProfile: avatarApi[8] + }, + { + title: 'Phasellus volutpat metus', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula.', + button: 'Buy now', + image: imgApi[28], + user: 'Bruno Vizovskyy', + userProfile: avatarApi[10] + } +]; + +const AnimatedSlider = () => ( + <div> + <Slider className="slider-wrapper"> + {content.map((item, index) => ( + <div + key={index.toString()} + className="slider-content" + style={{ background: `url('${item.image}') no-repeat center center` }} + > + <div className="inner"> + <Typography variant="h4" component="h1" className={Type.light} gutterBottom>{item.title}</Typography> + <Hidden mdDown> + <p>{item.description}</p> + </Hidden> + <Button variant="contained" color="primary"> + {item.button} + </Button> + </div> + <section> + <img src={item.userProfile} alt={item.user} /> + <span> + Posted by + {' '} + <strong>{item.user}</strong> + </span> + </section> + </div> + ))} + </Slider> + </div> +); + +export default AnimatedSlider; diff --git a/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/AutoplayCarousel.js b/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/AutoplayCarousel.js new file mode 100644 index 0000000..59c11ef --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/AutoplayCarousel.js @@ -0,0 +1,52 @@ +import React from 'react'; +import Slider from 'react-slick'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import imgData from 'ba-api/imgData'; +import 'ba-styles/vendors/slick-carousel/slick-carousel.css'; +import 'ba-styles/vendors/slick-carousel/slick.css'; +import 'ba-styles/vendors/slick-carousel/slick-theme.css'; + +const styles = ({ + item: { + textAlign: 'center', + '& img': { + margin: '10px auto' + } + } +}); + +class AutoplayCarousel extends React.Component { + render() { + const { classes } = this.props; + const settings = { + dots: true, + infinite: true, + centerMode: false, + speed: 500, + autoplaySpeed: 2000, + pauseOnHover: true, + autoplay: true, + slidesToShow: 3, + slidesToScroll: 1, + cssEase: 'ease-out' + }; + return ( + <div className="container"> + <Slider {...settings}> + {imgData.map((item, index) => ( + <div key={index.toString()} className={classes.item}> + <img src={item.img} alt={item.title} /> + </div> + ))} + </Slider> + </div> + ); + } +} + +AutoplayCarousel.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(AutoplayCarousel); diff --git a/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/CustomCarousel.js b/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/CustomCarousel.js new file mode 100644 index 0000000..a558d8e --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/CustomCarousel.js @@ -0,0 +1,93 @@ +import React from 'react'; +import Slider from 'react-slick'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import ArrowForward from '@material-ui/icons/ArrowForward'; +import ArrowBack from '@material-ui/icons/ArrowBack'; +import imgData from 'ba-api/imgData'; +import 'ba-styles/vendors/slick-carousel/slick-carousel.css'; +import 'ba-styles/vendors/slick-carousel/slick.css'; +import 'ba-styles/vendors/slick-carousel/slick-theme.css'; +import { IconButton } from '@material-ui/core'; + +function SampleNextArrow(props) { + const { onClick } = props; + return ( + <IconButton + className="nav-next" + onClick={onClick} + > + <ArrowForward /> + </IconButton> + ); +} + +SampleNextArrow.propTypes = { + onClick: PropTypes.func, +}; + +SampleNextArrow.defaultProps = { + onClick: undefined, +}; + +function SamplePrevArrow(props) { + const { onClick } = props; + return ( + <IconButton + className="nav-prev" + onClick={onClick} + > + <ArrowBack /> + </IconButton> + ); +} + +SamplePrevArrow.propTypes = { + onClick: PropTypes.func, +}; + +SamplePrevArrow.defaultProps = { + onClick: undefined, +}; + +const styles = ({ + item: { + textAlign: 'center', + '& img': { + margin: '10px auto' + } + } +}); + +class CustomCarousel extends React.Component { + render() { + const { classes } = this.props; + const settings = { + dots: true, + infinite: true, + centerMode: true, + speed: 500, + slidesToShow: 3, + slidesToScroll: 1, + nextArrow: <SampleNextArrow />, + prevArrow: <SamplePrevArrow /> + }; + return ( + <div className="container custom-arrow"> + <Slider {...settings}> + {imgData.map((item, index) => ( + <div key={index.toString()} className={classes.item}> + <img src={item.img} alt={item.title} /> + </div> + ))} + </Slider> + </div> + ); + } +} + +CustomCarousel.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CustomCarousel); diff --git a/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/MultipleCarousel.js b/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/MultipleCarousel.js new file mode 100644 index 0000000..c8ff743 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/MultipleCarousel.js @@ -0,0 +1,48 @@ +import React from 'react'; +import Slider from 'react-slick'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import imgData from 'ba-api/imgData'; +import 'ba-styles/vendors/slick-carousel/slick-carousel.css'; +import 'ba-styles/vendors/slick-carousel/slick.css'; +import 'ba-styles/vendors/slick-carousel/slick-theme.css'; + +const styles = ({ + item: { + textAlign: 'center', + '& img': { + margin: '10px auto' + } + } +}); + +class MultipleCarousel extends React.Component { + render() { + const { classes } = this.props; + const settings = { + dots: true, + infinite: true, + centerMode: true, + speed: 500, + slidesToShow: 3, + slidesToScroll: 1 + }; + return ( + <div className="container"> + <Slider {...settings}> + {imgData.map((item, index) => ( + <div key={index.toString()} className={classes.item}> + <img src={item.img} alt={item.title} /> + </div> + ))} + </Slider> + </div> + ); + } +} + +MultipleCarousel.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(MultipleCarousel); diff --git a/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/SingleCarousel.js b/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/SingleCarousel.js new file mode 100644 index 0000000..76158f7 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/SingleCarousel.js @@ -0,0 +1,80 @@ +import React from 'react'; +import Slider from 'react-slick'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import imgData from 'ba-api/imgData'; +import 'ba-styles/vendors/slick-carousel/slick-carousel.css'; +import 'ba-styles/vendors/slick-carousel/slick.css'; +import 'ba-styles/vendors/slick-carousel/slick-theme.css'; + +import { FormControl, MenuItem, InputLabel, Select } from '@material-ui/core'; + +const styles = ({ + root: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center' + }, + formControl: { + width: '50%', + margin: '0 auto' + }, + item: { + textAlign: 'center', + '& img': { + margin: '10px auto' + } + } +}); + +class SingleCarousel extends React.Component { + state = { + transition: 'slide' + } + + handleChange = event => { + this.setState({ [event.target.name]: event.target.value }); + }; + + render() { + const { classes } = this.props; + const { transition } = this.state; + const settings = { + dots: true, + fade: this.state.transition === 'fade', + }; + return ( + <div className={classes.root}> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="age-simple">Carousel Transition</InputLabel> + <Select + value={transition} + onChange={this.handleChange} + inputProps={{ + name: 'transition', + id: 'transition-single-carousel', + }} + > + <MenuItem value="slide">Slide</MenuItem> + <MenuItem value="fade">Fade</MenuItem> + </Select> + </FormControl> + <div className="container"> + <Slider {...settings}> + {imgData.map((item, index) => ( + <div key={index.toString()} className={classes.item}> + <img src={item.img} alt={item.title} /> + </div> + ))} + </Slider> + </div> + </div> + ); + } +} + +SingleCarousel.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(SingleCarousel); diff --git a/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/ThumbnailCarousel.js b/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/ThumbnailCarousel.js new file mode 100644 index 0000000..9860b55 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/ThumbnailCarousel.js @@ -0,0 +1,90 @@ +import React from 'react'; +import Slider from 'react-slick'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import 'ba-styles/vendors/slick-carousel/slick-carousel.css'; +import 'ba-styles/vendors/slick-carousel/slick.css'; +import 'ba-styles/vendors/slick-carousel/slick-theme.css'; +import imgData from 'ba-api/imgData'; + +import { FormControl, MenuItem, InputLabel, Select } from '@material-ui/core'; + +const styles = ({ + root: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center' + }, + formControl: { + width: '50%', + margin: '0 auto' + }, + item: { + textAlign: 'center', + '& img': { + margin: '10px auto' + } + } +}); + +const getThumb = imgData.map(a => a.thumb); + +class ThumbnailCarousel extends React.Component { + state = { + transition: 'slide' + } + + handleChange = event => { + this.setState({ [event.target.name]: event.target.value }); + }; + + render() { + const { classes } = this.props; + const { transition } = this.state; + const settings = { + customPaging: (i) => ( + <a> + <img src={getThumb[i]} alt="thumb" /> + </a> + ), + infinite: true, + dots: true, + slidesToShow: 1, + slidesToScroll: 1, + fade: this.state.transition === 'fade', + }; + return ( + <div className={classes.root}> + <FormControl className={classes.formControl}> + <InputLabel htmlFor="age-simple">Carousel Transition</InputLabel> + <Select + value={transition} + onChange={this.handleChange} + inputProps={{ + name: 'transition', + id: 'transition-single-carousel', + }} + > + <MenuItem value="slide">Slide</MenuItem> + <MenuItem value="fade">Fade</MenuItem> + </Select> + </FormControl> + <div className="container thumb-nav custom-nav"> + <Slider {...settings}> + {imgData.map((item, index) => ( + <div key={index.toString()} className={classes.item}> + <img src={item.img} alt={item.title} /> + </div> + ))} + </Slider> + </div> + </div> + ); + } +} + +ThumbnailCarousel.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ThumbnailCarousel); diff --git a/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/VerticalCarousel.js b/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/VerticalCarousel.js new file mode 100644 index 0000000..36dd7c3 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/SliderCaraousel/VerticalCarousel.js @@ -0,0 +1,48 @@ +import React from 'react'; +import Slider from 'react-slick'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import imgData from 'ba-api/imgData'; +import 'ba-styles/vendors/slick-carousel/slick-carousel.css'; +import 'ba-styles/vendors/slick-carousel/slick.css'; +import 'ba-styles/vendors/slick-carousel/slick-theme.css'; + +const styles = ({ + item: { + textAlign: 'center', + '& img': { + margin: '10px auto' + } + } +}); + +class VerticalCarousel extends React.Component { + render() { + const { classes } = this.props; + const settings = { + infinite: true, + slidesToShow: 2, + slidesToScroll: 1, + vertical: true, + verticalSwiping: true, + swipeToSlide: true, + }; + return ( + <div className="container"> + <Slider {...settings}> + {imgData.map((item, index) => ( + <div key={index.toString()} className={classes.item}> + <img src={item.img} alt={item.title} /> + </div> + ))} + </Slider> + </div> + ); + } +} + +VerticalCarousel.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(VerticalCarousel); diff --git a/front/odiparpack/app/containers/UiElements/demos/Steppers/HorizontalLinear.js b/front/odiparpack/app/containers/UiElements/demos/Steppers/HorizontalLinear.js new file mode 100644 index 0000000..db47ee1 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Steppers/HorizontalLinear.js @@ -0,0 +1,196 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Type from 'ba-styles/Typography.scss'; + +import { + Stepper, + Step, + StepLabel, + Button, + Typography, + FormGroup, + FormControlLabel, + Switch, + Divider, +} from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '100%', + }, + button: { + marginRight: theme.spacing(1), + }, + instructions: { + marginTop: theme.spacing(1), + marginBottom: theme.spacing(1), + }, +}); + +function getSteps() { + return ['Select campaign settings', 'Create an ad group', 'Create an ad']; +} + +function getStepContent(step) { + switch (step) { + case 0: + return 'Select campaign settings...'; + case 1: + return 'What is an ad group anyways?'; + case 2: + return 'This is the bit I really care about!'; + default: + return 'Unknown step'; + } +} + +class HorizontalLinear extends React.Component { + static propTypes = { + classes: PropTypes.object.isRequired, + }; + + state = { + activeStep: 0, + altLabel: false, + skipped: new Set(), + }; + + isStepOptional = step => (step === 1); + + isStepSkipped(step) { + return this.state.skipped.has(step); + } + + handleNext = () => { + const { activeStep } = this.state; + let { skipped } = this.state; + if (this.isStepSkipped(activeStep)) { + skipped = new Set(skipped.values()); + skipped.delete(activeStep); + } + this.setState({ + activeStep: activeStep + 1, + skipped, + }); + }; + + handleBack = () => { + const { activeStep } = this.state; + this.setState({ + activeStep: activeStep - 1, + }); + }; + + handleSkip = () => { + const { activeStep } = this.state; + if (!this.isStepOptional(activeStep)) { + // You probably want to guard against something like this, + // it should never occur unless someone's actively trying to break something. + throw new Error("You can't skip a step that isn't optional."); + } + const skipped = new Set(this.state.skipped.values()); + skipped.add(activeStep); + this.setState({ + activeStep: this.state.activeStep + 1, + skipped, + }); + }; + + handleReset = () => { + this.setState({ + activeStep: 0, + }); + }; + + handleChange = name => event => { + this.setState({ [name]: event.target.checked }); + }; + + render() { + const { classes } = this.props; + const steps = getSteps(); + const { activeStep, altLabel } = this.state; + + return ( + <div className={classes.root}> + <FormGroup row> + <FormControlLabel + control={( + <Switch + checked={altLabel} + onChange={this.handleChange('altLabel')} + value="altLabel" + /> + )} + label="Alternative Design" + /> + </FormGroup> + <Divider /> + <Stepper activeStep={activeStep} alternativeLabel={altLabel}> + {steps.map((label, index) => { + const props = {}; + const labelProps = {}; + if (this.isStepOptional(index)) { + labelProps.optional = <Typography className={altLabel ? Type.textCenter : ''} variant="caption">Optional</Typography>; + } + if (this.isStepSkipped(index)) { + props.completed = false; + } + return ( + <Step key={label} {...props}> + <StepLabel {...labelProps}>{label}</StepLabel> + </Step> + ); + })} + </Stepper> + <Divider /> + <div> + {activeStep === steps.length ? ( + <div> + <Typography className={classes.instructions}> + All steps completed - you"re finished + </Typography> + <Button onClick={this.handleReset} className={classes.button}> + Reset + </Button> + </div> + ) : ( + <div> + <Typography className={classes.instructions}>{getStepContent(activeStep)}</Typography> + <div> + <Button + disabled={activeStep === 0} + onClick={this.handleBack} + className={classes.button} + > + Back + </Button> + {this.isStepOptional(activeStep) && ( + <Button + variant="contained" + color="primary" + onClick={this.handleSkip} + className={classes.button} + > + Skip + </Button> + )} + <Button + variant="contained" + color="primary" + onClick={this.handleNext} + className={classes.button} + > + {activeStep === steps.length - 1 ? 'Finish' : 'Next'} + </Button> + </div> + </div> + )} + </div> + </div> + ); + } +} + +export default withStyles(styles)(HorizontalLinear); diff --git a/front/odiparpack/app/containers/UiElements/demos/Steppers/HorizontalNonLinear.js b/front/odiparpack/app/containers/UiElements/demos/Steppers/HorizontalNonLinear.js new file mode 100644 index 0000000..3c72df3 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Steppers/HorizontalNonLinear.js @@ -0,0 +1,213 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; + +import { + Stepper, + Step, + StepButton, + Button, + Divider, + FormGroup, + FormControlLabel, + Switch, + Typography, +} from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '100%', + }, + button: { + marginRight: theme.spacing(1), + }, + completed: { + display: 'inline-block', + }, + instructions: { + marginTop: theme.spacing(1), + marginBottom: theme.spacing(1), + }, + stepItem: { + whiteSpace: 'inherit' + } +}); + +function getSteps() { + return ['Select campaign settings', 'Create an ad group', 'Create an ad']; +} + +function getStepContent(step) { + switch (step) { + case 0: + return 'Step 1: Select campaign settings...'; + case 1: + return 'Step 2: What is an ad group anyways?'; + case 2: + return 'Step 3: This is the bit I really care about!'; + default: + return 'Unknown step'; + } +} + +class HorizontalNonLinear extends React.Component { + state = { + activeStep: 0, + altLabel: false, + completed: {}, + }; + + completedSteps() { + return Object.keys(this.state.completed).length; + } + + totalSteps = () => ( + getSteps().length + ); + + isLastStep() { + return this.state.activeStep === this.totalSteps() - 1; + } + + allStepsCompleted() { + return this.completedSteps() === this.totalSteps(); + } + + handleNext = () => { + let activeStep; + + if (this.isLastStep() && !this.allStepsCompleted()) { + // It's the last step, but not all steps have been completed, + // find the first step that has been completed + const steps = getSteps(); + activeStep = steps.findIndex((step, i) => !(i in this.state.completed)); + } else { + activeStep = this.state.activeStep + 1; + } + this.setState({ + activeStep, + }); + }; + + handleBack = () => { + const { activeStep } = this.state; + this.setState({ + activeStep: activeStep - 1, + }); + }; + + handleStep = step => () => { + this.setState({ + activeStep: step, + }); + }; + + handleComplete = () => { + const { completed } = this.state; + completed[this.state.activeStep] = true; + this.setState({ + completed, + }); + this.handleNext(); + }; + + handleReset = () => { + this.setState({ + activeStep: 0, + completed: {}, + }); + }; + + handleChange = name => event => { + this.setState({ [name]: event.target.checked }); + }; + + render() { + const { classes } = this.props; + const steps = getSteps(); + const { activeStep, altLabel } = this.state; + + return ( + <div className={classes.root}> + <FormGroup row> + <FormControlLabel + control={( + <Switch + checked={altLabel} + onChange={this.handleChange('altLabel')} + value="altLabel" + /> + )} + label="Alternative Design" + /> + </FormGroup> + <Divider /> + <Stepper nonLinear activeStep={activeStep} alternativeLabel={altLabel}> + {steps.map((label, index) => ( + <Step key={label}> + <StepButton + className={classes.stepItem} + onClick={this.handleStep(index)} + completed={this.state.completed[index]} + > + {label} + </StepButton> + </Step> + ))} + </Stepper> + <Divider /> + <div> + {this.allStepsCompleted() ? ( + <div> + <Typography className={classes.instructions}> + All steps completed - you"re finished + </Typography> + <Button onClick={this.handleReset}>Reset</Button> + </div> + ) : ( + <div> + <Typography className={classes.instructions}>{getStepContent(activeStep)}</Typography> + <div> + <Button + disabled={activeStep === 0} + onClick={this.handleBack} + className={classes.button} + > + Back + </Button> + <Button + variant="contained" + color="primary" + onClick={this.handleNext} + className={classes.button} + > + Next + </Button> + {activeStep !== steps.length + && (this.state.completed[this.state.activeStep] ? ( + <Typography variant="caption" className={classes.completed}> + Step + {' '} + {activeStep + 1} + {' '} +already completed + </Typography> + ) : ( + <Button variant="contained" color="primary" onClick={this.handleComplete}> + {this.completedSteps() === this.totalSteps() - 1 ? 'Finish' : 'Complete Step'} + </Button> + ))} + </div> + </div> + )} + </div> + </div> + ); + } +} + +HorizontalNonLinear.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(HorizontalNonLinear); diff --git a/front/odiparpack/app/containers/UiElements/demos/Steppers/MobileSteppers.js b/front/odiparpack/app/containers/UiElements/demos/Steppers/MobileSteppers.js new file mode 100644 index 0000000..c9d7cf1 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Steppers/MobileSteppers.js @@ -0,0 +1,116 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft'; +import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight'; + +import { MobileStepper, Button, Grid, Typography } from '@material-ui/core'; + +const styles = theme => ({ + root: { + [theme.breakpoints.up('sm')]: { + width: 400, + margin: '0 auto' + }, + flexGrow: 1, + }, + title: { + textAlign: 'center', + margin: `${theme.spacing(4)}px 0 ${theme.spacing(2)}px`, + }, +}); + +class MobileSteppers extends React.Component { + state = { + activeStepDots: 0, + activeStepLine: 0, + }; + + handleNextDots = () => { + this.setState({ + activeStepDots: this.state.activeStepDots + 1, + }); + }; + + handleBackDots = () => { + this.setState({ + activeStepDots: this.state.activeStepDots - 1, + }); + }; + + handleNextLine = () => { + this.setState({ + activeStepLine: this.state.activeStepLine + 1, + }); + }; + + handleBackLine = () => { + this.setState({ + activeStepLine: this.state.activeStepLine - 1, + }); + }; + + render() { + const { classes, theme } = this.props; + + return ( + <Grid container spacing={2}> + <Grid item container justify="center" direction="column" md={6}> + <Typography variant="button" className={classes.title}> + Mobile Stepper - Dots + </Typography> + <MobileStepper + variant="dots" + steps={6} + position="static" + activeStep={this.state.activeStepDots} + className={classes.root} + nextButton={( + <Button size="small" onClick={this.handleNextDots} disabled={this.state.activeStepDots === 5}> + Next + {theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />} + </Button> + )} + backButton={( + <Button size="small" onClick={this.handleBackDots} disabled={this.state.activeStepDots === 0}> + {theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />} + Back + </Button> + )} + /> + </Grid> + <Grid item container justify="center" direction="column" md={6}> + <Typography variant="button" className={classes.title}> + Mobile Stepper - Progress + </Typography> + <MobileStepper + variant="progress" + steps={6} + position="static" + activeStep={this.state.activeStepLine} + className={classes.root} + nextButton={( + <Button size="small" onClick={this.handleNextLine} disabled={this.state.activeStepLine === 5}> + Next + {theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />} + </Button> + )} + backButton={( + <Button size="small" onClick={this.handleBackLine} disabled={this.state.activeStepLine === 0}> + {theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />} + Back + </Button> + )} + /> + </Grid> + </Grid> + ); + } +} + +MobileSteppers.propTypes = { + classes: PropTypes.object.isRequired, + theme: PropTypes.object.isRequired, +}; + +export default withStyles(styles, { withTheme: true })(MobileSteppers); diff --git a/front/odiparpack/app/containers/UiElements/demos/Steppers/StepperCarousel.js b/front/odiparpack/app/containers/UiElements/demos/Steppers/StepperCarousel.js new file mode 100644 index 0000000..0f127b3 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Steppers/StepperCarousel.js @@ -0,0 +1,187 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft'; +import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight'; +import SwipeableViews from 'react-swipeable-views'; +import imgApi from 'ba-api/images'; + +import { MobileStepper, Paper, Typography, Button, Grid } from '@material-ui/core'; + +const tutorialSteps = [ + { + label: 'How to be happy :)', + imgPath: imgApi[45], + }, + { + label: '1. Work with something that you like, like…', + imgPath: imgApi[33], + }, + { + label: '2. Keep your friends close to you and hangout with them', + imgPath: imgApi[14], + }, + { + label: '3. Travel everytime that you have a chance', + imgPath: imgApi[9], + }, + { + label: '4. And contribute to Material-UI :D', + imgPath: imgApi[44], + }, +]; + +const styles = theme => ({ + root: { + [theme.breakpoints.up('sm')]: { + width: 400, + }, + flexGrow: 1, + }, + header: { + textAlign: 'center', + height: 50, + paddingLeft: theme.spacing(4), + marginBottom: 20, + backgroundColor: theme.palette.background.default, + }, + img: { + height: 255, + maxWidth: 400, + overflow: 'hidden', + width: '100%', + margin: '0 auto' + }, + figure: { + maxWidth: 400, + overflow: 'hidden', + margin: '0 auto' + }, + mobileStepper: { + [theme.breakpoints.up('sm')]: { + width: 400, + }, + margin: '0 auto', + textAlign: 'center' + } +}); + +class StepperCarousel extends React.Component { + state = { + activeStep: 0, + activeStepSwipe: 0, + }; + + handleNext = () => { + this.setState(prevState => ({ + activeStep: prevState.activeStep + 1, + })); + }; + + handleBack = () => { + this.setState(prevState => ({ + activeStep: prevState.activeStep - 1, + })); + }; + + handleNextSwipe = () => { + this.setState(prevState => ({ + activeStepSwipe: prevState.activeStepSwipe + 1, + })); + }; + + handleBackSwipe = () => { + this.setState(prevState => ({ + activeStepSwipe: prevState.activeStepSwipe - 1, + })); + }; + + handleStepChangeSwipe = activeStepSwipe => { + this.setState({ activeStepSwipe }); + }; + + render() { + const { classes, theme } = this.props; + const { activeStep, activeStepSwipe } = this.state; + + const maxSteps = tutorialSteps.length; + const maxStepsSwipe = tutorialSteps.length; + + return ( + <Grid container spacing={2}> + <Grid item container justify="center" direction="column" md={6}> + <Paper square elevation={0} className={classes.header}> + <Typography>{tutorialSteps[activeStep].label}</Typography> + </Paper> + <img + className={classes.img} + src={tutorialSteps[activeStep].imgPath} + alt={tutorialSteps[activeStep].label} + /> + <MobileStepper + variant="text" + steps={maxSteps} + position="static" + activeStep={activeStep} + className={classes.mobileStepper} + nextButton={( + <Button size="small" onClick={this.handleNext} disabled={activeStep === maxSteps - 1}> + Next + {theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />} + </Button> + )} + backButton={( + <Button size="small" onClick={this.handleBack} disabled={activeStep === 0}> + {theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />} + Back + </Button> + )} + /> + </Grid> + <Grid item container justify="center" direction="column" md={6}> + <Paper square elevation={0} className={classes.header}> + <Typography>{tutorialSteps[activeStepSwipe].label}</Typography> + </Paper> + <SwipeableViews + axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'} + index={this.state.activeStepSwipe} + onChangeIndex={this.handleStepChangeSwipe} + enableMouseEvents + > + {tutorialSteps.map((step, index) => ( + <div className={classes.figure} key={index.toString()}> + <img key={step.label} className={classes.img} src={step.imgPath} alt={step.label} /> + </div> + ))} + </SwipeableViews> + <MobileStepper + variant="text" + steps={maxStepsSwipe} + position="static" + activeStep={activeStepSwipe} + className={classes.mobileStepper} + nextButton={( + <Button size="small" onClick={this.handleNextSwipe} disabled={activeStepSwipe === maxStepsSwipe - 1}> + Next + {theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />} + </Button> + )} + backButton={( + <Button size="small" onClick={this.handleBackSwipe} disabled={activeStepSwipe === 0}> + {theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />} + Back + </Button> + )} + /> + </Grid> + </Grid> + ); + } +} + +StepperCarousel.propTypes = { + classes: PropTypes.object.isRequired, + theme: PropTypes.object.isRequired, +}; + +export default withStyles(styles, { withTheme: true })(StepperCarousel); diff --git a/front/odiparpack/app/containers/UiElements/demos/Steppers/StepperError.js b/front/odiparpack/app/containers/UiElements/demos/Steppers/StepperError.js new file mode 100644 index 0000000..85630a1 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Steppers/StepperError.js @@ -0,0 +1,178 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Stepper, Step, StepLabel, Button, Typography } from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '100%', + }, + button: { + marginRight: theme.spacing(1), + }, + instructions: { + marginTop: theme.spacing(1), + marginBottom: theme.spacing(1), + }, +}); + +function getSteps() { + return ['Select campaign settings', 'Create an ad group', 'Create an ad']; +} + +function getStepContent(step) { + switch (step) { + case 0: + return 'Select campaign settings...'; + case 1: + return 'What is an ad group anyways?'; + case 2: + return 'This is the bit I really care about!'; + default: + return 'Unknown step'; + } +} + +class StepperError extends React.Component { + static propTypes = { + classes: PropTypes.object.isRequired, + }; + + state = { + activeStep: 0, + skipped: new Set(), + }; + + isStepOptional = step => ( + step === 1 + ); + + isStepSkipped(step) { + return this.state.skipped.has(step); + } + + isStepFailed = step => ( + step === 1 + ); + + handleNext = () => { + const { activeStep } = this.state; + let { skipped } = this.state; + if (this.isStepSkipped(activeStep)) { + skipped = new Set(skipped.values()); + skipped.delete(activeStep); + } + this.setState({ + activeStep: activeStep + 1, + skipped, + }); + }; + + handleBack = () => { + const { activeStep } = this.state; + this.setState({ + activeStep: activeStep - 1, + }); + }; + + handleSkip = () => { + const { activeStep } = this.state; + if (!this.isStepOptional(activeStep)) { + // You probably want to guard against something like this, + // it should never occur unless someone's actively trying to break something. + throw new Error("You can't skip a step that isn't optional."); + } + const skipped = new Set(this.state.skipped.values()); + skipped.add(activeStep); + this.setState({ + activeStep: this.state.activeStep + 1, + skipped, + }); + }; + + handleReset = () => { + this.setState({ + activeStep: 0, + }); + }; + + render() { + const { classes } = this.props; + const steps = getSteps(); + const { activeStep } = this.state; + + return ( + <div className={classes.root}> + <Stepper activeStep={activeStep}> + {steps.map((label, index) => { + const props = {}; + const labelProps = {}; + if (this.isStepOptional(index)) { + labelProps.optional = ( + <Typography variant="caption" color="error"> + Alert message + </Typography> + ); + } + if (this.isStepFailed(index)) { + labelProps.error = true; + } + if (this.isStepSkipped(index)) { + props.completed = false; + } + return ( + <Step key={label} {...props}> + <StepLabel {...labelProps}>{label}</StepLabel> + </Step> + ); + })} + </Stepper> + <div> + {activeStep === steps.length ? ( + <div> + <Typography className={classes.instructions}> + All steps completed - you"re finished + </Typography> + <Button onClick={this.handleReset} className={classes.button}> + Reset + </Button> + </div> + ) : ( + <div> + <Typography className={classes.instructions}>{getStepContent(activeStep)}</Typography> + <div> + <Button + disabled={activeStep === 0} + onClick={this.handleBack} + className={classes.button} + > + Back + </Button> + {this.isStepOptional(activeStep) && ( + <Button + variant="contained" + color="primary" + onClick={this.handleSkip} + className={classes.button} + > + Skip + </Button> + )} + <Button + variant="contained" + color="primary" + onClick={this.handleNext} + className={classes.button} + > + {activeStep === steps.length - 1 ? 'Finish' : 'Next'} + </Button> + </div> + </div> + )} + </div> + </div> + ); + } +} + +export default withStyles(styles)(StepperError); diff --git a/front/odiparpack/app/containers/UiElements/demos/Steppers/VerticalStepper.js b/front/odiparpack/app/containers/UiElements/demos/Steppers/VerticalStepper.js new file mode 100644 index 0000000..5a2314b --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Steppers/VerticalStepper.js @@ -0,0 +1,120 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Stepper, Step, StepLabel, StepContent, Button, Paper, Typography } from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: '90%', + }, + button: { + marginTop: theme.spacing(1), + marginRight: theme.spacing(1), + }, + actionsContainer: { + marginBottom: theme.spacing(2), + }, + resetContainer: { + padding: theme.spacing(3), + }, +}); + +function getSteps() { + return ['Select campaign settings', 'Create an ad group', 'Create an ad']; +} + +function getStepContent(step) { + switch (step) { + case 0: + return `For each ad campaign that you create, you can control how much + you're willing to spend on clicks and conversions, which networks + and geographical locations you want your ads to show on, and more.`; + case 1: + return 'An ad group contains one or more ads which target a shared set of keywords.'; + case 2: + return `Try out different ad text to see what brings in the most customers, + and learn how to enhance your ads using features like ad extensions. + If you run into any problems with your ads, find out how to tell if + they're running and how to resolve approval issues.`; + default: + return 'Unknown step'; + } +} + +class VerticalStepper extends React.Component { + state = { + activeStep: 0, + }; + + handleNext = () => { + this.setState({ + activeStep: this.state.activeStep + 1, + }); + }; + + handleBack = () => { + this.setState({ + activeStep: this.state.activeStep - 1, + }); + }; + + handleReset = () => { + this.setState({ + activeStep: 0, + }); + }; + + render() { + const { classes } = this.props; + const steps = getSteps(); + const { activeStep } = this.state; + + return ( + <div className={classes.root}> + <Stepper activeStep={activeStep} orientation="vertical"> + {steps.map((label, index) => ( + <Step key={label}> + <StepLabel>{label}</StepLabel> + <StepContent> + <Typography>{getStepContent(index)}</Typography> + <div className={classes.actionsContainer}> + <div> + <Button + disabled={activeStep === 0} + onClick={this.handleBack} + className={classes.button} + > + Back + </Button> + <Button + variant="contained" + color="primary" + onClick={this.handleNext} + className={classes.button} + > + {activeStep === steps.length - 1 ? 'Finish' : 'Next'} + </Button> + </div> + </div> + </StepContent> + </Step> + ))} + </Stepper> + {activeStep === steps.length && ( + <Paper square elevation={0} className={classes.resetContainer}> + <Typography>All steps completed - you"re finished</Typography> + <Button onClick={this.handleReset} className={classes.button}> + Reset + </Button> + </Paper> + )} + </div> + ); + } +} + +VerticalStepper.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(VerticalStepper); diff --git a/front/odiparpack/app/containers/UiElements/demos/Tabs/BottomNav.js b/front/odiparpack/app/containers/UiElements/demos/Tabs/BottomNav.js new file mode 100644 index 0000000..f3aa033 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Tabs/BottomNav.js @@ -0,0 +1,82 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import RestoreIcon from '@material-ui/icons/Restore'; +import FavoriteIcon from '@material-ui/icons/Favorite'; +import LocationOnIcon from '@material-ui/icons/LocationOn'; + +import { Typography, Grid, BottomNavigation, BottomNavigationAction, Icon } from '@material-ui/core'; + +const styles = theme => ({ + root: { + width: 'auto', + }, + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + } +}); + +class BottomNav extends React.Component { + state = { + value: 0, + value2: 'recents', + }; + + handleChange = (event, value) => { + this.setState({ value }); + }; + + handleChange2 = (event, value2) => { + this.setState({ value2 }); + }; + + + render() { + const { classes } = this.props; + const { value, value2 } = this.state; + + return ( + <Grid + container + alignItems="flex-start" + justify="flex-start" + direction="row" + spacing={2} + > + <Grid item md={6} xs={12}> + <Typography variant="button" className={classes.divider}>With Label</Typography> + <div> + <BottomNavigation + value={value} + onChange={this.handleChange} + showLabels + className={classes.root} + > + <BottomNavigationAction label="Recents" icon={<RestoreIcon />} /> + <BottomNavigationAction label="Favorites" icon={<FavoriteIcon />} /> + <BottomNavigationAction label="Nearby" icon={<LocationOnIcon />} /> + </BottomNavigation> + </div> + </Grid> + <Grid item md={6} xs={12}> + <Typography variant="button" className={classes.divider}>Without Label</Typography> + <div> + <BottomNavigation value={value2} onChange={this.handleChange2} className={classes.root}> + <BottomNavigationAction label="Recents" value="recents" icon={<RestoreIcon />} /> + <BottomNavigationAction label="Favorites" value="favorites" icon={<FavoriteIcon />} /> + <BottomNavigationAction label="Nearby" value="nearby" icon={<LocationOnIcon />} /> + <BottomNavigationAction label="Folder" value="folder" icon={<Icon>folder</Icon>} /> + </BottomNavigation> + </div> + </Grid> + </Grid> + ); + } +} + +BottomNav.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(BottomNav); diff --git a/front/odiparpack/app/containers/UiElements/demos/Tabs/CenteredTabs.js b/front/odiparpack/app/containers/UiElements/demos/Tabs/CenteredTabs.js new file mode 100644 index 0000000..00a285e --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Tabs/CenteredTabs.js @@ -0,0 +1,46 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Paper, Tabs, Tab } from '@material-ui/core'; + +const styles = { + root: { + flexGrow: 1, + }, +}; + +class CenteredTabs extends React.Component { + state = { + value: 0, + }; + + handleChange = (event, value) => { + this.setState({ value }); + }; + + render() { + const { classes } = this.props; + + return ( + <Paper className={classes.root}> + <Tabs + value={this.state.value} + onChange={this.handleChange} + indicatorColor="primary" + textColor="primary" + centered + > + <Tab label="Item One" /> + <Tab label="Item Two" /> + <Tab label="Item Three" /> + </Tabs> + </Paper> + ); + } +} + +CenteredTabs.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CenteredTabs); diff --git a/front/odiparpack/app/containers/UiElements/demos/Tabs/CustomTabs.js b/front/odiparpack/app/containers/UiElements/demos/Tabs/CustomTabs.js new file mode 100644 index 0000000..96fd136 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Tabs/CustomTabs.js @@ -0,0 +1,101 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Tabs, Tab, Typography } from '@material-ui/core'; + +const styles = theme => ({ + root: { + flexGrow: 1, + backgroundColor: theme.palette.background.paper, + }, + tabsRoot: { + borderBottom: '1px solid #e8e8e8', + }, + tabsIndicator: { + backgroundColor: '#1890ff', + }, + tabRoot: { + textTransform: 'initial', + minWidth: 64, + [theme.breakpoints.up('sm')]: { + minWidth: 72, + }, + fontWeight: theme.typography.fontWeightRegular, + marginRight: theme.spacing(4), + fontFamily: [ + '-apple-system', + 'BlinkMacSystemFont', + '"Segoe UI"', + 'Roboto', + '"Helvetica Neue"', + 'Arial', + 'sans-serif', + '"Apple Color Emoji"', + '"Segoe UI Emoji"', + '"Segoe UI Symbol"', + ].join(','), + '&:hover': { + color: '#40a9ff', + opacity: 1, + }, + '&$tabSelected': { + color: '#1890ff', + fontWeight: theme.typography.fontWeightMedium, + }, + '&:focus': { + color: '#40a9ff', + }, + }, + tabSelected: {}, + typography: { + padding: theme.spacing(3), + }, +}); + +class CustomTabs extends React.Component { + state = { + value: 0, + }; + + handleChange = (event, value) => { + this.setState({ value }); + }; + + render() { + const { classes } = this.props; + const { value } = this.state; + + return ( + <div className={classes.root}> + <Tabs + value={value} + onChange={this.handleChange} + classes={{ root: classes.tabsRoot, indicator: classes.tabsIndicator }} + > + <Tab + disableRipple + classes={{ root: classes.tabRoot, selected: classes.tabSelected }} + label="Tab 1" + /> + <Tab + disableRipple + classes={{ root: classes.tabRoot, selected: classes.tabSelected }} + label="Tab 2" + /> + <Tab + disableRipple + classes={{ root: classes.tabRoot, selected: classes.tabSelected }} + label="Tab 3" + /> + </Tabs> + <Typography className={classes.typography}>powered by Material-UI</Typography> + </div> + ); + } +} + +CustomTabs.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CustomTabs); diff --git a/front/odiparpack/app/containers/UiElements/demos/Tabs/DisabledTab.js b/front/odiparpack/app/containers/UiElements/demos/Tabs/DisabledTab.js new file mode 100644 index 0000000..17808a1 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Tabs/DisabledTab.js @@ -0,0 +1,31 @@ +import React from 'react'; +import { Paper, Tabs, Tab } from '@material-ui/core'; + +class DisabledTab extends React.Component { + state = { + value: 2, + }; + + handleChange = (event, value) => { + this.setState({ value }); + }; + + render() { + return ( + <Paper> + <Tabs + value={this.state.value} + indicatorColor="primary" + textColor="primary" + onChange={this.handleChange} + > + <Tab label="Active" /> + <Tab label="Disabled" disabled /> + <Tab label="Active" /> + </Tabs> + </Paper> + ); + } +} + +export default DisabledTab; diff --git a/front/odiparpack/app/containers/UiElements/demos/Tabs/FixedTabs.js b/front/odiparpack/app/containers/UiElements/demos/Tabs/FixedTabs.js new file mode 100644 index 0000000..f64240f --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Tabs/FixedTabs.js @@ -0,0 +1,80 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import SwipeableViews from 'react-swipeable-views'; +import { AppBar, Tabs, Tab, Typography } from '@material-ui/core'; + +function TabContainer({ children, dir }) { + return ( + <Typography component="div" dir={dir} style={{ padding: 8 * 3 }}> + {children} + </Typography> + ); +} + +TabContainer.propTypes = { + children: PropTypes.node.isRequired, + dir: PropTypes.string.isRequired, +}; + +const styles = theme => ({ + root: { + backgroundColor: theme.palette.background.paper, + [theme.breakpoints.up('sm')]: { + width: 500, + }, + margin: '10px auto' + }, +}); + +class FixedTabs extends React.Component { + state = { + value: 0, + }; + + handleChange = (event, value) => { + this.setState({ value }); + }; + + handleChangeIndex = index => { + this.setState({ value: index }); + }; + + render() { + const { classes, theme } = this.props; + + return ( + <div className={classes.root}> + <AppBar position="static" color="default"> + <Tabs + value={this.state.value} + onChange={this.handleChange} + indicatorColor="primary" + textColor="primary" + variant="fullWidth" + > + <Tab label="Item One" /> + <Tab label="Item Two" /> + <Tab label="Item Three" /> + </Tabs> + </AppBar> + <SwipeableViews + axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'} + index={this.state.value} + onChangeIndex={this.handleChangeIndex} + > + <TabContainer dir={theme.direction}>Item One</TabContainer> + <TabContainer dir={theme.direction}>Item Two</TabContainer> + <TabContainer dir={theme.direction}>Item Three</TabContainer> + </SwipeableViews> + </div> + ); + } +} + +FixedTabs.propTypes = { + classes: PropTypes.object.isRequired, + theme: PropTypes.object.isRequired, +}; + +export default withStyles(styles, { withTheme: true })(FixedTabs); diff --git a/front/odiparpack/app/containers/UiElements/demos/Tabs/IconTabs.js b/front/odiparpack/app/containers/UiElements/demos/Tabs/IconTabs.js new file mode 100644 index 0000000..ff97d16 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Tabs/IconTabs.js @@ -0,0 +1,61 @@ +import React from 'react'; +import PhoneIcon from '@material-ui/icons/Phone'; +import FavoriteIcon from '@material-ui/icons/Favorite'; +import PersonPinIcon from '@material-ui/icons/PersonPin'; +import { Paper, Grid, Tabs, Tab, Typography } from '@material-ui/core'; + +export default class IconTabs extends React.Component { + state = { + value: 0, + value2: 0, + }; + + handleChange = (event, value) => { + this.setState({ value }); + }; + + handleChange2 = (event, value2) => { + this.setState({ value2 }); + }; + + render() { + return ( + <div> + <Grid container spacing={3}> + <Grid item md={6} xs={12}> + <Typography variant="button" gutterBottom>Without Text</Typography> + <Paper> + <Tabs + value={this.state.value2} + onChange={this.handleChange2} + variant="fullWidth" + indicatorColor="primary" + textColor="primary" + > + <Tab icon={<PhoneIcon />} /> + <Tab icon={<FavoriteIcon />} /> + <Tab icon={<PersonPinIcon />} /> + </Tabs> + </Paper> + </Grid> + <Grid item md={6} xs={12}> + <Typography variant="button" gutterBottom>With Text</Typography> + <Paper> + <Tabs + value={this.state.value} + onChange={this.handleChange} + variant="fullWidth" + indicatorColor="secondary" + textColor="secondary" + > + <Tab icon={<PhoneIcon />} label="RECENTS" /> + <Tab icon={<FavoriteIcon />} label="FAVORITES" /> + <Tab icon={<PersonPinIcon />} label="NEARBY" /> + </Tabs> + </Paper> + </Grid> + </Grid> + </div> + ); + } +} diff --git a/front/odiparpack/app/containers/UiElements/demos/Tabs/LongTextTabs.js b/front/odiparpack/app/containers/UiElements/demos/Tabs/LongTextTabs.js new file mode 100644 index 0000000..724ac03 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Tabs/LongTextTabs.js @@ -0,0 +1,59 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { AppBar, Tabs, Tab, Typography } from '@material-ui/core'; + +function TabContainer(props) { + return ( + <Typography component="div" style={{ padding: 8 * 3 }}> + {props.children} + </Typography> + ); +} + +TabContainer.propTypes = { + children: PropTypes.node.isRequired, +}; + +const styles = theme => ({ + root: { + flexGrow: 1, + backgroundColor: theme.palette.background.paper, + }, +}); + +class LongTextTabs extends React.Component { + state = { + value: 'one', + }; + + handleChange = (event, value) => { + this.setState({ value }); + }; + + render() { + const { classes } = this.props; + const { value } = this.state; + + return ( + <div className={classes.root}> + <AppBar position="static"> + <Tabs value={value} onChange={this.handleChange}> + <Tab value="one" label="New Arrivals in the Longest Text of Nonfiction" /> + <Tab value="two" label="Item Two" /> + <Tab value="three" label="Item Three" /> + </Tabs> + </AppBar> + {value === 'one' && <TabContainer>Item One</TabContainer>} + {value === 'two' && <TabContainer>Item Two</TabContainer>} + {value === 'three' && <TabContainer>Item Three</TabContainer>} + </div> + ); + } +} + +LongTextTabs.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(LongTextTabs); diff --git a/front/odiparpack/app/containers/UiElements/demos/Tabs/ScrollIconTabs.js b/front/odiparpack/app/containers/UiElements/demos/Tabs/ScrollIconTabs.js new file mode 100644 index 0000000..98f52d2 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Tabs/ScrollIconTabs.js @@ -0,0 +1,82 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import PhoneIcon from '@material-ui/icons/Phone'; +import FavoriteIcon from '@material-ui/icons/Favorite'; +import PersonPinIcon from '@material-ui/icons/PersonPin'; +import HelpIcon from '@material-ui/icons/Help'; +import ShoppingBasket from '@material-ui/icons/ShoppingBasket'; +import ThumbDown from '@material-ui/icons/ThumbDown'; +import ThumbUp from '@material-ui/icons/ThumbUp'; +import { AppBar, Tabs, Tab, Typography } from '@material-ui/core'; + +function TabContainer(props) { + return ( + <Typography component="div" style={{ padding: 8 * 3 }}> + {props.children} + </Typography> + ); +} + +TabContainer.propTypes = { + children: PropTypes.node.isRequired, +}; + +const styles = theme => ({ + root: { + flexGrow: 1, + width: '100%', + backgroundColor: theme.palette.background.paper, + }, +}); + +class ScrollIconTabs extends React.Component { + state = { + value: 0, + }; + + handleChange = (event, value) => { + this.setState({ value }); + }; + + render() { + const { classes } = this.props; + const { value } = this.state; + + return ( + <div className={classes.root}> + <AppBar position="static" color="default"> + <Tabs + value={value} + onChange={this.handleChange} + variant="scrollable" + scrollButtons="on" + indicatorColor="primary" + textColor="secondary" + > + <Tab label="Item One" icon={<PhoneIcon />} /> + <Tab label="Item Two" icon={<FavoriteIcon />} /> + <Tab label="Item Three" icon={<PersonPinIcon />} /> + <Tab label="Item Four" icon={<HelpIcon />} /> + <Tab label="Item Five" icon={<ShoppingBasket />} /> + <Tab label="Item Six" icon={<ThumbDown />} /> + <Tab label="Item Seven" icon={<ThumbUp />} /> + </Tabs> + </AppBar> + {value === 0 && <TabContainer>Item One</TabContainer>} + {value === 1 && <TabContainer>Item Two</TabContainer>} + {value === 2 && <TabContainer>Item Three</TabContainer>} + {value === 3 && <TabContainer>Item Four</TabContainer>} + {value === 4 && <TabContainer>Item Five</TabContainer>} + {value === 5 && <TabContainer>Item Six</TabContainer>} + {value === 6 && <TabContainer>Item Seven</TabContainer>} + </div> + ); + } +} + +ScrollIconTabs.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ScrollIconTabs); diff --git a/front/odiparpack/app/containers/UiElements/demos/Tabs/ScrollTabs.js b/front/odiparpack/app/containers/UiElements/demos/Tabs/ScrollTabs.js new file mode 100644 index 0000000..f7f9477 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Tabs/ScrollTabs.js @@ -0,0 +1,75 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { AppBar, Tabs, Tab, Typography } from '@material-ui/core'; + +function TabContainer(props) { + return ( + <Typography component="div" style={{ padding: 8 * 3 }}> + {props.children} + </Typography> + ); +} + +TabContainer.propTypes = { + children: PropTypes.node.isRequired, +}; + +const styles = theme => ({ + root: { + flexGrow: 1, + width: '100%', + backgroundColor: theme.palette.background.paper, + }, +}); + +class ScrollTabs extends React.Component { + state = { + value: 0, + }; + + handleChange = (event, value) => { + this.setState({ value }); + }; + + render() { + const { classes } = this.props; + const { value } = this.state; + + return ( + <div className={classes.root}> + <AppBar position="static" color="default"> + <Tabs + value={value} + onChange={this.handleChange} + indicatorColor="secondary" + textColor="secondary" + variant="scrollable" + scrollButtons="auto" + > + <Tab label="Item One" /> + <Tab label="Item Two" /> + <Tab label="Item Three" /> + <Tab label="Item Four" /> + <Tab label="Item Five" /> + <Tab label="Item Six" /> + <Tab label="Item Seven" /> + </Tabs> + </AppBar> + {value === 0 && <TabContainer>Item One</TabContainer>} + {value === 1 && <TabContainer>Item Two</TabContainer>} + {value === 2 && <TabContainer>Item Three</TabContainer>} + {value === 3 && <TabContainer>Item Four</TabContainer>} + {value === 4 && <TabContainer>Item Five</TabContainer>} + {value === 5 && <TabContainer>Item Six</TabContainer>} + {value === 6 && <TabContainer>Item Seven</TabContainer>} + </div> + ); + } +} + +ScrollTabs.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ScrollTabs); diff --git a/front/odiparpack/app/containers/UiElements/demos/Tabs/SimpleTabs.js b/front/odiparpack/app/containers/UiElements/demos/Tabs/SimpleTabs.js new file mode 100644 index 0000000..fd791cc --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Tabs/SimpleTabs.js @@ -0,0 +1,59 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { AppBar, Tabs, Tab, Typography } from '@material-ui/core'; + +function TabContainer(props) { + return ( + <Typography component="div" style={{ padding: 8 * 3 }}> + {props.children} + </Typography> + ); +} + +TabContainer.propTypes = { + children: PropTypes.node.isRequired, +}; + +const styles = theme => ({ + root: { + flexGrow: 1, + backgroundColor: theme.palette.background.paper, + }, +}); + +class SimpleTabs extends React.Component { + state = { + value: 0, + }; + + handleChange = (event, value) => { + this.setState({ value }); + }; + + render() { + const { classes } = this.props; + const { value } = this.state; + + return ( + <div className={classes.root}> + <AppBar position="static"> + <Tabs value={value} onChange={this.handleChange}> + <Tab label="Item One" /> + <Tab label="Item Two" /> + <Tab label="Item Three" href="#basic-tabs" /> + </Tabs> + </AppBar> + {value === 0 && <TabContainer>Item One</TabContainer>} + {value === 1 && <TabContainer>Item Two</TabContainer>} + {value === 2 && <TabContainer>Item Three</TabContainer>} + </div> + ); + } +} + +SimpleTabs.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(SimpleTabs); diff --git a/front/odiparpack/app/containers/UiElements/demos/Tags/ArrayTags.js b/front/odiparpack/app/containers/UiElements/demos/Tags/ArrayTags.js new file mode 100644 index 0000000..921a668 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Tags/ArrayTags.js @@ -0,0 +1,78 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import TagFacesIcon from '@material-ui/icons/TagFaces'; + +import { Avatar, Chip, Paper } from '@material-ui/core'; + +const styles = theme => ({ + root: { + display: 'flex', + justifyContent: 'center', + flexWrap: 'wrap', + padding: theme.spacing(0.5), + }, + chip: { + margin: theme.spacing(0.5), + }, +}); + +class ArrayTags extends React.Component { + state = { + chipData: [ + { key: 0, label: 'Angular' }, + { key: 1, label: 'jQuery' }, + { key: 2, label: 'Polymer' }, + { key: 3, label: 'React' }, + { key: 4, label: 'Vue.js' }, + ], + }; + + handleDelete = data => () => { + if (data.label === 'React') { + alert('Why would you want to delete React?! :)'); // eslint-disable-line no-alert + return; + } + + const chipData = [...this.state.chipData]; + const chipToDelete = chipData.indexOf(data); + chipData.splice(chipToDelete, 1); + this.setState({ chipData }); + }; + + render() { + const { classes } = this.props; + + return ( + <Paper className={classes.root}> + {this.state.chipData.map(data => { + let avatar = null; + + if (data.label === 'React') { + avatar = ( + <Avatar> + <TagFacesIcon className={classes.svgIcon} /> + </Avatar> + ); + } + + return ( + <Chip + key={data.key} + avatar={avatar} + label={data.label} + onDelete={this.handleDelete(data)} + className={classes.chip} + /> + ); + })} + </Paper> + ); + } +} + +ArrayTags.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ArrayTags); diff --git a/front/odiparpack/app/containers/UiElements/demos/Tags/BasicTags.js b/front/odiparpack/app/containers/UiElements/demos/Tags/BasicTags.js new file mode 100644 index 0000000..f2e3693 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Tags/BasicTags.js @@ -0,0 +1,72 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import FaceIcon from '@material-ui/icons/Face'; +import DoneIcon from '@material-ui/icons/Done'; + +import { Avatar, Chip } from '@material-ui/core'; + +const styles = theme => ({ + root: { + display: 'flex', + justifyContent: 'center', + flexWrap: 'wrap', + }, + chip: { + margin: theme.spacing(1), + }, +}); + +function handleDelete() { + alert('You clicked the delete icon.'); // eslint-disable-line no-alert +} + +function handleClick() { + alert('You clicked the Chip.'); // eslint-disable-line no-alert +} + +function BasicTags(props) { + const { classes } = props; + return ( + <div className={classes.root}> + <Chip label="Basic Tag" className={classes.chip} color="primary" /> + <Chip + avatar={<Avatar>MB</Avatar>} + label="Clickable Tag" + onClick={handleClick} + className={classes.chip} + color="primary" + /> + <Chip + avatar={<Avatar src="/images/pp_girl.svg" />} + label="Deletable Tag" + onDelete={handleDelete} + className={classes.chip} + /> + <Chip + avatar={( + <Avatar> + <FaceIcon /> + </Avatar> + )} + label="Clickable Deletable Tag" + onClick={handleClick} + onDelete={handleDelete} + className={classes.chip} + /> + <Chip + label="Custom delete icon Tag" + onClick={handleClick} + onDelete={handleDelete} + className={classes.chip} + deleteIcon={<DoneIcon />} + /> + </div> + ); +} + +BasicTags.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(BasicTags); diff --git a/front/odiparpack/app/containers/UiElements/demos/Typography/AlignTypo.js b/front/odiparpack/app/containers/UiElements/demos/Typography/AlignTypo.js new file mode 100644 index 0000000..38088a9 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Typography/AlignTypo.js @@ -0,0 +1,46 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Type from 'ba-styles/Typography.scss'; +import { Typography, Divider } from '@material-ui/core'; + +const styles = theme => ({ + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + } +}); + +class AlignTypo extends React.Component { + render() { + const { classes } = this.props; + return ( + <div> + <Typography variant="subtitle1" className={Type.textCenter} gutterBottom> + Align center: Nullam in + <span className={Type.bold}> tortor </span> + <span className={Type.italic}> ligula </span> + </Typography> + <Divider className={classes.divider} /> + <Typography variant="h5" className={Type.textLeft} gutterBottom> + Align Left + </Typography> + <Typography variant="h5" className={Type.textRight} gutterBottom> + Align Right + </Typography> + <Divider className={classes.divider} /> + <Typography variant="h4" className={Type.medium} gutterBottom>Justify Align</Typography> + <Typography gutterBottom className={Type.textJustify}> + Vestibulum faucibus eget erat eget pretium. Donec commodo convallis ligula, eget suscipit orci. Suspendisse potenti. + Nulla eget lobortis lacus. Aliquam venenatis magna et odio lobortis maximus. Nullam in tortor ligula. Proin maximus risus nunc, eu aliquam nibh tempus a. Interdum et malesuada fames ac ante ipsum primis in faucibus. + </Typography> + </div> + ); + } +} + +AlignTypo.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(AlignTypo); diff --git a/front/odiparpack/app/containers/UiElements/demos/Typography/ColouredTypo.js b/front/odiparpack/app/containers/UiElements/demos/Typography/ColouredTypo.js new file mode 100644 index 0000000..b7b1516 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Typography/ColouredTypo.js @@ -0,0 +1,35 @@ +import React from 'react'; +import Type from 'ba-styles/Typography.scss'; +import { Typography } from '@material-ui/core'; + +class ColouredTypo extends React.Component { + render() { + return ( + <div> + <Typography variant="h5" className={Type.textInfo} gutterBottom> + Ut sed eros finibus + </Typography> + <Typography variant="body1" className={Type.textSuccess} gutterBottom> + Nulla eget lobortis lacus. Aliquam venenatis magna et odio lobortis maximus. + </Typography> + <Typography variant="h5" className={Type.textWarning} gutterBottom> + Aliquam nec ex aliquet + </Typography> + <Typography variant="h6" className={Type.textError} gutterBottom> + Aenean facilisis vitae purus facilisis semper + </Typography> + <Typography className={Type.textGreyDark} gutterBottom> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse sed urna in justo euismod condimentum. + </Typography> + <Typography className={Type.textGrey} gutterBottom> + Vivamus et luctus mauris. Maecenas nisl libero, tincidunt id odio id, feugiat vulputate quam. + </Typography> + <Typography className={Type.textGreyLight} gutterBottom> + Curabitur egestas consequat lorem, vel fermentum augue porta id. + </Typography> + </div> + ); + } +} + +export default ColouredTypo; diff --git a/front/odiparpack/app/containers/UiElements/demos/Typography/GeneralTypo.js b/front/odiparpack/app/containers/UiElements/demos/Typography/GeneralTypo.js new file mode 100644 index 0000000..6a1079f --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Typography/GeneralTypo.js @@ -0,0 +1,139 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { withStyles } from '@material-ui/core/styles'; +import Type from 'ba-styles/Typography.scss'; +import { Typography, Divider } from '@material-ui/core'; + +const background = { + width: '100%', + maxWidth: 500, + background: + 'transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAICAYAAAA4GpVBAAAMKGlDQ1BJQ0MgUHJvZmlsZQAASMetl3dUU8kex+eWJCQktEAoUkJvovQqNbQIAlIFGyEJJJQYEoKKHRUVWAsqFqzoqoiiawFkURELtkWx9wUVFGVdLNhQeZME0PW998c75805c+dzfvc3v/n+7p2ZewcA9RiOWJyNagCQI8qTxIYFMSckpzBJHYACMKAFrIANhysVB8bERAJYhtp/lve3ACJvrzvIY4H/rWjy+FIuAEgM5DSelJsD+QgAuDtXLMkDgNAD7ebT88SQiVAl0JZAgZAt5JyhZE85pyk5UuETH8uCnAqACpXDkWQAoCbXxcznZsA4aqWQHUU8oQhyI2Q/roDDg/wF8sicnGmQ1W0g26T9ECfjHzHThmNyOBnDrMxFUVSChVJxNmcm+H+XnGzZ0BjmsFIFkvBYec7y55Y1LULOVMjnRWlR0ZC1IN8Q8hT+cu4SyMITBv0/cqUs+MwAAwCUyuMER0A2hGwmy0oIHGQ/jkTRF/qjKQWC+CRlfFQkmRY7GB8tEGVHRQ7GKRXw2UNcyZeGxA35pAtD2ZDhO0TrhXns+MGY5/OFiVGQ1SA/kGbFRQz2fVEgYEUNjyWLlWuG7xwDOdKhXDCLdElorNIfcxcI2VGD9sg8QXy4si82hctRaNCDnMmXTogc0sPjB4co9WCFfFHCoE6sTJwXFDvov1OcHTPojzXys8PkdjPIrdL8uKG+vXlwsilzwUEmZ2yMclxcW5wXE6/UhjNBJGCBYMAEMljTwDSQCYStPXU9YOhOKOAACcgAfOAwaBnqkaS4I4LXOFAA/oLEB9LhfkGKu3yQD+1fh63KqwNIV9zNV/TIAl2Qc3AD3A/3wSPhNQBWZ9wT9xrqx1QfGpUYQgwmhhNDibZThYWSn+IyARdmkA2rBETAlg+zkmsQDWn/HofQRWgjPCbcJLQT7oJE8BT6Cf8tw+/RhMO2caAdRg0dzC7tx+xwK6jaDQ/CfaF+qB1n4AbAAXeFmQTi/jA3N2j9/tT+k3bZkGqyIxkl65IDyDY/+6nZqbkN95Hn9qNOpa604UxYw3d+Ho31Q2482Eb87IktxQ5jLdgp7ALWiNUBJnYSq8cuY8flPDw3nirmxtBosQo9WTCOcMjHsdqx2/HLT2NzBseXKN4/yOPPyJMvHNY08UyJMEOQxwyEuzWfyRZxR41kOjs6eQAg3/uVW8tbhmJPRxgXv9sK3wHgyxsYGGj8bouEa/LIYgAoXd9t1ifgctYF4HwJVybJV9pw+YUAvyrqcKXoA2O4d9nAjJyBO/ABASAEjAXRIB4kgynwOQtADlQ9HcwGC0ARKAErwVqwEWwFO8AesB8cAnWgEZwC58AlcBXcBPfhXOkEL0EveA/6EQQhITSEjugjJoglYo84I56IHxKCRCKxSDKSimQgIkSGzEYWIiVIGbIR2Y5UIb8hx5BTyAWkDbmLdCDdyBvkM4qhVFQbNUKt0NGoJxqIRqDx6GQ0A81FC9BF6HJ0PVqJ7kNr0VPoJfQm2o6+RPswgKliDMwUc8A8MRYWjaVg6ZgEm4sVY+VYJVaDNcA3fR1rx3qwTzgRp+NM3AHO13A8AefiufhcvBTfiO/Ba/Ez+HW8A+/FvxFoBEOCPcGbwCZMIGQQphOKCOWEXYSjhLNwTXUS3hOJRAbRmugB12oyMZM4i1hK3Ew8QGwithGfEPtIJJI+yZ7kS4omcUh5pCLSBtI+0knSNVIn6aOKqoqJirNKqEqKikilUKVcZa/KCZVrKs9U+skaZEuyNzmazCPPJK8g7yQ3kK+QO8n9FE2KNcWXEk/JpCygrKfUUM5SHlDeqqqqmql6qY5XFarOV12velD1vGqH6ieqFtWOyqJOosqoy6m7qU3Uu9S3NBrNihZAS6Hl0ZbTqminaY9oH9XoaqPU2Go8tXlqFWq1atfUXqmT1S3VA9WnqBeol6sfVr+i3qNB1rDSYGlwNOZqVGgc07it0adJ13TSjNbM0SzV3Kt5QfO5FknLSitEi6e1SGuH1mmtJ3SMbk5n0bn0hfSd9LP0Tm2itrU2WztTu0R7v3ardq+Olo6rTqLODJ0KneM67QyMYcVgM7IZKxiHGLcYn3WNdAN1+brLdGt0r+l+0BuhF6DH1yvWO6B3U++zPlM/RD9Lf5V+nf5DA9zAzmC8wXSDLQZnDXpGaI/wGcEdUTzi0Ih7hqihnWGs4SzDHYaXDfuMjI3CjMRGG4xOG/UYM4wDjDON1xifMO42oZv4mQhN1picNHnB1GEGMrOZ65lnmL2mhqbhpjLT7aatpv1m1mYJZoVmB8wemlPMPc3TzdeYN5v3WphYjLOYbVFtcc+SbOlpKbBcZ9li+cHK2irJaolVndVzaz1rtnWBdbX1Axuajb9Nrk2lzQ1boq2nbZbtZturdqidm53ArsLuij1q724vtN9s3zaSMNJrpGhk5cjbDlSHQId8h2qHjlGMUZGjCkfVjXo12mJ0yuhVo1tGf3N0c8x23Ol430nLaaxToVOD0xtnO2euc4XzDReaS6jLPJd6l9eu9q581y2ud9zobuPclrg1u31193CXuNe4d3tYeKR6bPK47antGeNZ6nnei+AV5DXPq9Hrk7e7d573Ie+/fRx8snz2+jwfYz2GP2bnmCe+Zr4c3+2+7X5Mv1S/bX7t/qb+HP9K/8cB5gG8gF0BzwJtAzMD9wW+CnIMkgQdDfrA8mbNYTUFY8FhwcXBrSFaIQkhG0MehZqFZoRWh/aGuYXNCmsKJ4RHhK8Kv802YnPZVezesR5j54w9E0GNiIvYGPE40i5SEtkwDh03dtzqcQ+iLKNEUXXRIJodvTr6YYx1TG7M7+OJ42PGV4zvinWKnR3bEkePmxq3N+59fFD8ivj7CTYJsoTmRPXESYlViR+SgpPKktonjJ4wZ8KlZINkYXJ9CiklMWVXSt/EkIlrJ3ZOcptUNOnWZOvJMyZfmGIwJXvK8anqUzlTD6cSUpNS96Z+4URzKjl9aey0TWm9XBZ3HfclL4C3htfN9+WX8Z+l+6aXpT/P8M1YndEt8BeUC3qELOFG4evM8MytmR+yorN2Zw1kJ2UfyFHJSc05JtISZYnOTDOeNmNam9heXCRuz/XOXZvbK4mQ7JIi0snS+jxt+JN9WWYjWyzryPfLr8j/OD1x+uEZmjNEMy7PtJu5bOazgtCCX2fhs7izmmebzl4wu2NO4Jztc5G5aXOb55nPWzSvc37Y/D0LKAuyFvxR6FhYVvhuYdLChkVGi+YverI4bHF1kVqRpOj2Ep8lW5fiS4VLW5e5LNuw7Fsxr/hiiWNJecmXUm7pxV+cfln/y8Dy9OWtK9xXbFlJXClaeWuV/6o9ZZplBWVPVo9bXbuGuaZ4zbu1U9deKHct37qOsk62rn195Pr6DRYbVm74slGw8WZFUMWBTYablm36sJm3+dqWgC01W422lmz9vE247c72sO21lVaV5TuIO/J3dO1M3Nnyq+evVbsMdpXs+rpbtLt9T+yeM1UeVVV7DfeuqEarZdXd+ybtu7o/eH99jUPN9gOMAyUHwUHZwRe/pf5261DEoebDnodrjlge2XSUfrS4FqmdWdtbJ6hrr0+ubzs29lhzg0/D0d9H/b670bSx4rjO8RUnKCcWnRg4WXCyr0nc1HMq49ST5qnN909POH3jzPgzrWcjzp4/F3rudEtgy8nzvucbL3hfOHbR82LdJfdLtZfdLh/9w+2Po63urbVXPK7UX/W62tA2pu3ENf9rp64HXz93g33j0s2om223Em7duT3pdvsd3p3nd7Pvvr6Xf6///vwHhAfFDzUelj8yfFT5p+2fB9rd2493BHdcfhz3+P4T7pOXT6VPv3Qu6qJ1lT8zeVb13Pl5Y3do99UXE190vhS/7O8p+kvzr02vbF4d+Tvg78u9E3o7X0teD7wpfav/dvc713fNfTF9j97nvO//UPxR/+OeT56fWj4nfX7WP/0L6cv6r7ZfG75FfHswkDMwIOZIOIpfAQxWND0dgDe7AaAlA0C/Cv8fJirPZoqCKM+TCgL/jZXnN0VxB6AGNvLfcFYTAAdhtZoPYwcAIP8djw8AqIvLcB0s0nQXZ2UsKjzhED4ODLw1AoDUAMBXycBA/+aBga87odi7ADTlKs+E8iI/g25zldM1Rvb2n89m/wJHBnEU5n9PrQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAAd0SU1FB+EKBRI2IMFyTLcAAAAaSURBVAjXY/h++dF/hl+/fv1nYmBgYMBJAAAKEQellAoQBwAAAABJRU5ErkJggg==)', // eslint-disable-line max-len +}; + +const styles = theme => ({ + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + } +}); + +class GeneralTypo extends React.Component { + render() { + const { classes } = this.props; + return ( + <div style={background}> + <Typography variant="h4" gutterBottom> + Lorem ipsum dolor + {' '} + <span className={Type.bold}>sit amet</span> + </Typography> + <Typography variant="subtitle1" gutterBottom> + Duis tristique metus magna, lobortis aliquam risus euismod sit amet. + </Typography> + <Typography variant="subtitle1" gutterBottom> + Nullam in + <span className={Type.bold}> tortor </span> + <span className={Type.italic}> ligula </span> + </Typography> + <Divider className={classes.divider} /> + <Typography variant="h5" gutterBottom> + <span className={Type.bolder}>Bolder</span> + + <span className={Type.bold}>Bold</span> + + <span className={Type.medium}>Medium</span> + + <br /> + <span className={Type.regular}>Regular</span> + + <span className={Type.light}>Light</span> + + <span className={Type.lighter}>Lighter</span> + + </Typography> + <Typography gutterBottom> + Duis tristique metus magna, lobortis aliquam risus euismod sit amet. Suspendisse porttitor velit nisl, feugiat tincidunt nisl mattis ut. Nulla lobortis nunc vitae nisi semper semper. + {' '} + <br /> + Nulla eget lobortis lacus. Aliquam venenatis magna et odio lobortis maximus. Nullam in tortor ligula. Proin maximus risus nunc, eu aliquam nibh tempus a. Interdum et malesuada fames ac ante ipsum primis in faucibus. + </Typography> + <Divider className={classes.divider} /> + <Typography variant="h4" className={classNames(Type.light, Type.uppercase)} gutterBottom> + Vestibulum nec mi + {' '} + <span className={Type.bolder}>suscipit</span> + </Typography> + <Typography variant="body1" className={Type.lowercase} gutterBottom> + <span className={Type.bolder}>Bolder</span> + + <span className={Type.bold}>Bold</span> + + <span className={Type.medium}>Medium</span> + + <span className={Type.regular}>Regular</span> + + <span className={Type.light}>Light</span> + + <span className={Type.lighter}>Lighter</span> + + </Typography> + <Typography gutterBottom className={Type.uppercase}> + Vestibulum faucibus eget erat eget pretium. Donec commodo convallis ligula, eget suscipit orci. Suspendisse potenti. + <br /> + Curabitur egestas consequat lorem, vel fermentum augue porta id. Aliquam lobortis magna neque, gravida consequat velit venenatis at. Duis sed augue leo. Phasellus ante massa, aliquam non ante at, suscipit ornare ipsum. Quisque a consequat ante, at volutpat enim. + </Typography> + <Divider className={classes.divider} /> + <Typography variant="h6" className={Type.lighter} gutterBottom> + <span className={Type.bold}>Numbers</span> + {' '} +looks great! 1234 + <span className={classNames(Type.bold, Type.italic, Type.underline)}>56</span> +7890 + </Typography> + <Typography variant="h6" className={Type.regular} gutterBottom> + Let your Creativity Flow + {' '} + <span className={Type.bold}> +123 + <sup>$</sup> + </span> + </Typography> + <Typography variant="h6" className={Type.Bold} gutterBottom> + Numerics + {' '} + <span className={Type.light}> +123 + <sup className={Type.italic}>00</sup> + </span> + </Typography> + <Divider className={classes.divider} /> + <Typography variant="body1" gutterBottom> + Body 2 + </Typography> + <Typography variant="body1" gutterBottom align="right"> + Body 1 + </Typography> + <Typography variant="caption" gutterBottom align="center"> + Caption + </Typography> + <Typography gutterBottom noWrap> + {` + Lorem ipsum dolor sit amet, consectetur adipisicing elit, + sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + `} + </Typography> + <Typography variant="button" gutterBottom> + Button + </Typography> + </div> + ); + } +} + +GeneralTypo.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(GeneralTypo); diff --git a/front/odiparpack/app/containers/UiElements/demos/Typography/Heading.js b/front/odiparpack/app/containers/UiElements/demos/Typography/Heading.js new file mode 100644 index 0000000..cf97396 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Typography/Heading.js @@ -0,0 +1,56 @@ +import React from 'react'; +import { Typography } from '@material-ui/core'; + +class Heading extends React.Component { + render() { + return ( + <div> + {/* <Typography variant="h1" gutterBottom> + Disp 4 + </Typography> + <Typography variant="h2" gutterBottom> + Display 3 + </Typography> + <Typography variant="h3" gutterBottom> + Display 2 + </Typography> + <Typography variant="h4" gutterBottom> + Display 1 + </Typography> + <Typography variant="h5" gutterBottom> + Headline + </Typography> + <Typography variant="h6" gutterBottom> + Title + </Typography> + <Typography variant="subtitle1" gutterBottom> + Subheading + </Typography> + <Typography gutterBottom noWrap> + {` + Lorem ipsum dolor sit amet, consectetur adipisicing elit, + sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + `} + </Typography> */} + <Typography variant="h4"> + {`h4 Título`} + </Typography> + + <Typography variant="h5"> + {`h5 Subtítulos`} + </Typography> + + <Typography variant="h6"> + {`h6 Título de tablas`} + </Typography> + + <Typography> + {` Cuerpo de texto `} + </Typography> + + </div> + ); + } +} + +export default Heading; diff --git a/front/odiparpack/app/containers/UiElements/demos/Typography/ListTypo.js b/front/odiparpack/app/containers/UiElements/demos/Typography/ListTypo.js new file mode 100644 index 0000000..2f76ff1 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Typography/ListTypo.js @@ -0,0 +1,71 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Type from 'ba-styles/Typography.scss'; +import { Typography, Divider } from '@material-ui/core'; + +const styles = theme => ({ + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + } +}); + +class ListTypo extends React.Component { + render() { + const { classes } = this.props; + return ( + <div> + <Typography variant="button" className={classes.divider}>Unordered List</Typography> + <div> + <ul className={Type.list}> + <li>Lorem ipsum dolor sit amet</li> + <li>Consectetur adipiscing elit</li> + <li>Integer molestie lorem at massa</li> + <li>Facilisis in pretium nisl aliquet</li> + <li> +Nulla volutpat aliquam velit + <ul> + <li>Phasellus iaculis neque</li> + <li>Purus sodales ultricies</li> + <li>Vestibulum laoreet porttitor sem</li> + <li>Ac tristique libero volutpat at</li> + </ul> + </li> + <li>Faucibus porta lacus fringilla vel</li> + <li>Aenean sit amet erat nunc</li> + <li>Eget porttitor lorem</li> + </ul> + </div> + <Divider className={classes.divider} /> + <Typography variant="button" className={classes.divider}>Ordered List</Typography> + <div> + <ul className={Type.orderedlist}> + <li>Lorem ipsum dolor sit amet</li> + <li>Consectetur adipiscing elit</li> + <li>Integer molestie lorem at massa</li> + <li>Facilisis in pretium nisl aliquet</li> + <li> +Nulla volutpat aliquam velit + <ul> + <li>Phasellus iaculis neque</li> + <li>Purus sodales ultricies</li> + <li>Vestibulum laoreet porttitor sem</li> + <li>Ac tristique libero volutpat at</li> + </ul> + </li> + <li>Faucibus porta lacus fringilla vel</li> + <li>Aenean sit amet erat nunc</li> + <li>Eget porttitor lorem</li> + </ul> + </div> + </div> + ); + } +} + +ListTypo.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(ListTypo); diff --git a/front/odiparpack/app/containers/UiElements/demos/Typography/QuotesDemo.js b/front/odiparpack/app/containers/UiElements/demos/Typography/QuotesDemo.js new file mode 100644 index 0000000..afd9197 --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/Typography/QuotesDemo.js @@ -0,0 +1,33 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import { Quote } from 'ba-components'; + +const styles = theme => ({ + divider: { + display: 'block', + margin: `${theme.spacing(3)}px 0`, + } +}); + +class QuotesDemo extends React.Component { + render() { + const { classes } = this.props; + return ( + <div> + <div className={classes.divider}> + <Quote align="left" content="Imagine all the people living life in peace. You may say I'm a dreamer, but I'm not the only one. I hope someday you'll join us, and the world will be as one." footnote="John Lennon" /> + </div> + <div className={classes.divider}> + <Quote align="right" content="A lot of people are afraid to say what they want. That's why they don't get what they want." footnote="Madonna" /> + </div> + </div> + ); + } +} + +QuotesDemo.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(QuotesDemo); diff --git a/front/odiparpack/app/containers/UiElements/demos/index.js b/front/odiparpack/app/containers/UiElements/demos/index.js new file mode 100644 index 0000000..311d50e --- /dev/null +++ b/front/odiparpack/app/containers/UiElements/demos/index.js @@ -0,0 +1,117 @@ +// Badges +export CommonBadges from './Badges/CommonBadges'; +export VariantBadges from './Badges/VariantBadges'; +// Avatars +export AvatarsDemo from './Avatars/AvatarsDemo'; +export AvatarsPractice from './Avatars/AvatarsPractice'; +// Accordion +export SimpleAccordion from './Accordion/SimpleAccordion'; +export AdvancedAccordion from './Accordion/AdvancedAccordion'; +export ControlledAccordion from './Accordion/ControlledAccordion'; +// List +export ListBasic from './List/ListBasic'; +export ListControl from './List/ListControl'; +export ListMenu from './List/ListMenu'; +export ListInteractive from './List/ListInteractive'; +export PinnedList from './List/PinnedList'; +// Popover & Tooltip +export SimpleTooltips from './PopoverTooltip/SimpleTooltips'; +export PositionedTooltips from './PopoverTooltip/PositionedTooltips'; +export SimplePopover from './PopoverTooltip/SimplePopover'; +export PopoverPlayground from './PopoverTooltip/PopoverPlayground'; +export DelayTooltips from './PopoverTooltip/DelayTooltips'; +export TransitionsTooltips from './PopoverTooltip/TransitionsTooltips'; +export TriggersTooltips from './PopoverTooltip/TriggersTooltips'; +export CustomizedTooltips from './PopoverTooltip/CustomizedTooltips'; +// Notification +export SimpleNotif from './Notification/SimpleNotif'; +export StyledNotif from './Notification/StyledNotif'; +export TransitionNotif from './Notification/TransitionNotif'; +export MobileNotif from './Notification/MobileNotif'; +// Typography +export GeneralTypo from './Typography/GeneralTypo'; +export Heading from './Typography/Heading'; +export ListTypo from './Typography/ListTypo'; +export ColouredTypo from './Typography/ColouredTypo'; +export QuotesDemo from './Typography/QuotesDemo'; +export AlignTypo from './Typography/AlignTypo'; +// Tabs +export SimpleTabs from './Tabs/SimpleTabs'; +export LongTextTabs from './Tabs/LongTextTabs'; +export FixedTabs from './Tabs/FixedTabs'; +export CenteredTabs from './Tabs/CenteredTabs'; +export IconTabs from './Tabs/IconTabs'; +export ScrollTabs from './Tabs/ScrollTabs'; +export ScrollIconTabs from './Tabs/ScrollIconTabs'; +export DisabledTab from './Tabs/DisabledTab'; +export CustomTabs from './Tabs/CustomTabs'; +export BottomNav from './Tabs/BottomNav'; +// Card +export StandardCards from './Cards/StandardCards'; +export ControlCards from './Cards/ControlCards'; +export PaperSheet from './Cards/PaperSheet'; +export SocialCards from './Cards/SocialCards'; +export EcommerceCards from './Cards/EcommerceCards'; +// Image Grid +export ImageGridList from './ImageGrid/ImageGridList'; +export TitlebarGridList from './ImageGrid/TitlebarGridList'; +export AdvancedGridList from './ImageGrid/AdvancedGridList'; +export SingleLineGridList from './ImageGrid/SingleLineGridList'; +// Progress +export CircularIndeterminate from './Progress/CircularIndeterminate'; +export CircularIntegration from './Progress/CircularIntegration'; +export CircularDeterminate from './Progress/CircularDeterminate'; +export CircularStatic from './Progress/CircularStatic'; +export LinearIndeterminate from './Progress/LinearIndeterminate'; +export LinearDeterminate from './Progress/LinearDeterminate'; +export LinearBuffer from './Progress/LinearBuffer'; +export LinearQuery from './Progress/LinearQuery'; +export LinearStatic from './Progress/LinearStatic'; +export ProgressDelay from './Progress/ProgressDelay'; +// Dialog +export ModalDemo from './DialogModal/ModalDemo'; +export AlertDialog from './DialogModal/AlertDialog'; +export SelectDialog from './DialogModal/SelectDialog'; +export SelectRadioDialog from './DialogModal/SelectRadioDialog'; +export FormDialog from './DialogModal/FormDialog'; +export FullScreenDialog from './DialogModal/FullScreenDialog'; +export ImagePopup from './DialogModal/ImagePopup'; +export ScrollDialog from './DialogModal/ScrollDialog'; +// Steppers +export HorizontalLinear from './Steppers/HorizontalLinear'; +export HorizontalNonLinear from './Steppers/HorizontalNonLinear'; +export StepperError from './Steppers/StepperError'; +export VerticalStepper from './Steppers/VerticalStepper'; +export MobileSteppers from './Steppers/MobileSteppers'; +export StepperCarousel from './Steppers/StepperCarousel'; +// Menu and Drawer +export TemporaryDrawer from './DrawerMenu/TemporaryDrawer'; +export PermanentDrawer from './DrawerMenu/PermanentDrawer'; +export PersistentDrawer from './DrawerMenu/PersistentDrawer'; +export SwipeDrawer from './DrawerMenu/SwipeDrawer'; +export MiniDrawer from './DrawerMenu/MiniDrawer'; +export BasicMenu from './DrawerMenu/BasicMenu'; +export DropdownMenu from './DrawerMenu/DropdownMenu'; +export MenuTransition from './DrawerMenu/MenuTransition'; +export StyledMenu from './DrawerMenu/StyledMenu'; +// Pagination +export TbPagination from './Pagination/TbPagination'; +export TbPaginationCustom from './Pagination/TbPaginationCustom'; +export GeneralPagination from './Pagination/GeneralPagination'; +// Breadcrumb +export ClassicBreadcrumbs from './Breadcrumbs/ClassicBreadcrumbs'; +export PaperBreadcrumbs from './Breadcrumbs/PaperBreadcrumbs'; +// Slider Carousel +export SingleCarousel from './SliderCaraousel/SingleCarousel'; +export MultipleCarousel from './SliderCaraousel/MultipleCarousel'; +export AutoplayCarousel from './SliderCaraousel/AutoplayCarousel'; +export ThumbnailCarousel from './SliderCaraousel/ThumbnailCarousel'; +export VerticalCarousel from './SliderCaraousel/VerticalCarousel'; +export CustomCarousel from './SliderCaraousel/CustomCarousel'; +export AnimatedSlider from './SliderCaraousel/AnimatedSlider'; +// Tags +export BasicTags from './Tags/BasicTags'; +export ArrayTags from './Tags/ArrayTags'; +// Dividers +export CommonDividers from './Dividers/CommonDividers'; +export SpecialDividers from './Dividers/SpecialDividers'; diff --git a/front/odiparpack/app/containers/pageListAsync.js b/front/odiparpack/app/containers/pageListAsync.js new file mode 100644 index 0000000..feff750 --- /dev/null +++ b/front/odiparpack/app/containers/pageListAsync.js @@ -0,0 +1,324 @@ +import Loadable from 'react-loadable'; +import Loading from 'ba-components/Loading'; + +// Dashboard +export const DashboardV1 = Loadable({ + loader: () => import('./Dashboard/Dashboard'), + loading: Loading, +}); +export const DashboardV2 = Loadable({ + loader: () => import('./Dashboard/DashboardV2'), + loading: Loading, +}); + +// Layouts +export const AppLayout = Loadable({ + loader: () => import('./Layouts/AppLayout'), + loading: Loading, +}); +export const Responsive = Loadable({ + loader: () => import('./Layouts/Responsive'), + loading: Loading, +}); +export const Grid = Loadable({ + loader: () => import('./Layouts/Grid'), + loading: Loading, +}); + +// Tables +export const SimpleTable = Loadable({ + loader: () => import('./Tables/BasicTable'), + loading: Loading, +}); +export const AdvancedTable = Loadable({ + loader: () => import('./Tables/AdvancedTable'), + loading: Loading, +}); +export const TreeTable = Loadable({ + loader: () => import('./Tables/TreeTable'), + loading: Loading, +}); +export const CrudTable = Loadable({ + loader: () => import('./Tables/CrudTable'), + loading: Loading, +}); +export const TablePlayground = Loadable({ + loader: () => import('./Tables/TablePlayground'), + loading: Loading, +}); + +// Forms +export const ReduxForm = Loadable({ + loader: () => import('./Forms/ReduxForm'), + loading: Loading, +}); +export const DateTimePicker = Loadable({ + loader: () => import('./Forms/DateTimePicker'), + loading: Loading, +}); +export const CheckboxRadio = Loadable({ + loader: () => import('./Forms/CheckboxRadio'), + loading: Loading, +}); +export const Switches = Loadable({ + loader: () => import('./Forms/Switches'), + loading: Loading, +}); +export const Selectbox = Loadable({ + loader: () => import('./Forms/Selectbox'), + loading: Loading, +}); +export const Rating = Loadable({ + loader: () => import('./Forms/Rating'), + loading: Loading, +}); +export const SliderRange = Loadable({ + loader: () => import('./Forms/SliderRange'), + loading: Loading, +}); +export const Buttons = Loadable({ + loader: () => import('./Forms/Buttons'), + loading: Loading, +}); +export const Textbox = Loadable({ + loader: () => import('./Forms/Textbox'), + loading: Loading, +}); +export const Autocomplete = Loadable({ + loader: () => import('./Forms/Autocomplete'), + loading: Loading, +}); +export const TextEditor = Loadable({ + loader: () => import('./Forms/TextEditor'), + loading: Loading, +}); +export const Upload = Loadable({ + loader: () => import('./Forms/Upload'), + loading: Loading, +}); + +// UI Components +export const Badges = Loadable({ + loader: () => import('./UiElements/Badges'), + loading: Loading, +}); +export const Avatars = Loadable({ + loader: () => import('./UiElements/Avatars'), + loading: Loading, +}); +export const Accordion = Loadable({ + loader: () => import('./UiElements/Accordion'), + loading: Loading, +}); +export const List = Loadable({ + loader: () => import('./UiElements/List'), + loading: Loading, +}); +export const PopoverTooltip = Loadable({ + loader: () => import('./UiElements/PopoverTooltip'), + loading: Loading, +}); +export const Notification = Loadable({ + loader: () => import('./UiElements/Notification'), + loading: Loading, +}); +export const Typography = Loadable({ + loader: () => import('./UiElements/Typography'), + loading: Loading, +}); +export const Tabs = Loadable({ + loader: () => import('./UiElements/Tabs'), + loading: Loading, +}); +export const Cards = Loadable({ + loader: () => import('./UiElements/Cards'), + loading: Loading, +}); +export const ImageGrid = Loadable({ + loader: () => import('./UiElements/ImageGrid'), + loading: Loading, +}); +export const Progress = Loadable({ + loader: () => import('./UiElements/Progress'), + loading: Loading, +}); +export const DialogModal = Loadable({ + loader: () => import('./UiElements/DialogModal'), + loading: Loading, +}); +export const Steppers = Loadable({ + loader: () => import('./UiElements/Steppers'), + loading: Loading, +}); +export const DrawerMenu = Loadable({ + loader: () => import('./UiElements/DrawerMenu'), + loading: Loading, +}); +export const Paginations = Loadable({ + loader: () => import('./UiElements/Paginations'), + loading: Loading, +}); +export const Breadcrumbs = Loadable({ + loader: () => import('./UiElements/Breadcrumbs'), + loading: Loading, +}); +export const Icons = Loadable({ + loader: () => import('./UiElements/Icons'), + loading: Loading, +}); +export const SliderCarousel = Loadable({ + loader: () => import('./UiElements/SliderCarousel'), + loading: Loading, +}); +export const Tags = Loadable({ + loader: () => import('./UiElements/Tags'), + loading: Loading, +}); +export const Dividers = Loadable({ + loader: () => import('./UiElements/Dividers'), + loading: Loading, +}); + +// Chart +export const LineCharts = Loadable({ + loader: () => import('./Charts/LineCharts'), + loading: Loading, +}); +export const BarCharts = Loadable({ + loader: () => import('./Charts/BarCharts'), + loading: Loading, +}); +export const AreaCharts = Loadable({ + loader: () => import('./Charts/AreaCharts'), + loading: Loading, +}); +export const PieCharts = Loadable({ + loader: () => import('./Charts/PieCharts'), + loading: Loading, +}); +export const RadarCharts = Loadable({ + loader: () => import('./Charts/RadarCharts'), + loading: Loading, +}); +export const ScatterCharts = Loadable({ + loader: () => import('./Charts/ScatterCharts'), + loading: Loading, +}); +export const CompossedCharts = Loadable({ + loader: () => import('./Charts/CompossedCharts'), + loading: Loading, +}); +export const ResponsiveCharts = Loadable({ + loader: () => import('./Charts/ResponsiveCharts'), + loading: Loading, +}); + +// Pages +export const Login = Loadable({ + loader: () => import('./Pages/Users/Login'), + loading: Loading, +}); +export const Register = Loadable({ + loader: () => import('./Pages/Users/Register'), + loading: Loading, +}); +export const Profile = Loadable({ + loader: () => import('./Pages/UserProfile'), + loading: Loading, +}); +export const SocialMedia = Loadable({ + loader: () => import('./Pages/SocialMedia'), + loading: Loading, +}); +export const BlankPage = Loadable({ + loader: () => import('./Pages/BlankPage'), + loading: Loading, +}); +export const Ecommerce = Loadable({ + loader: () => import('./Pages/Ecommerce'), + loading: Loading, +}); +export const Contact = Loadable({ + loader: () => import('./Pages/Contact'), + loading: Loading, +}); +export const ResetPassword = Loadable({ + loader: () => import('./Pages/Users/ResetPassword'), + loading: Loading, +}); +export const LockScreen = Loadable({ + loader: () => import('./Pages/Users/LockScreen'), + loading: Loading, +}); +export const Chat = Loadable({ + loader: () => import('./Pages/Chat'), + loading: Loading, +}); +export const Email = Loadable({ + loader: () => import('./Pages/Email'), + loading: Loading, +}); +export const Photos = Loadable({ + loader: () => import('./Pages/Photos'), + loading: Loading, +}); +export const Calendar = Loadable({ + loader: () => import('./Pages/Calendar'), + loading: Loading, +}); +export const LoginDedicated = Loadable({ + loader: () => import('./Pages/Standalone/LoginDedicated'), + loading: Loading, +}); + +// Maps +export const MapMarker = Loadable({ + loader: () => import('./Maps/MapMarker'), + loading: Loading, +}); +export const MapDirection = Loadable({ + loader: () => import('./Maps/MapDirection'), + loading: Loading, +}); +export const SearchMap = Loadable({ + loader: () => import('./Maps/SearchMap'), + loading: Loading, +}); +export const TrafficIndicator = Loadable({ + loader: () => import('./Maps/TrafficIndicator'), + loading: Loading, +}); +export const StreetViewMap = Loadable({ + loader: () => import('./Maps/StreetViewMap'), + loading: Loading, +}); + +// Other +export const NotFound = Loadable({ + loader: () => import('./NotFound/NotFound'), + loading: Loading, +}); +export const NotFoundDedicated = Loadable({ + loader: () => import('./Pages/Standalone/NotFoundDedicated'), + loading: Loading, +}); +export const Error = Loadable({ + loader: () => import('./Pages/Error'), + loading: Loading, +}); +export const Maintenance = Loadable({ + loader: () => import('./Pages/Maintenance'), + loading: Loading, +}); +export const Parent = Loadable({ + loader: () => import('./Parent'), + loading: Loading, +}); +export const Settings = Loadable({ + loader: () => import('./Pages/Settings'), + loading: Loading, +}); +export const HelpSupport = Loadable({ + loader: () => import('./Pages/HelpSupport'), + loading: Loading, +}); diff --git a/front/odiparpack/app/i18n.js b/front/odiparpack/app/i18n.js new file mode 100644 index 0000000..cac434f --- /dev/null +++ b/front/odiparpack/app/i18n.js @@ -0,0 +1,46 @@ +/** + * i18n.js + * + * This will setup the i18n language files and locale data for your app. + * + * IMPORTANT: This file is used by the internal build + * script `extract-intl`, and must use CommonJS module syntax + * You CANNOT use import/export in this file. + */ +const addLocaleData = require('react-intl').addLocaleData; //eslint-disable-line +const enLocaleData = require('react-intl/locale-data/en'); + +const enTranslationMessages = require('./translations/en.json'); + +addLocaleData(enLocaleData); + +const DEFAULT_LOCALE = 'en'; + +// prettier-ignore +const appLocales = [ + 'en', +]; + +const formatTranslationMessages = (locale, messages) => { + const defaultFormattedMessages = + locale !== DEFAULT_LOCALE + ? formatTranslationMessages(DEFAULT_LOCALE, enTranslationMessages) + : {}; + const flattenFormattedMessages = (formattedMessages, key) => { + const formattedMessage = + !messages[key] && locale !== DEFAULT_LOCALE + ? defaultFormattedMessages[key] + : messages[key]; + return Object.assign(formattedMessages, { [key]: formattedMessage }); + }; + return Object.keys(messages).reduce(flattenFormattedMessages, {}); +}; + +const translationMessages = { + en: formatTranslationMessages('en', enTranslationMessages), +}; + +exports.appLocales = appLocales; +exports.formatTranslationMessages = formatTranslationMessages; +exports.translationMessages = translationMessages; +exports.DEFAULT_LOCALE = DEFAULT_LOCALE; diff --git a/front/odiparpack/app/index.html b/front/odiparpack/app/index.html new file mode 100644 index 0000000..74ddd5a --- /dev/null +++ b/front/odiparpack/app/index.html @@ -0,0 +1,58 @@ +<!doctype html> +<html lang="en"> + <head> + <!-- The first thing in any HTML file should be the charset --> + <meta charset="utf-8"> + <!-- Make the page mobile compatible --> + <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1" /> + <meta name="mobile-web-app-capable" content="yes"> + <title>Boss Ultimate - React Admin Template Material Design</title> + <!-- Favicon --> + <link rel="shortcut icon" href="/favicons/favicon.ico" /> + <link rel="apple-touch-icon" sizes="57x57" href="/favicons/apple-icon-57x57.png" /> + <link rel="apple-touch-icon" sizes="60x60" href="/favicons/apple-icon-60x60.png" /> + <link rel="apple-touch-icon" sizes="72x72" href="/favicons/apple-icon-72x72.png" /> + <link rel="apple-touch-icon" sizes="76x76" href="/favicons/apple-icon-76x76.png" /> + <link rel="apple-touch-icon" sizes="114x114" href="/favicons/apple-icon-114x114.png" /> + <link rel="apple-touch-icon" sizes="120x120" href="/favicons/apple-icon-120x120.png" /> + <link rel="apple-touch-icon" sizes="144x144" href="/favicons/apple-icon-144x144.png" /> + <link rel="apple-touch-icon" sizes="152x152" href="/favicons/apple-icon-152x152.png" /> + <link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-icon-180x180.png" /> + <link rel="icon" type="image/png" sizes="192x192" href="/favicons/android-icon-192x192.png" /> + <link rel="icon" type="image/png" sizes="32x32" href="/favicons/favicon-32x32.png" /> + <link rel="icon" type="image/png" sizes="96x96" href="/favicons/favicon-96x96.png" /> + <link rel="icon" type="image/png" sizes="16x16" href="/favicons/favicon-16x16.png" /> + <link rel="manifest" href="/favicons/manifest.json" /> + <meta name="msapplication-TileColor" content="#ffffff" /> + <meta name="msapplication-TileImage" content="/favicons/ms-icon-144x144.png" /> + <meta name="theme-color" content="#512ea8" /> + <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet" /> + <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" /> + <!-- Facebook --> + <meta property="author" content="boss" /> + <meta property="og:site_name" content="boss.ux-maestro.com" /> + <meta property="og:locale" content="en_US" /> + <meta property="og:type" content="website" /> + <meta property="og:image" content="/logo.jpg" /> + <!-- Twitter --> + <meta property="twitter:site" content="boss.ux-maestro.com" /> + <meta property="twitter:domain" content="boss.ux-maestro.com" /> + <meta property="twitter:creator" content="boss" /> + <meta property="twitter:card" content="summary" /> + <meta property="twitter:image:src" content="/logo.jpg" /> + <style> + #app { position: absolute;top:0;left: 0;background: #fafafa;width: 100%;min-height: 100%;z-index: 1000; display: flex;} + .preloader-init { position: absolute; top: calc(50% - 100px); left: calc(50% - 100px) } + </style> + </head> + <body> + <!-- Display a message if JS has been disabled on the browser. --> + <noscript>If you're seeing this message, that means <strong>JavaScript has been disabled on your browser</strong>, please <strong>enable JS</strong> to make this app work.</noscript> + + <!-- The app hooks into this div --> + <div id="app"> + <img src="/images/spinner.gif" class="preloader-init" alt="spinner" /> + </div> + <!-- A lot of magic happens in this file. HtmlWebpackPlugin automatically includes all assets (e.g. bundle.js, main.css) with the correct HTML tags, which is why they are missing in this HTML file. Don't add any assets here! (Check out webpackconfig.js if you want to know more) --> + </body> +</html> diff --git a/front/odiparpack/app/redux/.DS_Store b/front/odiparpack/app/redux/.DS_Store Binary files differnew file mode 100644 index 0000000..bef93f9 --- /dev/null +++ b/front/odiparpack/app/redux/.DS_Store diff --git a/front/odiparpack/app/redux/configureStore.js b/front/odiparpack/app/redux/configureStore.js new file mode 100644 index 0000000..49a6b7b --- /dev/null +++ b/front/odiparpack/app/redux/configureStore.js @@ -0,0 +1,53 @@ +/** + * Create the store with dynamic reducers + */ + +import { createStore, applyMiddleware, compose } from 'redux'; +import { fromJS } from 'immutable'; +import { routerMiddleware } from 'connected-react-router/immutable'; +import createSagaMiddleware from 'redux-saga'; +import createReducer from './reducers'; + +const sagaMiddleware = createSagaMiddleware(); + +export default function configureStore(initialState = {}, history) { + // Create the store with two middlewares + // 1. sagaMiddleware: Makes redux-sagas work + // 2. routerMiddleware: Syncs the location/URL path to the state + const middlewares = [sagaMiddleware, routerMiddleware(history)]; + + const enhancers = [applyMiddleware(...middlewares)]; + + // If Redux DevTools Extension is installed use it, otherwise use Redux compose + /* eslint-disable no-underscore-dangle, indent */ + const composeEnhancers = process.env.NODE_ENV !== 'production' + && typeof window === 'object' + && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ + ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ + // TODO Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading + // Prevent recomputing reducers for `replaceReducer` + shouldHotReload: false, + }) + : compose; + /* eslint-enable */ + const store = createStore( + createReducer(), + fromJS(initialState), + composeEnhancers(...enhancers), + ); + + // Extensions + store.runSaga = sagaMiddleware.run; + store.injectedReducers = {}; // Reducer registry + store.injectedSagas = {}; // Saga registry + + // Make reducers hot reloadable, see http://mxs.is/googmo + /* istanbul ignore next */ + if (module.hot) { + module.hot.accept('./reducers', () => { + store.replaceReducer(createReducer(store.injectedReducers)); + }); + } + + return store; +} diff --git a/front/odiparpack/app/redux/helpers/dateTimeHelper.js b/front/odiparpack/app/redux/helpers/dateTimeHelper.js new file mode 100644 index 0000000..e91c981 --- /dev/null +++ b/front/odiparpack/app/redux/helpers/dateTimeHelper.js @@ -0,0 +1,35 @@ +export function getDate() { + let today = new Date(); + let dd = today.getDate(); + const monthNames = [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ]; + const mm = monthNames[today.getMonth()]; // January is 0! + const yyyy = today.getFullYear(); + + if (dd < 10) { + dd = '0' + dd; + } + + today = mm + ', ' + dd + ' ' + yyyy; + + return today; +} + +export function getTime() { + let now = new Date(); + let h = now.getHours(); + let m = now.getMinutes(); + + if (h < 10) { + h = '0' + h; + } + + if (m < 10) { + m = '0' + m; + } + + now = h + ':' + m; + return now; +} diff --git a/front/odiparpack/app/redux/modules/calendar.js b/front/odiparpack/app/redux/modules/calendar.js new file mode 100644 index 0000000..fb1291d --- /dev/null +++ b/front/odiparpack/app/redux/modules/calendar.js @@ -0,0 +1,79 @@ +import { fromJS, List, Map } from 'immutable'; +import notif from 'ba-api/notifMessage'; +import { + FETCH_CALENDAR_DATA, + ADD_EVENT, + DISCARD_EVENT, + SUBMIT_EVENT, + DELETE_EVENT, + CLOSE_NOTIF +} from 'ba-actions/actionTypes'; + +const initialState = { + events: List([]), + openFrm: false, + formValues: Map(), + notifMsg: '', +}; + +const initForm = Map({ + title: '', + start: new Date(), + end: new Date(), + hexColor: 'F8BBD0', +}); + +const initialImmutableState = fromJS(initialState); +export default function reducer(state = initialImmutableState, action = {}) { + switch (action.type) { + case FETCH_CALENDAR_DATA: + return state.withMutations((mutableState) => { + const items = fromJS(action.items); + mutableState.set('events', items); + }); + case ADD_EVENT: + return state.withMutations((mutableState) => { + mutableState + .set('openFrm', true) + .set('formValues', initForm); + }); + case DISCARD_EVENT: + return state.withMutations((mutableState) => { + mutableState + .set('openFrm', false) + .set('formValues', Map()) + .set('notifMsg', notif.discard); + }); + case SUBMIT_EVENT: + return state.withMutations((mutableState) => { + const initItem = Map(action.newEvent); + const id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36); + const newItem = initItem + .update('id', (val = id) => val) + .set('start', action.newEvent.get('start')._d || action.newEvent.get('start')) + .set('end', action.newEvent.get('end')._d || action.newEvent.get('end')); + mutableState.update('events', events => events.push(newItem)); + mutableState + .set('formValues', Map()) + .set('openFrm', false) + .set('notifMsg', notif.saved); + }); + case DELETE_EVENT: + return state.withMutations((mutableState) => { + const eventItem = state.get('events') + .find(obj => ( + obj.get('id') === action.event.id + )); + const index = state.get('events').indexOf(eventItem); + mutableState + .update('events', events => events.splice(index, 1)) + .set('notifMsg', notif.removed); + }); + case CLOSE_NOTIF: + return state.withMutations((mutableState) => { + mutableState.set('notifMsg', ''); + }); + default: + return state; + } +} diff --git a/front/odiparpack/app/redux/modules/chat.js b/front/odiparpack/app/redux/modules/chat.js new file mode 100644 index 0000000..e30c97a --- /dev/null +++ b/front/odiparpack/app/redux/modules/chat.js @@ -0,0 +1,73 @@ +import { fromJS, List, Map } from 'immutable'; +import { + FETCH_CHAT_DATA, + SHOW_CHAT, + HIDE_CHAT, + SEND_CHAT, + DELETE_CONVERSATION +} from 'ba-actions/actionTypes'; +import { getDate, getTime } from '../helpers/dateTimeHelper'; + +const initialState = { + chatList: List([]), + activeChat: List([]), + chatSelected: 0, + showMobileDetail: false +}; + +const buildMessage = (message, curData) => { + const id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36); + const newData = Map({ + id, + from: 'me', + date: getDate(), + time: getTime(), + message, + }); + return curData.push(newData); +}; + +const initialImmutableState = fromJS(initialState); +export default function reducer(state = initialImmutableState, action = {}) { + switch (action.type) { + case FETCH_CHAT_DATA: + return state.withMutations((mutableState) => { + const items = fromJS(action.items); + mutableState + .set('chatList', items) + .set('activeChat', items.getIn([state.get('chatSelected'), 'chat'])); + }); + case SHOW_CHAT: + return state.withMutations((mutableState) => { + const chatItem = state.get('chatList') + .find(obj => ( + obj.get('with') === action.person.get('id') + )); + const index = state.get('chatList').indexOf(chatItem); + const chatValue = chatItem.get('chat') !== [] ? chatItem.get('chat') : List([]); + mutableState + .set('chatSelected', index) + .set('activeChat', chatValue) + .set('showMobileDetail', true); + }); + case HIDE_CHAT: + return state.withMutations((mutableState) => { + mutableState.set('showMobileDetail', false); + }); + case SEND_CHAT: + return state.withMutations((mutableState) => { + const newMessage = buildMessage(action.message, state.getIn(['chatList', state.get('chatSelected'), 'chat'])); + mutableState + .update('chatList', chatList => chatList.setIn([state.get('chatSelected'), 'chat'], newMessage)) + .set('activeChat', newMessage); + }); + case DELETE_CONVERSATION: + return state.withMutations((mutableState) => { + mutableState + .update('chatList', chatList => chatList.setIn([state.get('chatSelected'), 'chat'], List([]))) + .set('activeChat', List([])); + }); + default: + return state; + } +} diff --git a/front/odiparpack/app/redux/modules/contact.js b/front/odiparpack/app/redux/modules/contact.js new file mode 100644 index 0000000..bf34a8b --- /dev/null +++ b/front/odiparpack/app/redux/modules/contact.js @@ -0,0 +1,131 @@ +import { fromJS, List, Map } from 'immutable'; +import notif from 'ba-api/notifMessage'; +import { + FETCH_CONTACT_DATA, + SEARCH_CONTACT, + SHOW_DETAIL_CONTACT, + HIDE_DETAIL, + EDIT_CONTACT, + SUBMIT_CONTACT, + DELETE_CONTACT, + TOGGLE_FAVORITE, + ADD_CONTACT, + CLOSE_CONTACT_FORM, + CLOSE_NOTIF +} from 'ba-actions/actionTypes'; + +const initialState = { + contactList: List([]), + formValues: Map(), + selectedIndex: 0, + selectedId: '', + keywordValue: '', + avatarInit: '', + openFrm: false, + showMobileDetail: false, + notifMsg: '', +}; +let editingIndex = 0; + +const initialImmutableState = fromJS(initialState); + +export default function reducer(state = initialImmutableState, action = {}) { + switch (action.type) { + case FETCH_CONTACT_DATA: + return state.withMutations((mutableState) => { + const items = fromJS(action.items); + mutableState.set('contactList', items); + }); + case SEARCH_CONTACT: + return state.withMutations((mutableState) => { + action.keyword.persist(); + const keyword = action.keyword.target.value.toLowerCase(); + mutableState.set('keywordValue', keyword); + }); + case ADD_CONTACT: + return state.withMutations((mutableState) => { + mutableState + .set('openFrm', true) + .set('formValues', Map()) + .set('avatarInit', ''); + }); + case CLOSE_CONTACT_FORM: + return state.withMutations((mutableState) => { + mutableState + .set('openFrm', false) + .set('formValues', Map()) + .set('avatarInit', '') + .set('notifMsg', notif.discard); + }); + case EDIT_CONTACT: + return state.withMutations((mutableState) => { + editingIndex = state.get('contactList').indexOf(action.item); + mutableState + .set('openFrm', true) + .set('selectedId', action.item.get('id')) + .set('formValues', action.item) + .set('avatarInit', action.item.get('avatar')); + }); + case SUBMIT_CONTACT: + return state.withMutations((mutableState) => { + const initItem = Map(action.newData); + if (state.get('selectedId') === action.newData.get('id')) { + // Update data + const avatar = action.avatar !== '' ? action.avatar : state.get('avatarInit'); + const newItem = initItem.update((initUpdated) => (initUpdated.set('avatar', avatar))); + mutableState + .update('contactList', contactList => contactList.setIn( + [editingIndex], newItem + )) + .set('notifMsg', notif.updated); + } else { + // Insert data + const avatar = action.avatar !== '' ? action.avatar : '/images/pp_boy.svg'; + const id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36); + const newItem = initItem + .update('id', (val = id) => val) + .update('avatar', (val = avatar) => val) + .update('favorited', (val = false) => val); + mutableState + .update('contactList', contactList => contactList.unshift(newItem)) + .set('selectedIndex', 0) + .set('notifMsg', notif.saved); + } + mutableState + .set('formValues', null) + .set('avatarInit', '') + .set('openFrm', false); + }); + case SHOW_DETAIL_CONTACT: + return state.withMutations((mutableState) => { + const index = state.get('contactList').indexOf(action.item); + mutableState + .set('selectedIndex', index) + .set('showMobileDetail', true); + }); + case HIDE_DETAIL: + return state.withMutations((mutableState) => { + mutableState.set('showMobileDetail', false); + }); + case DELETE_CONTACT: + return state.withMutations((mutableState) => { + const index = state.get('contactList').indexOf(action.item); + mutableState + .update('contactList', contactList => contactList.splice(index, 1)) + .set('notifMsg', notif.removed); + }); + case TOGGLE_FAVORITE: + return state.withMutations((mutableState) => { + const index = state.get('contactList').indexOf(action.item); + mutableState.update('contactList', contactList => contactList + .setIn([index, 'favorited'], !state.getIn(['contactList', index, 'favorited'])) + ); + }); + case CLOSE_NOTIF: + return state.withMutations((mutableState) => { + mutableState.set('notifMsg', ''); + }); + default: + return state; + } +} diff --git a/front/odiparpack/app/redux/modules/crudTable.js b/front/odiparpack/app/redux/modules/crudTable.js new file mode 100644 index 0000000..2bdb1e8 --- /dev/null +++ b/front/odiparpack/app/redux/modules/crudTable.js @@ -0,0 +1,94 @@ +import { fromJS, List, Map } from 'immutable'; +import notif from 'ba-api/notifMessage'; +import { + FETCH_DATA, + ADD_EMPTY_ROW, + UPDATE_ROW, + REMOVE_ROW, + EDIT_ROW, + SAVE_ROW, + CLOSE_NOTIF +} from 'ba-actions/actionTypes'; + +const initialState = { + dataTable: List([]), + notifMsg: '', +}; + +const initialItem = (keyTemplate, anchor) => { + const [...rawKey] = keyTemplate.keys(); + const staticKey = { + id: (+new Date() + Math.floor(Math.random() * 999999)).toString(36), + }; + for (let i = 0; i < rawKey.length; i += 1) { + if (rawKey[i] !== 'id' && rawKey[i] !== 'edited') { + staticKey[rawKey[i]] = anchor[i].initialValue; + } + } + // Push another static key + staticKey.edited = true; + + return Map(staticKey); +}; + +const initialImmutableState = fromJS(initialState); + +export default function reducer(state = initialImmutableState, action = {}) { + const { branch } = action; + switch (action.type) { + case `${branch}/${FETCH_DATA}`: + return state.withMutations((mutableState) => { + const items = fromJS(action.items); + mutableState.set('dataTable', items); + }); + case `${branch}/${ADD_EMPTY_ROW}`: + return state.withMutations((mutableState) => { + const raw = state.get('dataTable').last(); + const initial = initialItem(raw, action.anchor); + mutableState.update('dataTable', dataTable => dataTable.unshift(initial)); + }); + case `${branch}/${REMOVE_ROW}`: + return state.withMutations((mutableState) => { + const index = state.get('dataTable').indexOf(action.item); + mutableState + .update('dataTable', dataTable => dataTable.splice(index, 1)) + .set('notifMsg', notif.removed); + }); + case `${branch}/${UPDATE_ROW}`: + return state.withMutations((mutableState) => { + const index = state.get('dataTable').indexOf(action.item); + const cellTarget = action.event.target.name; + const newVal = type => { + if (type === 'checkbox') { + return action.event.target.checked; + } + return action.event.target.value; + }; + mutableState.update('dataTable', dataTable => dataTable + .setIn([index, cellTarget], newVal(action.event.target.type)) + ); + }); + case `${branch}/${EDIT_ROW}`: + return state.withMutations((mutableState) => { + const index = state.get('dataTable').indexOf(action.item); + mutableState.update('dataTable', dataTable => dataTable + .setIn([index, 'edited'], true) + ); + }); + case `${branch}/${SAVE_ROW}`: + return state.withMutations((mutableState) => { + const index = state.get('dataTable').indexOf(action.item); + mutableState + .update('dataTable', dataTable => dataTable + .setIn([index, 'edited'], false) + ) + .set('notifMsg', notif.saved); + }); + case `${branch}/${CLOSE_NOTIF}`: + return state.withMutations((mutableState) => { + mutableState.set('notifMsg', ''); + }); + default: + return state; + } +} diff --git a/front/odiparpack/app/redux/modules/crudTableForm.js b/front/odiparpack/app/redux/modules/crudTableForm.js new file mode 100644 index 0000000..d5194c1 --- /dev/null +++ b/front/odiparpack/app/redux/modules/crudTableForm.js @@ -0,0 +1,99 @@ +import { fromJS, List, Map } from 'immutable'; +import notif from 'ba-api/notifMessage'; +import { + FETCH_DATA_FORM, + ADD_NEW, + CLOSE_FORM, + SUBMIT_DATA, + REMOVE_ROW_FORM, + EDIT_ROW_FORM, + CLOSE_NOTIF +} from 'ba-actions/actionTypes'; + +const initialState = { + dataTable: List([]), + formValues: Map(), + editingId: '', + showFrm: false, + notifMsg: '', +}; + +const initialItem = (keyTemplate, anchor) => { + const [...rawKey] = keyTemplate.keys(); + const staticKey = {}; + for (let i = 0; i < rawKey.length; i += 1) { + if (rawKey[i] !== 'id') { + const itemIndex = anchor.findIndex(a => a.name === rawKey[i]); + staticKey[rawKey[i]] = anchor[itemIndex].initialValue; + } + } + + return Map(staticKey); +}; +let editingIndex = 0; + +const initialImmutableState = fromJS(initialState); + +export default function reducer(state = initialImmutableState, action = {}) { + const { branch } = action; + switch (action.type) { + case `${branch}/${FETCH_DATA_FORM}`: + return state.withMutations((mutableState) => { + const items = fromJS(action.items); + mutableState.set('dataTable', items); + }); + case `${branch}/${ADD_NEW}`: + return state.withMutations((mutableState) => { + const raw = state.get('dataTable').last(); + const initial = initialItem(raw, action.anchor); + mutableState.set('formValues', initial); + mutableState.set('showFrm', true); + }); + case `${branch}/${SUBMIT_DATA}`: + return state.withMutations((mutableState) => { + if (state.get('editingId') === action.newData.get('id')) { + // Update data + mutableState + .update('dataTable', dataTable => dataTable.setIn([editingIndex], action.newData)) + .set('notifMsg', notif.updated); + } else { + // Insert data + const id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36); + const initItem = Map(action.newData); + const newItem = initItem.update('id', (val = id) => val); + mutableState + .update('dataTable', dataTable => dataTable.unshift(newItem)) + .set('notifMsg', notif.saved); + } + mutableState.set('showFrm', false); + mutableState.set('formValues', Map()); + }); + case `${branch}/${CLOSE_FORM}`: + return state.withMutations((mutableState) => { + mutableState + .set('formValues', Map()) + .set('showFrm', false); + }); + case `${branch}/${REMOVE_ROW_FORM}`: + return state.withMutations((mutableState) => { + const index = state.get('dataTable').indexOf(action.item); + mutableState + .update('dataTable', dataTable => dataTable.splice(index, 1)) + .set('notifMsg', notif.removed); + }); + case `${branch}/${EDIT_ROW_FORM}`: + return state.withMutations((mutableState) => { + editingIndex = state.get('dataTable').indexOf(action.item); + mutableState + .set('formValues', action.item) + .set('editingId', action.item.get('id')) + .set('showFrm', true); + }); + case `${branch}/${CLOSE_NOTIF}`: + return state.withMutations((mutableState) => { + mutableState.set('notifMsg', ''); + }); + default: + return state; + } +} diff --git a/front/odiparpack/app/redux/modules/ecommerce.js b/front/odiparpack/app/redux/modules/ecommerce.js new file mode 100644 index 0000000..a294bae --- /dev/null +++ b/front/odiparpack/app/redux/modules/ecommerce.js @@ -0,0 +1,94 @@ +import { fromJS, List } from 'immutable'; +import notif from 'ba-api/notifMessage'; +import { + FETCH_PRODUCT_DATA, + ADD_TO_CART, + DELETE_CART_ITEM, + CHECKOUT, + SHOW_DETAIL_PRODUCT, + SEARCH_PRODUCT, + CLOSE_NOTIF +} from 'ba-actions/actionTypes'; + +const initialState = { + productList: List([]), + cart: List([]), + totalItems: 0, + totalPrice: 0, + productIndex: 0, + keywordValue: '', + notifMsg: '', +}; + +let itemId = []; + +const initialImmutableState = fromJS(initialState); +export default function reducer(state = initialImmutableState, action = {}) { + switch (action.type) { + case FETCH_PRODUCT_DATA: + return state.withMutations((mutableState) => { + const items = fromJS(action.items); + mutableState.set('productList', items); + }); + case SEARCH_PRODUCT: + return state.withMutations((mutableState) => { + action.keyword.persist(); + const keyword = action.keyword.target.value.toLowerCase(); + mutableState.set('keywordValue', keyword); + }); + case ADD_TO_CART: + return state.withMutations((mutableState) => { + const item = fromJS(action.item); + const qty = Number(item.get('quantity')); + const price = item.get('price'); + const index = itemId.indexOf(action.item.id); + if (index > -1) { + // If item already added to cart + mutableState.update('cart', cart => cart.setIn( + [index, 'quantity'], + state.getIn(['cart', index, 'quantity']) + qty + )); + } else { + // item not exist in cart + itemId.push(action.item.id); + mutableState.update('cart', cart => cart.push(item)); + } + mutableState + .set('totalItems', state.get('totalItems') + qty) + .set('totalPrice', state.get('totalPrice') + (price * qty)) + .set('notifMsg', notif.addCart); + }); + case DELETE_CART_ITEM: + return state.withMutations((mutableState) => { + const index = state.get('cart').indexOf(action.item); + const qty = Number(action.item.get('quantity')); + const price = action.item.get('price'); + itemId = itemId.filter(item => item !== action.item.get('id')); + mutableState + .update('cart', cart => cart.splice(index, 1)) + .set('totalItems', state.get('totalItems') - qty) + .set('totalPrice', state.get('totalPrice') - (price * qty)) + .set('notifMsg', notif.removed); + }); + case CHECKOUT: + itemId = []; + return state.withMutations((mutableState) => { + mutableState + .set('cart', List([])) + .set('totalItems', 0) + .set('totalPrice', 0) + .set('notifMsg', notif.checkout); + }); + case SHOW_DETAIL_PRODUCT: + return state.withMutations((mutableState) => { + const index = state.get('productList').indexOf(action.item); + mutableState.set('productIndex', index); + }); + case CLOSE_NOTIF: + return state.withMutations((mutableState) => { + mutableState.set('notifMsg', ''); + }); + default: + return state; + } +} diff --git a/front/odiparpack/app/redux/modules/email.js b/front/odiparpack/app/redux/modules/email.js new file mode 100644 index 0000000..e103316 --- /dev/null +++ b/front/odiparpack/app/redux/modules/email.js @@ -0,0 +1,119 @@ +import { fromJS, List, Map } from 'immutable'; +import notif from 'ba-api/notifMessage'; +import dummyData from 'ba-api/dummyContents'; +import { + FETCH_EMAIL_DATA, + OPEN_MAIL, + FILTER_MAIL, + COMPOSE_MAIL, + SEND_MAIL, + DISCARD_MESSAGE, + SEARCH_MAIL, + DELETE_MAIL, + MOVE_TO, + TOGGLE_STARED, + CLOSE_NOTIF +} from 'ba-actions/actionTypes'; +import { getDate, getTime } from '../helpers/dateTimeHelper'; + +const initialState = { + inbox: List([]), + selectedMail: 0, + selectedMailId: '', + keywordValue: '', + currentPage: 'inbox', + openFrm: false, + notifMsg: '', +}; + +const buildMessage = (to, subject, content, files) => { + const id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36); + const newData = Map({ + id, + date: getDate(), + time: getTime(), + avatar: dummyData.user.avatar, + name: to, + subject, + content, + attachment: files, + category: 'sent', + stared: false, + }); + return newData; +}; + +const initialImmutableState = fromJS(initialState); +export default function reducer(state = initialImmutableState, action = {}) { + switch (action.type) { + case FETCH_EMAIL_DATA: + return state.withMutations((mutableState) => { + const items = fromJS(action.items); + mutableState.set('inbox', items); + }); + case OPEN_MAIL: + return state.withMutations((mutableState) => { + const index = state.get('inbox').indexOf(action.mail); + mutableState.set('selectedMail', index); + }); + case FILTER_MAIL: + return state.withMutations((mutableState) => { + mutableState.set('currentPage', action.filter); + }); + case COMPOSE_MAIL: + return state.withMutations((mutableState) => { + mutableState.set('openFrm', true); + }); + case SEND_MAIL: + return state.withMutations((mutableState) => { + const newMail = buildMessage(action.to, action.subject, action.content, action.attachment); + mutableState + .update('inbox', inbox => inbox.unshift(newMail)) + .set('selectedMailId', '') + .set('openFrm', false) + .set('notifMsg', notif.sent); + }); + case DISCARD_MESSAGE: + return state.withMutations((mutableState) => { + mutableState + .set('openFrm', false) + .set('selectedMailId', '') + .set('notifMsg', notif.discard); + }); + case SEARCH_MAIL: + return state.withMutations((mutableState) => { + action.keyword.persist(); + const keyword = action.keyword.target.value.toLowerCase(); + mutableState.set('keywordValue', keyword); + }); + case DELETE_MAIL: + return state.withMutations((mutableState) => { + const index = state.get('inbox').indexOf(action.mail); + mutableState + .update('inbox', inbox => inbox.splice(index, 1)) + .set('notifMsg', notif.removed); + }); + case TOGGLE_STARED: + return state.withMutations((mutableState) => { + const index = state.get('inbox').indexOf(action.mail); + mutableState.update('inbox', inbox => inbox + .setIn([index, 'stared'], !state.getIn(['inbox', index, 'stared'])) + ); + }); + case MOVE_TO: + return state.withMutations((mutableState) => { + const index = state.get('inbox').indexOf(action.mail); + mutableState + .update('inbox', inbox => inbox + .setIn([index, 'category'], action.category) + ) + .set('notifMsg', notif.labeled); + }); + case CLOSE_NOTIF: + return state.withMutations((mutableState) => { + mutableState.set('notifMsg', ''); + }); + default: + return state; + } +} diff --git a/front/odiparpack/app/redux/modules/initForm.js b/front/odiparpack/app/redux/modules/initForm.js new file mode 100644 index 0000000..82bd90b --- /dev/null +++ b/front/odiparpack/app/redux/modules/initForm.js @@ -0,0 +1,22 @@ +import { fromJS, Map } from 'immutable'; +import { INIT, CLEAR } from 'ba-actions/actionTypes'; + +const initialState = { + formValues: Map() +}; + +const initialImmutableState = fromJS(initialState); +export default function reducer(state = initialImmutableState, action = {}) { + switch (action.type) { + case INIT: + return state.withMutations((mutableState) => { + mutableState.set('formValues', action.data); + }); + case CLEAR: + return state.withMutations((mutableState) => { + mutableState.set('formValues', []); + }); + default: + return state; + } +} diff --git a/front/odiparpack/app/redux/modules/login.js b/front/odiparpack/app/redux/modules/login.js new file mode 100644 index 0000000..add50fb --- /dev/null +++ b/front/odiparpack/app/redux/modules/login.js @@ -0,0 +1,19 @@ +import { Map, fromJS } from 'immutable'; +import { INIT } from 'ba-actions/actionTypes'; + +const initialState = { + usersLogin: Map({ + email: '[email protected]', + password: '12345678', + remember: false + }) +}; +const initialImmutableState = fromJS(initialState); +export default function reducer(state = initialImmutableState, action = {}) { + switch (action.type) { + case INIT: + return state; + default: + return state; + } +} diff --git a/front/odiparpack/app/redux/modules/socialMedia.js b/front/odiparpack/app/redux/modules/socialMedia.js new file mode 100644 index 0000000..a775f49 --- /dev/null +++ b/front/odiparpack/app/redux/modules/socialMedia.js @@ -0,0 +1,110 @@ +import { fromJS, List, Map } from 'immutable'; +import notif from 'ba-api/notifMessage'; +import dummy from 'ba-api/dummyContents'; +import { + FETCH_TIMELINE_DATA, + POST, + TOGGLE_LIKE, + FETCH_COMMENT_DATA, + POST_COMMENT, + CLOSE_NOTIF +} from 'ba-actions/actionTypes'; +import { getDate, getTime } from '../helpers/dateTimeHelper'; + +const initialState = { + dataTimeline: List([]), + commentIndex: 0, + notifMsg: '', +}; + +const icon = privacyType => { + switch (privacyType) { + case 'public': + return 'language'; + case 'friends': + return 'people'; + default: + return 'lock'; + } +}; + +const buildTimeline = (text, image, privacy) => { + const id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36); + const imageSrc = image !== undefined ? URL.createObjectURL(image[0]) : ''; + return Map({ + id, + name: 'John Doe', + date: getDate(), + time: getTime(), + icon: icon(privacy), + avatar: dummy.user.avatar, + image: imageSrc, + content: text, + liked: false, + comments: List([]) + }); +}; + +const buildComment = (message, curData) => { + const id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36); + const newData = Map({ + id, + from: 'John Doe', + avatar: dummy.user.avatar, + date: getDate(), + message, + }); + return curData.push(newData); +}; + +const initialImmutableState = fromJS(initialState); + +export default function reducer(state = initialImmutableState, action = {}) { + switch (action.type) { + case FETCH_TIMELINE_DATA: + return state.withMutations((mutableState) => { + const items = fromJS(action.items); + mutableState.set('dataTimeline', items); + }); + case POST: + return state.withMutations((mutableState) => { + mutableState + .update( + 'dataTimeline', + dataTimeline => dataTimeline.unshift( + buildTimeline(action.text, action.media, action.privacy) + ) + ) + .set('notifMsg', notif.posted); + }); + case TOGGLE_LIKE: + return state.withMutations((mutableState) => { + const index = state.get('dataTimeline').indexOf(action.item); + mutableState.update('dataTimeline', dataTimeline => dataTimeline + .setIn([index, 'liked'], !state.getIn(['dataTimeline', index, 'liked'])) + ); + }); + case FETCH_COMMENT_DATA: + return state.withMutations((mutableState) => { + const index = state.get('dataTimeline').indexOf(action.item); + mutableState.set('commentIndex', index); + }); + case POST_COMMENT: + return state.withMutations((mutableState) => { + mutableState + .update('dataTimeline', + dataTimeline => dataTimeline.setIn( + [state.get('commentIndex'), 'comments'], + buildComment(action.comment, state.getIn(['dataTimeline', state.get('commentIndex'), 'comments'])) + ) + ) + .set('notifMsg', notif.commented); + }); + case CLOSE_NOTIF: + return state.withMutations((mutableState) => { + mutableState.set('notifMsg', ''); + }); + default: + return state; + } +} diff --git a/front/odiparpack/app/redux/modules/treeTable.js b/front/odiparpack/app/redux/modules/treeTable.js new file mode 100644 index 0000000..96fa08a --- /dev/null +++ b/front/odiparpack/app/redux/modules/treeTable.js @@ -0,0 +1,49 @@ +import { fromJS, List } from 'immutable'; +import { TOGGLE_TREE } from 'ba-actions/actionTypes'; + +const initialState = { + treeOpen: List([]), + arrowMore: List([]) +}; + +const initialImmutableState = fromJS(initialState); + +// Collect existing child and parent id's +function collectId(id, listedId, collapsed, arrowLess) { + arrowLess.push(id); + for (let i = 0; i < listedId.size; i += 1) { + if (listedId.getIn([i]).startsWith(id + '_')) { + collapsed.push(listedId.getIn([i])); + arrowLess.push(listedId.getIn([i])); + } + } +} + +export default function reducer(state = initialImmutableState, action = {}) { + const { branch } = action; + switch (action.type) { + case `${branch}/${TOGGLE_TREE}`: + return state.withMutations((mutableState) => { + const listedId = state.get('treeOpen'); + const collapsed = []; + const arrowLess = []; + + // Collect existing id + collectId(action.keyID, listedId, collapsed, arrowLess); + + // Collapse and Expand row + if (collapsed.length > 0) { // Collapse tree table + mutableState.update('treeOpen', treeOpen => treeOpen.filter(x => collapsed.indexOf(x) < 0)); + mutableState.update('arrowMore', arrowMore => arrowMore.filter(x => arrowLess.indexOf(x) < 0)); + } else { // Expand tree table + mutableState.update('arrowMore', arrowMore => arrowMore.push(action.keyID)); + action.child.map(item => { + mutableState.update('treeOpen', treeOpen => treeOpen.push(item.id)); + return true; + }); + } + }); + default: + return state; + } +} diff --git a/front/odiparpack/app/redux/modules/ui.js b/front/odiparpack/app/redux/modules/ui.js new file mode 100644 index 0000000..a0fa4e6 --- /dev/null +++ b/front/odiparpack/app/redux/modules/ui.js @@ -0,0 +1,95 @@ +import { fromJS, List } from 'immutable'; +import MenuContent from 'ba-api/menu'; +import { + TOGGLE_SIDEBAR, + OPEN_SUBMENU, + CHANGE_THEME, + LOAD_PAGE +} from 'ba-actions/actionTypes'; + +const initialState = { + sidebarOpen: true, + theme: 'purpleRedTheme', + pageLoaded: false, + palette: List([ + { name: 'Purple Red', value: 'purpleRedTheme' }, + { name: 'Natural Green Orange', value: 'greenTheme' }, + { name: 'Blue Ocean', value: 'blueTheme' }, + { name: 'Blue Sky', value: 'skyBlueTheme' }, + { name: 'Sweet Magenta Cyan', value: 'magentaTheme' }, + { name: 'Violet Green', value: 'purpleTheme' }, + { name: 'Vintage Yellow', value: 'yellowCyanTheme' }, + { name: 'Orange Violet', value: 'orangeTheme' }, + { name: 'Cyan Green', value: 'cyanTheme' }, + { name: 'Red Silver', value: 'redTheme' }, + { name: 'Grey', value: 'greyTheme' }, + { name: 'Green Nature', value: 'greenNatureTheme' }, + ]), + subMenuOpen: [] +}; + +const getMenus = menuArray => menuArray.map(item => { + if (item.child) { + return item.child; + } + return false; +}); + +const setNavCollapse = (arr, curRoute) => { + let headMenu = 'not found'; + for (let i = 0; i < arr.length; i += 1) { + for (let j = 0; j < arr[i].length; j += 1) { + if (arr[i][j].link === curRoute) { + headMenu = MenuContent[i].key; + } + } + } + return headMenu; +}; + +const initialImmutableState = fromJS(initialState); + +export default function reducer(state = initialImmutableState, action = {}) { + switch (action.type) { + case TOGGLE_SIDEBAR: + return state.withMutations((mutableState) => { + mutableState.set('sidebarOpen', !state.get('sidebarOpen')); + }); + case OPEN_SUBMENU: + return state.withMutations((mutableState) => { + // Set initial open parent menu + const activeParent = setNavCollapse( + getMenus(MenuContent), + action.initialLocation + ); + + // Once page loaded will expand the parent menu + if (action.initialLocation) { + mutableState.set('subMenuOpen', List([activeParent])); + return; + } + + // Expand / Collapse parent menu + const menuList = state.get('subMenuOpen'); + if (menuList.indexOf(action.key) > -1) { + if (action.keyParent) { + mutableState.set('subMenuOpen', List([action.keyParent])); + } else { + mutableState.set('subMenuOpen', List([])); + } + } else { + mutableState.set('subMenuOpen', List([action.key, action.keyParent])); + } + }); + case CHANGE_THEME: + return state.withMutations((mutableState) => { + mutableState.set('theme', action.theme); + }); + case LOAD_PAGE: + return state.withMutations((mutableState) => { + mutableState.set('pageLoaded', action.isLoaded); + }); + default: + return state; + } +} diff --git a/front/odiparpack/app/redux/reducers.js b/front/odiparpack/app/redux/reducers.js new file mode 100644 index 0000000..902ab2b --- /dev/null +++ b/front/odiparpack/app/redux/reducers.js @@ -0,0 +1,66 @@ +/** + * Combine all reducers in this file and export the combined reducers. + */ +import { reducer as form } from 'redux-form/immutable'; +import { combineReducers } from 'redux-immutable'; +import { connectRouter } from 'connected-react-router/immutable'; +import history from 'utils/history'; + +import languageProviderReducer from 'containers/LanguageProvider/reducer'; +import login from './modules/login'; +import uiReducer from './modules/ui'; +import treeTable from './modules/treeTable'; +import crudTable from './modules/crudTable'; +import crudTableForm from './modules/crudTableForm'; +import socmed from './modules/socialMedia'; +import ecommerce from './modules/ecommerce'; +import contact from './modules/contact'; +import chat from './modules/chat'; +import email from './modules/email'; +import calendar from './modules/calendar'; +import initval from './modules/initForm'; + +/** + * Branching reducers to use one reducer for many components + */ + +function branchReducer(reducerFunction, reducerName) { + return (state, action) => { + const { branch } = action; + const isInitializationCall = state === undefined; + if (branch !== reducerName && !isInitializationCall) { + return state; + } + return reducerFunction(state, action); + }; +} + +/** + * Creates the main reducer with the dynamically injected ones + */ +export default function createReducer(injectedReducers) { + const rootReducer = combineReducers({ + form, + ui: uiReducer, + initval, + login, + socmed, + calendar, + ecommerce, + contact, + chat, + email, + treeTableArrow: branchReducer(treeTable, 'treeTableArrow'), + treeTablePM: branchReducer(treeTable, 'treeTablePM'), + crudTableDemo: branchReducer(crudTable, 'crudTableDemo'), + crudTableForm, + crudTbFrmDemo: branchReducer(crudTableForm, 'crudTbFrmDemo'), + language: languageProviderReducer, + router: connectRouter(history), + ...injectedReducers, + }); + + // Wrap the root reducer and return a new root reducer with router state + const mergeWithRouterState = connectRouter(history); + return mergeWithRouterState(rootReducer); +} diff --git a/front/odiparpack/app/styles/.DS_Store b/front/odiparpack/app/styles/.DS_Store Binary files differnew file mode 100644 index 0000000..27ec003 --- /dev/null +++ b/front/odiparpack/app/styles/.DS_Store diff --git a/front/odiparpack/app/styles/components/Code.scss b/front/odiparpack/app/styles/components/Code.scss new file mode 100644 index 0000000..11a105f --- /dev/null +++ b/front/odiparpack/app/styles/components/Code.scss @@ -0,0 +1,9 @@ +.codePre{ + code{ + color: #4f00ff; + padding: 3px; + border: #c1c1c1 1px solid; + border-radius: 3px; + font-size: 13px; + } +}
\ No newline at end of file diff --git a/front/odiparpack/app/styles/components/Form.scss b/front/odiparpack/app/styles/components/Form.scss new file mode 100644 index 0000000..eb13fe3 --- /dev/null +++ b/front/odiparpack/app/styles/components/Form.scss @@ -0,0 +1,24 @@ +@import "./../mixins"; +$sm: "(max-width: 600px)"; +.bodyForm{ + position: relative; + background: $white; + padding: 24px; + @media #{$sm} { + padding: 15px 10px; + } + max-height: 450px; + overflow: auto; +} +.buttonArea{ + background: material-color('grey', '100'); + position: relative; + bottom: 0; + left: 0; + width: 100%; + text-align: right; + padding: 8px 24px; + button{ + margin-right: 5px; + } +}
\ No newline at end of file diff --git a/front/odiparpack/app/styles/components/Messages.scss b/front/odiparpack/app/styles/components/Messages.scss new file mode 100644 index 0000000..16b61ca --- /dev/null +++ b/front/odiparpack/app/styles/components/Messages.scss @@ -0,0 +1,89 @@ +@import "../../styles/mixins"; + +@mixin theme() { + width: 100%; + display: flex; + padding: 10px; + background: none; + border: none; +} + +// Fill Background +.bgInfo, div.bgInfo{ + background: material-color('blue', '300'); + button{ + color: material-color('blue', '900'); + } +} +.bgSuccess, div.bgSuccess{ + background: material-color('green', '300'); + button{ + color: material-color('green', '900'); + } +} +.bgWarning, div.bgWarning{ + background: material-color('orange', '300'); + button{ + color: material-color('orange', '900'); + } +} +.bgError, div.bgError{ + background: material-color('red', '300'); + button{ + color: material-color('red', '900'); + } +} +.bgDefault, div.bgDefault{ + background: material-color('grey', '700') +} + +// Fill Background Table Row +table tr{ + &.bgInfo{ + background: material-color('blue', '50') + } + &.bgSuccess{ + background: material-color('green', '50') + } + &.bgWarning{ + background: material-color('orange', '50') + } + &.bgError{ + background: material-color('red', '50') + } + &.bgDefault{ + background: #FFF + } +} + +// Icon Background +.messageInfo{ + @include theme(); + .icon{ + background: material-color('blue', '300') + } +} +.messageSuccess{ + @include theme(); + .icon{ + background: material-color('green', '300') + } +} +.messageWarning{ + @include theme(); + .icon{ + background: material-color('orange', '300') + } +} +.messageError{ + @include theme(); + .icon{ + background: material-color('red', '300') + } +} +.messageDefault{ + @include theme(); + .icon{ + background: material-color('grey', '300') + } +} diff --git a/front/odiparpack/app/styles/components/Progress.scss b/front/odiparpack/app/styles/components/Progress.scss new file mode 100644 index 0000000..fdc1757 --- /dev/null +++ b/front/odiparpack/app/styles/components/Progress.scss @@ -0,0 +1,18 @@ +@import "../../styles/mixins"; + +// Fill Background +.bgInfo div{ + background: material-color('blue', '300') +} +.bgSuccess div{ + background: material-color('green', '300') +} +.bgWarning div{ + background: material-color('orange', '300') +} +.bgError div{ + background: material-color('red', '300') +} +.bgDefault div{ + background: material-color('grey', '700') +}
\ No newline at end of file diff --git a/front/odiparpack/app/styles/components/Table.scss b/front/odiparpack/app/styles/components/Table.scss new file mode 100644 index 0000000..04209f0 --- /dev/null +++ b/front/odiparpack/app/styles/components/Table.scss @@ -0,0 +1,152 @@ +@import "../../styles/mixins"; + +.stripped{ + tbody tr:nth-child(even){ + background: material-color('grey', '100'); + } +} + +.hover{ + tbody tr:hover{ + background: material-color('grey', '200'); + } +} + +.bordered{ + thead tr{ + background: material-color('grey', '200'); + } + td, th{ + border: 1px solid material-color('grey', '300'); + } + tr td, tr th{ + &:first-child{ + border-left: none + } + &:last-child{ + border-right: none + } + } +} + +table.small{ + tr{ + height: 24px; + td, th{ + padding: 4px 10px; + font-size: 12px; + } + } +} + +table.medium{ + tr{ + height: 48px; + td, th{ + padding: 4px 56px 4px 24px; + font-size: 14px; + } + } +} + +table.big{ + tr{ + height: 64px; + td, th{ + padding: 8px 56px 8px 24px; + font-size: 18px; + } + } +} + +.nodata{ + text-align: center; + padding: 10px 10px 40px; + font-size: 14px; + line-height: 16px; + color: material-color('grey', '500'); + svg{ + position: relative; + top: 5px; + width: 22px; + margin: 0 6px; + fill: material-color('grey', '500'); + } +} + +.hideAction{ + display: none !important; +} + +.tableCrud{ + table-layout: fixed; + .hiddenField{ + opacity: 0; + position: absolute; + } + .editing{ + background: material-color('lime', '50'); + td .crudInput{ + &:before{ + opacity: 1; + } + > * { + &:before, &:after{ + opacity: 1; + } + } + svg, button { + visibility: visible; + } + input[type="text"], + input[type="number"], + input[type="email"]{ + width: 100%; + } + } + } + th{ + padding: 0 15px; + } + td{ + padding: 0 15px; + &.toggleCell{ + position: relative; + } + vertical-align: middle; + .coverReadonly{ + width: 100%; + height: 100%; + position: absolute; + z-index: 2; + display: none; + &.show{ + display: block; + } + } + .crudInput{ + &:before{ + opacity: 0; + } + color: material-color('grey', '900'); + > * > input { + color: material-color('grey', '900'); + &:before, &:after{ + opacity: 0; + } + } + svg, button { + visibility: hidden; + } + } + } + td [disabled], td[disabled]:hover{ + border: none !important; + box-shadow: none; + cursor: text; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: none; + } +} diff --git a/front/odiparpack/app/styles/components/TextEditor.scss b/front/odiparpack/app/styles/components/TextEditor.scss new file mode 100644 index 0000000..1110532 --- /dev/null +++ b/front/odiparpack/app/styles/components/TextEditor.scss @@ -0,0 +1,24 @@ +@import "../../styles/mixins"; + +.TextEditor{ + background: #fff; + min-height: 200px; + border: 1px solid material-color('grey', '300'); + padding: 0 10px; +} + +.ToolbarEditor{ + background: material-color('grey', '50'); + border: none; +} + +.textPreview{ + width: 100%; + resize: none; + height: 305px; + border-width: 1px; + border-style: solid; + border-color: material-color('grey', '300'); + border-image: initial; + padding: 5px; +}
\ No newline at end of file diff --git a/front/odiparpack/app/styles/components/Typography.scss b/front/odiparpack/app/styles/components/Typography.scss new file mode 100644 index 0000000..bf179e3 --- /dev/null +++ b/front/odiparpack/app/styles/components/Typography.scss @@ -0,0 +1,101 @@ +@import url('https://fonts.googleapis.com/css?family=Roboto:100,700,900'); +@import "../../styles/mixins"; + +// Font Weight +.lighter{ + font-weight: 100 +} +.light{ + font-weight: 300 +} +.regular{ + font-weight: 400 +} +.medium{ + font-weight: 500 +} +.bold{ + font-weight: 700 +} +.bolder{ + font-weight: 900 +} + +// Font stlye +.italic { + font-style: italic +} +.underline { + text-decoration: underline +} +.lineThrought { + text-decoration: line-through +} + +// Color +.textInfo{ + color: material-color('blue', '500') !important; +} +.textSuccess{ + color: material-color('green', '500') !important; +} +.textWarning{ + color: material-color('orange', '500') !important; +} +.textError{ + color: material-color('red', '500') !important; +} +.textGreyDark{ + color: material-color('blue-grey', '900') !important; +} +.textGrey{ + color: material-color('blue-grey', '400') !important; +} +.textGreyLight{ + color: material-color('blue-grey', '100') !important; +} + +// Transform +.capitalyze{ + text-transform: capitalize +} +.uppercase{ + text-transform: uppercase +} +.lowercase{ + text-transform: lowercase +} + +// Align +.textLeft{ + text-align: left +} +.textRight{ + text-align: right +} +.textCenter{ + text-align: center; +} +.textJustify{ + text-align: justify; +} + +// List +.list{ + list-style: disc; + margin-left: 20px; + ul { + list-style: circle; + margin-left: 20px; + } +} + +.orderedlist{ + list-style: decimal; + margin-left: 20px; + ul { + list-style: lower-alpha; + margin-left: 20px; + } +} + diff --git a/front/odiparpack/app/styles/components/vendors/emoji-picker-react/emoji-picker-react.css b/front/odiparpack/app/styles/components/vendors/emoji-picker-react/emoji-picker-react.css new file mode 100644 index 0000000..796ec48 --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/emoji-picker-react/emoji-picker-react.css @@ -0,0 +1,625 @@ +.emoji-picker .icn-magnifier:before { + background-image: url("data:image/svg+xml,%3Csvg id='Capa_1' xmlns='http://www.w3.org/2000/svg' width='310.4' height='310.4'%3E%3Cstyle%3E.st0%7Bfill:%239e9e9e%7D%3C/style%3E%3Cpath class='st0' d='M273.6 215c49.1-49.1 49.1-129 0-178.1-49.1-49.1-129-49.1-178.1 0-41.7 41.7-48 103.6-18.9 152 0 0 2.1 3.5-.7 6.3l-64.3 64.3c-12.8 12.8-15.8 30.7-4.5 42l2 2c11.3 11.3 29.2 8.3 42-4.5l64.1-64.1c3-3 6.4-.9 6.4-.9 48.4 28.9 110.3 22.6 152-19zm-154.9-23.3c-36.3-36.3-36.3-95.3 0-131.6s95.3-36.3 131.6 0 36.3 95.3 0 131.6-95.3 36.3-131.6 0z'/%3E%3Cpath class='st0' d='M126.8 118.4c-1.7 0-3.4-.3-5.1-1-6.6-2.8-9.7-10.4-6.9-17 17.6-41.6 65.7-61.1 107.3-43.5 6.6 2.8 9.7 10.4 6.9 17-2.8 6.6-10.4 9.7-17 6.9-28.4-12-61.2 1.3-73.2 29.7-2.2 4.9-7 7.9-12 7.9z'/%3E%3C/svg%3E"); +} + +.emoji-picker ul.skin-tones { + position: absolute; + right: 0; +} + +.emoji-picker ul.skin-tones.spread li { + opacity: 1; +} + +.emoji-picker ul.skin-tones li { + position: absolute; + top: 16px; + right: 20px; + z-index: 3; + padding: 1px; + border-radius: 3px; + opacity: 0; + transition: transform .4s, opacity .3s; +} + +.emoji-picker ul.skin-tones li.selected { + opacity: 1; + transform: scale(1.5); +} + +.emoji-picker ul.skin-tones li.neutral a { + background-color: #ffe082; +} + +.emoji-picker ul.skin-tones li.m1f3fb a { + background-color: #ffe0b2; +} + +.emoji-picker ul.skin-tones li.m1f3fc a { + background-color: #ffccbc; +} + +.emoji-picker ul.skin-tones li.m1f3fe a { + background-color: #795548; +} + +.emoji-picker ul.skin-tones li.m1f3ff a { + background-color: #5d4037; +} + +.emoji-picker ul.skin-tones li.m1f3fd a { + background-color: #ca7e55; +} + +.emoji-picker ul.skin-tones li a { + display: block; + height: 10px; + width: 10px; + border-radius: 2px; +} + +.emoji-picker ul.skin-tones li a img { + z-index: 2; + height: 10px; + width: 10px; +} + +.emoji-picker nav ~ .bar-wrapper { + margin-top: 45px; + border-top: 1px solid #eeeeee; +} + +.emoji-picker nav { + flex-direction: row; + min-height: 45px; + background-color: #ffffff; + display: flex; + justify-content: space-between; + position: absolute; + z-index: 1; + box-sizing: border-box; + left: 10px; + right: 10px; +} + +.emoji-picker nav a { + position: relative; + transition: filter .2s; +} + +.emoji-picker nav a:hover:after { + opacity: 1; + background-color: #a5d6a7; +} + +.emoji-picker nav a:hover i { + opacity: 1; +} + +.emoji-picker nav a i { + content: ''; + display: block; + height: 45px; + width: 20px; + background-size: 18px auto; + background-position: 50% 50%; + background-repeat: no-repeat; + opacity: .4; + transition: opacity .3s; + transition-delay: .2s; +} + +.emoji-picker nav a.people i { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 33 33'%3E%3Cpath d='M16.5 33C7.402 33 0 25.598 0 16.5S7.402 0 16.5 0 33 7.402 33 16.5 25.598 33 16.5 33zm0-32C7.953 1 1 7.953 1 16.5S7.953 32 16.5 32 32 25.047 32 16.5 25.047 1 16.5 1z'/%3E%3Cpath d='M16.5 33a16.38 16.38 0 0 1-9.549-3.06.5.5 0 1 1 .116-.876c4.146-1.535 4.815-2.781 4.815-5.169 0-.631-.142-.838-.398-1.214-.339-.494-.803-1.171-1.129-2.939-.048-.254-.089-.274-.316-.384-.606-.292-1.163-.712-1.309-2.628 0-.928.32-1.441.585-1.708-.058-.33-.153-.899-.242-1.519-.453-2.777-.473-6.178 3.433-7.759 3.404-1.38 6.121-.626 6.974.273.604.019 2.162.177 3.246 1.438 1.668 1.94 1.137 6.363.955 7.562.266.261.589.767.589 1.675-.146 1.954-.703 2.375-1.31 2.666-.228.11-.269.129-.316.384-.326 1.768-.789 2.445-1.128 2.939-.257.375-.398.583-.398 1.214 0 2.388.669 3.634 4.815 5.169a.498.498 0 0 1 .116.876A16.38 16.38 0 0 1 16.5 33zm-8.183-3.349C10.779 31.191 13.589 32 16.5 32s5.721-.809 8.183-2.349c-3.474-1.426-4.565-2.864-4.565-5.755 0-.941.278-1.348.573-1.779.304-.444.682-.996.971-2.556.139-.754.576-.964.865-1.103.311-.149.631-.303.744-1.803-.001-.764-.344-.972-.358-.98a.533.533 0 0 1-.264-.537c.248-1.329.656-5.474-.681-7.031-.913-1.062-2.352-1.091-2.626-1.08-.046-.004-.091-.005-.134-.016-.13-.033-.35-.146-.417-.262-.272-.466-2.641-1.403-5.91-.08-3.231 1.308-3.238 4.112-2.819 6.682.138.957.289 1.784.29 1.788a.5.5 0 0 1-.283.544c.003 0-.339.209-.339 1.008.112 1.461.433 1.616.743 1.765.289.139.727.349.866 1.103.288 1.56.666 2.112.97 2.556.296.431.574.838.574 1.779 0 2.894-1.091 4.332-4.566 5.757z'/%3E%3C/svg%3E"); +} + +.emoji-picker nav a.foods i { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M337.502 61.244c-46.267-19.341-98.094-21.573-145.933-6.282-5.497 1.758-8.528 7.638-6.772 13.134 1.758 5.497 7.64 8.528 13.134 6.772 43.115-13.782 89.819-11.772 131.51 5.657 1.317.55 2.682.811 4.026.811 4.087 0 7.969-2.415 9.644-6.422 2.228-5.324-.284-11.445-5.609-13.67zM368.323 77.252a11.31 11.31 0 0 0-.375-.239c-4.925-3.009-11.356-1.458-14.364 3.467-2.984 4.884-1.483 11.249 3.346 14.29a10.398 10.398 0 0 0 5.587 1.626c3.377 0 6.69-1.633 8.704-4.654 3.201-4.801 1.904-11.289-2.898-14.49zM447.293 161.884c-4.081-4.08-10.698-4.08-14.778 0l-14.629 14.629c-4.08 4.081-4.08 10.698 0 14.778 2.04 2.041 4.715 3.06 7.388 3.06s5.349-1.02 7.389-3.06l14.629-14.629c4.081-4.081 4.081-10.698.001-14.778zM83.999 214.617c-4.081-4.08-10.698-4.08-14.778 0l-14.629 14.629c-4.08 4.081-4.08 10.698 0 14.778 2.04 2.041 4.715 3.06 7.388 3.06s5.349-1.02 7.39-3.06l14.629-14.629c4.081-4.082 4.081-10.698 0-14.778zM115.508 100.235c-4.081-4.08-10.698-4.08-14.778 0l-14.629 14.629c-4.08 4.081-4.08 10.698 0 14.778 2.04 2.041 4.715 3.06 7.388 3.06s5.348-1.02 7.39-3.06l14.629-14.629c4.081-4.081 4.081-10.698 0-14.778zM386.754 116.24h-20.688c-5.771 0-10.449 4.678-10.449 10.449s4.678 10.449 10.449 10.449h20.688c5.771 0 10.449-4.678 10.449-10.449s-4.678-10.449-10.449-10.449zM151.326 161.908l-14.618-14.618c-4.081-4.08-10.698-4.081-14.778 0s-4.08 10.698 0 14.778l14.618 14.618a10.413 10.413 0 0 0 7.388 3.06c2.674 0 5.349-1.02 7.39-3.06 4.081-4.081 4.081-10.698 0-14.778zM411.753 229.241l-14.618-14.617c-4.08-4.081-10.696-4.08-14.777 0s-4.08 10.697 0 14.777l14.618 14.617c2.041 2.041 4.715 3.06 7.388 3.06s5.348-1.021 7.388-3.06c4.083-4.08 4.082-10.696.001-14.777zM318.326 126.607l-14.617-14.617c-4.081-4.08-10.698-4.081-14.778 0s-4.08 10.698 0 14.778l14.617 14.617a10.414 10.414 0 0 0 7.388 3.061 10.42 10.42 0 0 0 7.39-3.061c4.08-4.081 4.08-10.698 0-14.778zM195.194 97.387c-3.904-4.25-10.515-4.528-14.763-.622l-15.22 13.989c-4.249 3.905-4.527 10.515-.622 14.763a10.42 10.42 0 0 0 7.696 3.378c2.528 0 5.063-.911 7.068-2.756l15.22-13.989c4.248-3.905 4.526-10.515.621-14.763zM256.153 145.241H255.865c-5.762 0-10.437 4.665-10.449 10.429-.011 5.771 4.658 10.457 10.429 10.469H256.134c5.762 0 10.438-4.664 10.449-10.429.01-5.771-4.659-10.458-10.43-10.469z'/%3E%3Cpath d='M437.019 74.981C388.668 26.628 324.38 0 256 0S123.332 26.628 74.981 74.981C26.629 123.333 0 187.62 0 256c0 68.38 26.628 132.668 74.981 181.019C123.333 485.371 187.62 512 256 512c68.38 0 132.668-26.628 181.019-74.981C485.371 388.667 512 324.38 512 256s-26.628-132.668-74.981-181.019zM256 491.102c-94.256 0-175.718-55.763-213.173-136.024 6.781-.56 13.126-3.458 18.112-8.35 5.756-5.647 9.057-13.495 9.057-21.531v-9.364c0-7.449 6.027-13.624 13.435-13.767 3.561-.079 6.976 1.322 9.617 3.913 2.77 2.718 4.359 6.484 4.359 10.332v23.709c0 18.754 15.222 34.302 33.932 34.66.221.004.442.006.664.006 8.973 0 17.47-3.499 23.989-9.895 6.751-6.623 10.623-15.826 10.623-25.25v-18.215c20.172 27.524 52.723 45.432 89.384 45.432 52.209 0 96.09-36.312 107.73-85.007a10.027 10.027 0 0 1 2.18 6.23v25.773c0 19.673 15.968 35.984 35.596 36.361.233.004.464.006.696.006 9.409 0 18.321-3.671 25.161-10.38 7.086-6.951 11.149-16.61 11.149-26.5v-24.856c0-2.83 1.17-5.6 3.21-7.602 1.927-1.889 4.429-2.882 6.986-2.854 5.403.104 9.8 4.612 9.8 10.05v2.721c0 14.409 10.071 26.69 23.526 30.04C453.848 418.996 363.189 491.102 256 491.102zm223.405-200.399v-2.721c0-16.741-13.591-30.624-30.297-30.944-8.235-.159-16.057 2.978-22.022 8.829-6.021 5.906-9.473 14.113-9.473 22.52v24.856c0 4.314-1.782 8.536-4.886 11.582-2.97 2.912-6.846 4.462-10.82 4.397-8.326-.16-15.099-7.098-15.099-15.468v-25.773c0-13.154-8.392-24.538-20.091-28.971.027-1.001.043-2.004.043-3.011 0-46.06-29.007-87.788-72.182-103.836-5.411-2.01-11.425.745-13.434 6.154-2.01 5.409.745 11.424 6.154 13.434 35.027 13.021 58.562 46.877 58.562 84.248 0 49.549-40.312 89.861-89.861 89.861-49.549 0-89.861-40.312-89.861-89.861 0-37.372 23.535-71.228 58.565-84.246 5.409-2.01 8.164-8.026 6.154-13.434s-8.022-8.165-13.434-6.154C174.248 168.21 145.239 209.938 145.239 256a110.22 110.22 0 0 0 7.085 38.971 30.145 30.145 0 0 0-6.607 18.808v25.763c0 3.848-1.589 7.614-4.359 10.332-2.642 2.591-6.036 3.973-9.617 3.913-7.407-.142-13.434-6.317-13.434-13.766v-23.709c0-9.424-3.872-18.627-10.623-25.251-6.681-6.554-15.435-10.081-24.652-9.889-18.71.358-33.932 15.907-33.932 34.66v9.364c0 2.461-1.019 4.871-2.794 6.613-1.668 1.635-3.808 2.506-6.034 2.47-3.374-.065-6.387-2.139-7.733-5.21-7.543-23.011-11.64-47.569-11.64-73.07C20.898 126.365 126.365 20.898 256 20.898S491.102 126.365 491.102 256c0 15.182-1.464 30.026-4.227 44.414-4.281-1.138-7.47-5.083-7.47-9.711z'/%3E%3C/svg%3E"); +} + +.emoji-picker nav a.nature i { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 470 470'%3E%3Cpath d='M401.17 68.83C356.784 24.444 297.771 0 235 0S113.216 24.444 68.83 68.83 0 172.229 0 235s24.444 121.784 68.83 166.17S172.229 470 235 470s121.784-24.444 166.17-68.83S470 297.771 470 235s-24.444-121.784-68.83-166.17zM235 455c-121.309 0-220-98.691-220-220S113.691 15 235 15s220 98.691 220 220-98.691 220-220 220z'/%3E%3Cpath d='M382.5 173.979c3.532 0 6.735 1.824 8.568 4.879a7.499 7.499 0 0 0 12.864 0c1.833-3.055 5.036-4.879 8.568-4.879 4.143 0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5c-5.461 0-10.724 1.829-15 5.039-4.276-3.21-9.539-5.039-15-5.039-4.143 0-7.5 3.357-7.5 7.5s3.357 7.5 7.5 7.5zM322.5 135.459c3.532 0 6.735 1.824 8.568 4.879a7.499 7.499 0 0 0 12.864 0c1.833-3.055 5.036-4.879 8.568-4.879 4.143 0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5c-5.461 0-10.724 1.829-15 5.039-4.276-3.21-9.539-5.039-15-5.039-4.143 0-7.5 3.357-7.5 7.5s3.357 7.5 7.5 7.5zM117.5 173.979c3.532 0 6.735 1.824 8.568 4.879a7.499 7.499 0 0 0 12.864 0c1.833-3.055 5.036-4.879 8.568-4.879 4.143 0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5c-5.461 0-10.724 1.829-15 5.039-4.276-3.21-9.539-5.039-15-5.039-4.143 0-7.5 3.357-7.5 7.5s3.357 7.5 7.5 7.5zM436.826 253.173a7.5 7.5 0 0 0-5.443-2.6c-12.664-.4-24.343-7.548-32.041-19.608a7.5 7.5 0 0 0-12.643-.001c-7.974 12.489-20.074 19.652-33.2 19.652-13.089 0-25.177-7.164-33.162-19.656a7.502 7.502 0 0 0-12.635-.004c-8 12.494-20.098 19.66-33.192 19.66-13.098 0-25.189-7.164-33.175-19.656a7.5 7.5 0 0 0-12.64.004c-7.974 12.489-20.069 19.652-33.187 19.652-13.098 0-25.19-7.164-33.176-19.656a7.502 7.502 0 0 0-12.635-.004c-8 12.494-20.098 19.66-33.191 19.66-13.099 0-25.19-7.164-33.175-19.655a7.5 7.5 0 0 0-12.64.004c-7.699 12.061-19.389 19.207-32.07 19.608a7.494 7.494 0 0 0-5.443 2.6 7.497 7.497 0 0 0-1.769 5.767c5.786 49.506 29.545 95.215 66.901 128.706C135.964 421.407 184.509 440 235 440c45.241 0 88.17-14.518 124.145-41.982a7.498 7.498 0 0 0 1.41-10.512 7.496 7.496 0 0 0-10.512-1.41C316.705 411.547 276.924 425 235 425c-93.882 0-173.276-68.424-187.68-160.366 11.265-2.217 21.561-8.215 29.707-17.284 10.49 11.584 24.673 18.267 39.476 18.267 14.808 0 29.002-6.691 39.505-18.291 10.493 11.6 24.685 18.291 39.498 18.291 14.828 0 29.022-6.689 39.511-18.284 10.493 11.595 24.682 18.284 39.491 18.284 14.808 0 29.002-6.691 39.505-18.291 10.493 11.6 24.679 18.291 39.485 18.291 14.826 0 29.018-6.681 39.505-18.264 8.14 9.065 18.422 15.061 29.671 17.278-6.044 38.177-24.008 74.246-51.068 102.269a7.5 7.5 0 1 0 10.791 10.419c31.08-32.185 51.038-74.226 56.198-118.38a7.495 7.495 0 0 0-1.769-5.766z'/%3E%3Cpath d='M289.513 310.616c-4.143 0-7.5 3.357-7.5 7.5s3.357 7.5 7.5 7.5h10c4.143 0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5h-10zM358.49 280.616h-10c-4.143 0-7.5 3.357-7.5 7.5s3.357 7.5 7.5 7.5h10c4.143 0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5zM111.503 280.616c-4.143 0-7.5 3.357-7.5 7.5s3.357 7.5 7.5 7.5h10c4.143 0 7.5-3.357 7.5-7.5s-3.357-7.5-7.5-7.5h-10zM235 191.25c30.327 0 55-24.673 55-55s-24.673-55-55-55-55 24.673-55 55 24.673 55 55 55zm0-95c22.056 0 40 17.944 40 40s-17.944 40-40 40-40-17.944-40-40 17.944-40 40-40z'/%3E%3C/svg%3E"); +} + +.emoji-picker nav a.activity i { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath d='M32 0C14.355 0 0 14.355 0 32s14.355 32 32 32 32-14.355 32-32S49.645 0 32 0zm29.624 36.731l-3.885-6.439 2.681-7.88a29.867 29.867 0 0 1 1.204 14.319zm-7.558 15.567a.994.994 0 0 0-.408-.02L43.98 53.83a.993.993 0 0 0-.123-.345l-5.502-9.17 8.896-13.7h8.428a.992.992 0 0 0 .105.312l5.236 8.678a29.956 29.956 0 0 1-6.954 12.693zm-10.085 3.557l7.688-1.232a29.958 29.958 0 0 1-11.706 6.296l4.018-5.064zM12.65 9.1a29.858 29.858 0 0 1 18.628-7.082.982.982 0 0 0 .24.376l5.525 5.214-2.185 8.156-14.237 5.465c-.052-.042-.093-.094-.154-.126l-8.87-4.701L12.65 9.1zm25.736-2.976l-4.283-4.042a29.763 29.763 0 0 1 10.989 2.931l-6.706 1.111zM21.93 38.737l-.816-15.554L35.655 17.6l9.803 12.106-8.483 13.063-15.045-4.032zm37.375-19.141c-.031.054-.072.098-.093.159l-3.015 8.86h-9.048L36.882 15.937l2.113-7.887 8.27-1.371a.979.979 0 0 0 .453-.218 30.2 30.2 0 0 1 11.587 13.135zm-48.994-8.289l-.802 5.561-5.349 3.975a30.035 30.035 0 0 1 6.151-9.536zm-7.255 12.82c.044-.023.09-.037.131-.068l7.737-5.751 8.158 4.323.888 16.936c.002.025.013.048.016.073l-7.71 7.629c-.066.065-.105.145-.149.222L4.734 44.32c-.028-.012-.057-.009-.085-.018A29.822 29.822 0 0 1 2 32c0-2.725.372-5.362 1.056-7.873zm3.022 22.945l5.415 2.322 4.141 7.729a30.222 30.222 0 0 1-9.556-10.051zm12.759 11.879c-.019-.064-.025-.131-.058-.192l-5.317-9.924c.076-.043.155-.08.22-.145l8.027-7.942 14.507 3.888 5.927 9.879c.05.083.11.154.178.217l-5.449 6.867c-1.587.26-3.213.401-4.872.401-4.72 0-9.186-1.099-13.163-3.049z'/%3E%3C/svg%3E"); +} + +.emoji-picker nav a.objects i { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 58.153 58.153'%3E%3Cpath d='M40.076 29.153h-7.142a3.995 3.995 0 0 0-2.858-2.858V16.153a1 1 0 1 0-2 0v10.142c-1.72.447-3 1.999-3 3.858 0 2.206 1.794 4 4 4 1.858 0 3.411-1.28 3.858-3h7.142a1 1 0 1 0 0-2zm-11 3c-1.103 0-2-.897-2-2s.897-2 2-2 2 .897 2 2-.897 2-2 2z'/%3E%3Cpath d='M50.188 9.764l4.096 4.096a1 1 0 0 0 1.414 0c3.167-3.166 3.167-8.319 0-11.485s-8.319-3.166-11.485 0a.997.997 0 0 0 0 1.414l4.561 4.561-1.699 1.699c-4.78-4.284-11.089-6.896-17.998-6.896s-13.218 2.612-17.998 6.896l-1.7-1.699 4.561-4.561a.997.997 0 0 0 0-1.414c-3.166-3.166-8.318-3.166-11.485 0s-3.167 8.319 0 11.485a1 1 0 0 0 1.414 0l4.096-4.096 1.676 1.676c-4.679 4.857-7.565 11.453-7.565 18.713 0 9.898 5.357 18.564 13.321 23.265l-3.028 3.028a.999.999 0 1 0 1.414 1.414l3.45-3.45c3.578 1.754 7.597 2.743 11.843 2.743s8.265-.989 11.843-2.743l3.45 3.45a.997.997 0 0 0 1.414 0 .999.999 0 0 0 0-1.414l-3.028-3.028c7.964-4.701 13.321-13.367 13.321-23.265 0-7.26-2.886-13.856-7.565-18.713l1.677-1.676zm4.095-5.975c2.146 2.146 2.362 5.502.649 7.893L46.391 3.14a6.13 6.13 0 0 1 7.892.649zM3.22 11.681c-1.713-2.39-1.497-5.746.649-7.892s5.502-2.361 7.892-.649L3.22 11.681zm25.856 43.472c-13.785 0-25-11.215-25-25s11.215-25 25-25 25 11.215 25 25-11.214 25-25 25z'/%3E%3Cpath d='M29.076 10.032a1 1 0 0 0 1-1v-1a1 1 0 1 0-2 0v1a1 1 0 0 0 1 1zM29.076 50.032a1 1 0 0 0-1 1v1a1 1 0 1 0 2 0v-1a1 1 0 0 0-1-1zM50.076 31.032h1a1 1 0 1 0 0-2h-1a1 1 0 1 0 0 2zM8.076 29.032h-1a1 1 0 1 0 0 2h1a1 1 0 1 0 0-2zM43.926 13.768l-.707.707a.999.999 0 1 0 1.414 1.414l.707-.707a.999.999 0 1 0-1.414-1.414zM13.52 44.174l-.707.707a.999.999 0 1 0 1.414 1.414l.707-.707a.999.999 0 1 0-1.414-1.414zM44.633 44.174a.999.999 0 1 0-1.414 1.414l.707.707a.997.997 0 0 0 1.414 0 .999.999 0 0 0 0-1.414l-.707-.707zM14.227 13.768a.999.999 0 1 0-1.414 1.414l.707.707a.997.997 0 0 0 1.414 0 .999.999 0 0 0 0-1.414l-.707-.707z'/%3E%3C/svg%3E"); +} + +.emoji-picker nav a.places i { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 470 470'%3E%3Cpath d='M401.17 68.83C356.784 24.444 297.771 0 235 0S113.216 24.444 68.83 68.83C24.444 113.215 0 172.229 0 235s24.444 121.785 68.83 166.17C113.216 445.556 172.229 470 235 470s121.784-24.444 166.17-68.83C445.556 356.785 470 297.771 470 235s-24.444-121.785-68.83-166.17zM235 455c-121.309 0-220-98.691-220-220S113.691 15 235 15s220 98.691 220 220-98.691 220-220 220z'/%3E%3Ccircle cx='235' cy='97.5' r='7.5'/%3E%3Cpath d='M437.56 242.365a7.501 7.501 0 0 0-5.467-2.365h-26.046v-22.5c0-4.142-3.357-7.5-7.5-7.5s-7.5 3.358-7.5 7.5V240H372.5v-52.5a7.5 7.5 0 0 0-3.525-6.36L292.5 133.343V97.5A7.5 7.5 0 0 0 285 90h-13.253C268.262 72.905 253.109 60 235 60s-33.262 12.905-36.747 30H185a7.5 7.5 0 0 0-7.5 7.5V130h-25v-22.5c0-4.142-3.357-7.5-7.5-7.5s-7.5 3.358-7.5 7.5V130H105a7.5 7.5 0 0 0-7.5 7.5v102.499l-59.593-.01a7.504 7.504 0 0 0-7.487 7.969c3.523 56.171 29.666 105.984 69.187 140.798.281.291.587.556.911.799 23.389 20.362 51.39 35.496 82.128 43.638.307.102.622.184.946.246A204.258 204.258 0 0 0 235 440c17.409 0 34.679-2.229 51.386-6.558a7.297 7.297 0 0 0 1.002-.262 203.842 203.842 0 0 0 50.574-20.966c30.222-17.629 55.631-42.86 73.479-72.965a7.5 7.5 0 0 0-12.902-7.65 189.49 189.49 0 0 1-26.039 34.299V255h51.438a188.457 188.457 0 0 1-12.616 50.728 7.499 7.499 0 0 0 4.156 9.758 7.498 7.498 0 0 0 9.758-4.157 203.511 203.511 0 0 0 14.342-63.359 7.499 7.499 0 0 0-2.018-5.605zM192.5 175h85v215h-85V175zm0-70H205a7.5 7.5 0 0 0 7.5-7.5c0-12.407 10.094-22.5 22.5-22.5s22.5 10.093 22.5 22.5a7.5 7.5 0 0 0 7.5 7.5h12.5v55h-85v-55zM46.059 254.99l51.441.009V307.5c0 4.142 3.357 7.5 7.5 7.5s7.5-3.358 7.5-7.5V145h65v245H175c-4.143 0-7.5 3.358-7.5 7.5s3.357 7.5 7.5 7.5h2.5v11.078c-24.056-7.668-46.091-20.018-65-35.997V337.5c0-4.142-3.357-7.5-7.5-7.5s-7.5 3.358-7.5 7.5v28.458c-28.127-29.492-46.937-68.033-51.441-110.968zM192.5 420.179V405h85v15.106A187.644 187.644 0 0 1 235 425a189.427 189.427 0 0 1-42.5-4.821zm100-4.235V405h2.5c4.143 0 7.5-3.358 7.5-7.5s-3.357-7.5-7.5-7.5h-2.5V151.032l65 40.625v188.307a191.989 191.989 0 0 1-65 35.98z'/%3E%3Cpath d='M325 320a7.5 7.5 0 0 0-7.5 7.5v10c0 4.142 3.357 7.5 7.5 7.5s7.5-3.358 7.5-7.5v-10a7.5 7.5 0 0 0-7.5-7.5zM325 280a7.5 7.5 0 0 0-7.5 7.5v10c0 4.142 3.357 7.5 7.5 7.5s7.5-3.358 7.5-7.5v-10a7.5 7.5 0 0 0-7.5-7.5zM325 240a7.5 7.5 0 0 0-7.5 7.5v10c0 4.142 3.357 7.5 7.5 7.5s7.5-3.358 7.5-7.5v-10a7.5 7.5 0 0 0-7.5-7.5zM325 200a7.5 7.5 0 0 0-7.5 7.5v10c0 4.142 3.357 7.5 7.5 7.5s7.5-3.358 7.5-7.5v-10a7.5 7.5 0 0 0-7.5-7.5zM145 345a7.5 7.5 0 0 0 7.5-7.5v-10c0-4.142-3.357-7.5-7.5-7.5s-7.5 3.358-7.5 7.5v10a7.5 7.5 0 0 0 7.5 7.5zM145 305a7.5 7.5 0 0 0 7.5-7.5v-10c0-4.142-3.357-7.5-7.5-7.5s-7.5 3.358-7.5 7.5v10a7.5 7.5 0 0 0 7.5 7.5zM145 265a7.5 7.5 0 0 0 7.5-7.5v-10c0-4.142-3.357-7.5-7.5-7.5s-7.5 3.358-7.5 7.5v10a7.5 7.5 0 0 0 7.5 7.5zM145 185a7.5 7.5 0 0 0 7.5-7.5v-10c0-4.142-3.357-7.5-7.5-7.5s-7.5 3.358-7.5 7.5v10a7.5 7.5 0 0 0 7.5 7.5zM145 225a7.5 7.5 0 0 0 7.5-7.5v-10c0-4.142-3.357-7.5-7.5-7.5s-7.5 3.358-7.5 7.5v10a7.5 7.5 0 0 0 7.5 7.5zM235 350a7.5 7.5 0 0 0-7.5 7.5v10c0 4.142 3.357 7.5 7.5 7.5s7.5-3.358 7.5-7.5v-10a7.5 7.5 0 0 0-7.5-7.5zM235 310a7.5 7.5 0 0 0-7.5 7.5v10c0 4.142 3.357 7.5 7.5 7.5s7.5-3.358 7.5-7.5v-10a7.5 7.5 0 0 0-7.5-7.5zM235 270a7.5 7.5 0 0 0-7.5 7.5v10c0 4.142 3.357 7.5 7.5 7.5s7.5-3.358 7.5-7.5v-10a7.5 7.5 0 0 0-7.5-7.5zM235 230a7.5 7.5 0 0 0-7.5 7.5v10c0 4.142 3.357 7.5 7.5 7.5s7.5-3.358 7.5-7.5v-10a7.5 7.5 0 0 0-7.5-7.5zM235 190a7.5 7.5 0 0 0-7.5 7.5v10c0 4.142 3.357 7.5 7.5 7.5s7.5-3.358 7.5-7.5v-10a7.5 7.5 0 0 0-7.5-7.5zM215 145h40c4.143 0 7.5-3.358 7.5-7.5s-3.357-7.5-7.5-7.5h-40c-4.143 0-7.5 3.358-7.5 7.5s3.357 7.5 7.5 7.5z'/%3E%3C/svg%3E"); +} + +.emoji-picker nav a.flags i { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 502 502'%3E%3Cpath d='M428.484 73.516C381.076 26.108 318.044 0 251 0S120.924 26.108 73.516 73.516 0 183.956 0 251s26.108 130.076 73.516 177.484S183.956 502 251 502s130.076-26.108 177.484-73.516C475.892 381.076 502 318.044 502 251s-26.108-130.076-73.516-177.484zM283.829 39h58.868c58.354 25.338 104.274 74.079 125.849 134.317h-41.725a21.139 21.139 0 0 0-19.587 13.087 21.139 21.139 0 0 0 4.595 23.104l3.3 3.3c4.638 4.637 4.638 12.184 0 16.821a11.42 11.42 0 0 1-8.13 3.368 11.422 11.422 0 0 1-8.13-3.368l-7.969-7.969c-13.135-13.135-30.599-20.369-49.175-20.369h-6.397v-8.036c0-19.265-7.502-37.376-21.124-50.999l-9.952-9.952c-10.216-10.216-23.799-15.843-38.247-15.843h-19.931c-7.721 0-14.98 3.007-20.439 8.466l-5.17 5.169c-5.459 5.459-8.466 12.718-8.466 20.439a4.736 4.736 0 0 1-4.73 4.73h-8.66v-12.154c0-8.648 3.368-16.78 9.483-22.895l5.849-5.849c5.244-5.243 8.131-12.214 8.131-19.629V92.71c0-.394.32-.713.713-.713H320.5c12.407 0 22.5-10.093 22.5-22.5S332.907 47 320.5 47h-36.671c-2.206 0-4-1.794-4-4s1.794-4 4-4zm74.893 252.437l-5.452 5.484a155.066 155.066 0 0 0-22.913 29.41l-9.918 16.5-12.403 20.492a48.673 48.673 0 0 0-7.036 25.21v.615a.857.857 0 0 1-.856.856h-.004a8.78 8.78 0 0 1-6.247-2.586 8.776 8.776 0 0 1-2.589-6.25c0-12.58-4.899-24.407-13.794-33.303l-4.591-4.591c-6.947-6.947-10.773-16.183-10.773-26.007v-29.475c0-14.806-12.045-26.851-26.852-26.851H231.8c-8.349 0-15.142-6.792-15.142-15.142v-15.343c0-9.034 7.35-16.384 16.384-16.384h79.886l24.099 24.1c6.003 6.003 9.309 13.984 9.309 22.473v11.464c0 8.56 5.082 15.955 12.386 19.328zM20 251c0-9.444.583-18.752 1.69-27.902h30.619c10.153 0 19.698 3.954 26.876 11.133l8.781 8.78c7.527 7.527 17.534 11.672 28.179 11.672 5.65 0 10.962 2.2 14.957 6.195l.193.193c7.233 7.233 11.217 16.851 11.217 27.081v17.886c0 13.63-5.308 26.444-14.945 36.082l-19.15 19.15c-13.442 13.443-21.939 30.512-24.58 49.002C44.303 368.799 20 312.684 20 251zm231 231c-56.288 0-107.93-20.247-148.049-53.827v-5.423c0-17.881 6.963-34.693 19.607-47.337l19.15-19.15c13.415-13.416 20.803-31.252 20.803-50.224v-17.886c0-15.573-6.064-30.213-17.075-41.224l-.193-.192c-7.772-7.772-18.106-12.053-29.099-12.053a19.72 19.72 0 0 1-14.036-5.814l-8.781-8.781c-10.957-10.956-25.524-16.99-41.019-16.99h-27.3C47.126 98.635 140.047 20 251 20c7.743 0 15.396.39 22.946 1.138-8.316 3.774-14.117 12.151-14.117 21.862 0 13.234 10.766 24 24 24H320.5c1.378 0 2.5 1.122 2.5 2.5s-1.122 2.5-2.5 2.5h-97.713c-11.421 0-20.713 9.292-20.713 20.713v2.028a7.706 7.706 0 0 1-2.273 5.486l-5.85 5.85c-9.893 9.893-15.341 23.047-15.341 37.037v13.574c0 10.245 8.334 18.58 18.579 18.58h10.081c13.636 0 24.73-11.094 24.73-24.73 0-2.379.926-4.615 2.608-6.297l5.169-5.169c.203-.203.414-.393.632-.574.167.195.334.389.518.574l19.932 19.932c-3.833 3.911-3.813 10.186.068 14.068 1.953 1.953 4.512 2.929 7.071 2.929s5.119-.976 7.071-2.929l7-7c3.905-3.905 3.905-10.237 0-14.143l-15.45-15.45c8.875.156 17.197 3.677 23.489 9.97l9.953 9.952c9.844 9.844 15.266 22.934 15.266 36.856v.817H233.04c-20.062 0-36.384 16.322-36.384 36.384V245.8c0 19.377 15.765 35.142 35.142 35.142h3.493a6.86 6.86 0 0 1 6.852 6.851v29.475c0 15.167 5.906 29.425 16.63 40.15l4.591 4.591c5.118 5.118 7.937 11.923 7.937 19.161 0 7.705 3.001 14.948 8.451 20.396 5.446 5.443 12.685 8.44 20.384 8.44h.015C311.648 410 321 400.644 321 389.149v-.614a28.68 28.68 0 0 1 4.146-14.854l12.409-20.502a.226.226 0 0 1 .016-.026l9.928-16.517a135.064 135.064 0 0 1 19.955-25.613l11.147-11.213c4.428-4.455 5.731-11.08 3.319-16.879s-8.029-9.546-14.31-9.546a1.274 1.274 0 0 1-1.273-1.273v-11.464c0-13.832-5.386-26.835-15.167-36.616l-2.215-2.215c10.49 1.524 20.173 6.357 27.804 13.988l7.969 7.969c6.141 6.141 14.207 9.211 22.272 9.211s16.132-3.07 22.272-9.211c6.024-6.024 9.341-14.033 9.341-22.553 0-8.519-3.317-16.528-9.341-22.553l-3.3-3.3c-.198-.198-.567-.567-.26-1.308.307-.741.829-.741 1.109-.741h47.888C479.468 211.761 482 231.09 482 251c0 127.374-103.626 231-231 231z'/%3E%3Cpath d='M184 85c5.523 0 10-4.477 10-10V54.494c0-5.523-4.477-10-10-10s-10 4.477-10 10V75c0 5.523 4.477 10 10 10zM450.39 314.63c-5.176-1.93-10.935.702-12.863 5.877C408.652 397.961 333.692 450 251 450c-5.523 0-10 4.477-10 10s4.477 10 10 10c45.543 0 89.207-13.849 126.272-40.048 36.24-25.617 63.556-61.046 78.995-102.458 1.929-5.175-.702-10.934-5.877-12.864zM202.433 444.034a198.232 198.232 0 0 1-28.554-9.526c-5.092-2.144-10.954.249-13.096 5.339-2.142 5.09.249 10.954 5.339 13.096a218.202 218.202 0 0 0 31.445 10.491c.817.205 1.635.303 2.44.303 4.478 0 8.554-3.03 9.692-7.57 1.344-5.358-1.909-10.79-7.266-12.133z'/%3E%3C/svg%3E"); +} + +.emoji-picker nav a.symbols i { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 490.4 490.4'%3E%3Cpath d='M229 381.2c4.4 4.4 10.1 6.8 16.3 6.8 6.2 0 12-2.4 16.3-6.8l96.3-96.3c15.7-15.7 24.4-36.6 24.4-58.7 0-22.2-8.6-43.1-24.3-58.8-15.7-15.7-36.6-24.3-58.7-24.3-20 0-38.9 7-54 19.9-15.1-13-34.1-20-54.1-20-22.2 0-43 8.6-58.7 24.3s-24.3 36.6-24.3 58.8 8.7 43 24.4 58.7l96.4 96.4zm-79.3-196.7c11.1-11.1 25.7-17.1 41.4-17.1s30.4 6.1 41.5 17.2l4 4c4.8 4.8 12.5 4.8 17.3 0l3.9-3.9c11.1-11.1 25.8-17.2 41.5-17.2 15.6 0 30.3 6.1 41.4 17.2 11.1 11.1 17.2 25.8 17.1 41.4 0 15.7-6.1 30.4-17.2 41.5l-95.3 95.3-95.5-95.5c-11.1-11.1-17.2-25.8-17.2-41.4 0-15.7 6.1-30.4 17.1-41.5z'/%3E%3Cpath d='M245.2 490.4c135.2 0 245.2-110 245.2-245.2S380.4 0 245.2 0 0 110 0 245.2s110 245.2 245.2 245.2zm0-465.9c121.7 0 220.7 99 220.7 220.7s-99 220.7-220.7 220.7-220.7-99-220.7-220.7 99-220.7 220.7-220.7z'/%3E%3C/svg%3E"); +} + +.emoji-picker nav a:after { + content: ''; + position: absolute; + bottom: 0; + width: 100%; + left: 0; + height: 2px; + border-radius: 5px; + background-color: #90caf9; + opacity: 0; + transition-delay: 1s; + transition: opacity .4s, background .2s; +} + +.emoji-picker.people nav .people i { + opacity: 1; +} + +.emoji-picker.people nav .people:after { + opacity: 1; +} + +.emoji-picker.foods nav .foods i { + opacity: 1; +} + +.emoji-picker.foods nav .foods:after { + opacity: 1; +} + +.emoji-picker.nature nav .nature i { + opacity: 1; +} + +.emoji-picker.nature nav .nature:after { + opacity: 1; +} + +.emoji-picker.activity nav .activity i { + opacity: 1; +} + +.emoji-picker.activity nav .activity:after { + opacity: 1; +} + +.emoji-picker.objects nav .objects i { + opacity: 1; +} + +.emoji-picker.objects nav .objects:after { + opacity: 1; +} + +.emoji-picker.places nav .places i { + opacity: 1; +} + +.emoji-picker.places nav .places:after { + opacity: 1; +} + +.emoji-picker.flags nav .flags i { + opacity: 1; +} + +.emoji-picker.flags nav .flags:after { + opacity: 1; +} + +.emoji-picker.symbols nav .symbols i { + opacity: 1; +} + +.emoji-picker.symbols nav .symbols:after { + opacity: 1; +} + +.emoji-picker .diversity-picker { + background-color: #fafafa; + display: flex; + justify-content: space-around; + flex-direction: row-reverse; + position: absolute; + z-index: 3; + left: 0; + top: 0; + right: 0; + height: 30px; + transform: translateY(-30px); + opacity: .6; + transition: .2s opacity, .2s transform; +} + +.emoji-picker .diversity-picker.shown { + transform: translateY(0); + opacity: 1; + border-bottom: 1px solid #eeeeee; +} + +.emoji-picker .diversity-picker .emoji { + display: inline-block; +} + +.emoji-picker .emoji { + border-radius: 5px; + height: 32px; + width: 32px; + padding: 5px; + background-size: 20px; + background-repeat: no-repeat; + background-position: 50% 50%; + overflow: hidden; + transition: background-color .2s; + display: none; +} + +.emoji-picker .emoji.shown { + display: inline-block; +} + +.emoji-picker .emoji:hover { + background-color: #bbdefb; +} + +.emoji-picker .emoji:hover.has-diversities:before { + opacity: 1; +} + +.emoji-picker .emoji.has-diversities:before { + content: ''; + background-color: #f5f5f5; + display: block; + height: 8px; + width: 8px; + float: right; + opacity: 0; + transform: translateY(-6px) translateX(6px); + transition: opacity .2s; +} + +.emoji-picker .emoji-category { + position: relative; + padding: 30px 5px 0 5px; + margin: 0; + text-align: left; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-start; +} + +.emoji-picker .emoji-category .category-name { + background-color: rgba(255, 255, 255, 0.9); + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 0; + display: block; + padding: 0 10px; + font-size: 12px; + font-weight: 700; + letter-spacing: .1em; + text-transform: uppercase; + color: #bdbdbd; + height: 30px; + line-height: 30px; + box-sizing: border-box; + transition: color .2s; +} + +.emoji-picker.people .people.emoji-category { + position: initial; +} + +.emoji-picker.people .people.emoji-category .category-name { + color: #9e9e9e; +} + +.emoji-picker.foods .foods.emoji-category { + position: initial; +} + +.emoji-picker.foods .foods.emoji-category .category-name { + color: #9e9e9e; +} + +.emoji-picker.nature .nature.emoji-category { + position: initial; +} + +.emoji-picker.nature .nature.emoji-category .category-name { + color: #9e9e9e; +} + +.emoji-picker.activity .activity.emoji-category { + position: initial; +} + +.emoji-picker.activity .activity.emoji-category .category-name { + color: #9e9e9e; +} + +.emoji-picker.objects .objects.emoji-category { + position: initial; +} + +.emoji-picker.objects .objects.emoji-category .category-name { + color: #9e9e9e; +} + +.emoji-picker.places .places.emoji-category { + position: initial; +} + +.emoji-picker.places .places.emoji-category .category-name { + color: #9e9e9e; +} + +.emoji-picker.flags .flags.emoji-category { + position: initial; +} + +.emoji-picker.flags .flags.emoji-category .category-name { + color: #9e9e9e; +} + +.emoji-picker.regional .regional.emoji-category { + position: initial; +} + +.emoji-picker.regional .regional.emoji-category .category-name { + color: #9e9e9e; +} + +.emoji-picker.symbols .symbols.emoji-category { + position: initial; +} + +.emoji-picker.symbols .symbols.emoji-category .category-name { + color: #9e9e9e; +} + +.emoji-picker .emoji-list { + width: 100%; + height: 240px; + padding: 0; + overflow-x: hidden; + box-sizing: border-box; + -ms-overflow-style: none; +} + +.emoji-picker .emoji-list::-webkit-scrollbar { + height: 0; + width: 0; +} + +.emoji-picker .emoji-list::scrollbar { + height: 0; + width: 0; +} + +.emoji-picker .emoji-list.filter:before { + content: ''; + background: #ffffff; + position: absolute; + left: 0; + right: 0; + top: 0; + height: 30px; + z-index: 1; + opacity: 0.9; +} + +.emoji-picker .emoji-list.filter .category-name { + background: none; + z-index: 1; +} + +.emoji-picker .emoji-list:not(.filter) .emoji-category { + min-height: 100%; +} + +.emoji-picker .wrapper { + position: relative; + overflow: hidden; +} + +.emoji-picker .wrapper.no-results:before { + content: ''; + background-repeat: no-repeat; + background-image: url("data:image/svg+xml,%3Csvg id='Capa_1' xmlns='http://www.w3.org/2000/svg' width='310.4' height='310.4'%3E%3Cstyle%3E.st0%7Bfill:%239e9e9e%7D%3C/style%3E%3Cpath class='st0' d='M273.6 215c49.1-49.1 49.1-129 0-178.1-49.1-49.1-129-49.1-178.1 0-41.7 41.7-48 103.6-18.9 152 0 0 2.1 3.5-.7 6.3l-64.3 64.3c-12.8 12.8-15.8 30.7-4.5 42l2 2c11.3 11.3 29.2 8.3 42-4.5l64.1-64.1c3-3 6.4-.9 6.4-.9 48.4 28.9 110.3 22.6 152-19zm-154.9-23.3c-36.3-36.3-36.3-95.3 0-131.6s95.3-36.3 131.6 0 36.3 95.3 0 131.6-95.3 36.3-131.6 0z'/%3E%3Cpath class='st0' d='M126.8 118.4c-1.7 0-3.4-.3-5.1-1-6.6-2.8-9.7-10.4-6.9-17 17.6-41.6 65.7-61.1 107.3-43.5 6.6 2.8 9.7 10.4 6.9 17-2.8 6.6-10.4 9.7-17 6.9-28.4-12-61.2 1.3-73.2 29.7-2.2 4.9-7 7.9-12 7.9z'/%3E%3C/svg%3E"); + background-position: 50% 50%; + background-size: 100px 100px; + display: block; + width: 100px; + height: 100px; + position: absolute; + top: 50%; + left: 50%; + opacity: .07; + transform: translateX(-50%) translateY(-50%); +} + +.emoji-picker .wrapper.no-results .emoji-name { + display: none; +} + +.emoji-picker .wrapper:after { + content: ''; + background-color: #ffffff; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 5px; +} + +.emoji-picker .wrapper .scroller { + display: block; + position: absolute; + width: 5px; + padding-top: 5px; + z-index: 2; + right: 3px; + box-sizing: border-box; + opacity: 0; + transition: opacity .2s; +} + +.emoji-picker .wrapper .scroller.shown { + opacity: 1; +} + +.emoji-picker .wrapper .scroller div { + background-color: #bdbdbd; + width: 100%; + min-height: 12px; +} + +.emoji-picker .wrapper .emoji-name { + position: absolute; + right: 10px; + top: 8px; + font-size: 10px; + font-weight: 100; + z-index: 2; + color: #9e9e9e; + max-width: 130px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.emoji-picker .search-bar { + height: 45px; + margin-top: 0; + padding: 10px; + position: relative; + z-index: 2; + left: 0; + box-sizing: border-box; +} + +.emoji-picker .search-bar input { + padding: 5px 5px 5px 35px; + margin: 0; + box-sizing: border-box; + color: #9e9e9e; + width: 100%; + outline: none; + border: 1px solid #eeeeee; + border-radius: 5px; + transition: border .2s; +} + +.emoji-picker .search-bar input::placeholder { + color: #e0e0e0; +} + +.emoji-picker .search-bar input:focus { + border: 1px solid #e0e0e0; +} + +.emoji-picker .search-bar input:focus + i:before { + opacity: 1; +} + +.emoji-picker .search-bar i:before { + content: ''; + background-repeat: no-repeat; + background-image: url("data:image/svg+xml,%3Csvg id='Capa_1' xmlns='http://www.w3.org/2000/svg' width='310.4' height='310.4'%3E%3Cstyle%3E.st0%7Bfill:%239e9e9e%7D%3C/style%3E%3Cpath class='st0' d='M273.6 215c49.1-49.1 49.1-129 0-178.1-49.1-49.1-129-49.1-178.1 0-41.7 41.7-48 103.6-18.9 152 0 0 2.1 3.5-.7 6.3l-64.3 64.3c-12.8 12.8-15.8 30.7-4.5 42l2 2c11.3 11.3 29.2 8.3 42-4.5l64.1-64.1c3-3 6.4-.9 6.4-.9 48.4 28.9 110.3 22.6 152-19zm-154.9-23.3c-36.3-36.3-36.3-95.3 0-131.6s95.3-36.3 131.6 0 36.3 95.3 0 131.6-95.3 36.3-131.6 0z'/%3E%3Cpath class='st0' d='M126.8 118.4c-1.7 0-3.4-.3-5.1-1-6.6-2.8-9.7-10.4-6.9-17 17.6-41.6 65.7-61.1 107.3-43.5 6.6 2.8 9.7 10.4 6.9 17-2.8 6.6-10.4 9.7-17 6.9-28.4-12-61.2 1.3-73.2 29.7-2.2 4.9-7 7.9-12 7.9z'/%3E%3C/svg%3E"); + background-position: 50% 50%; + background-size: 15px 15px; + height: 15px; + width: 15px; + position: absolute; + left: 20px; + top: 15px; + display: block; + opacity: .3; + transition: opacity .2s; +} + +.emoji-picker { + background-color: #ffffff; + width: 285px; + display: block; + position: relative; + padding: 0; + border: 1px solid #eeeeee; + border-radius: 3px; + overflow: hidden; + font-family: sans-serif; +} + +.emoji-picker .hidden { + display: none !important; + visibility: hidden !important; + padding: 0 !important; + margin: 0 !important; +} + +.emoji-picker a { + outline: none; +} + +.emoji-picker ul { + list-style-type: none; + margin: 0; + padding: 0; +} + +.emoji-picker ul li { + display: inline-block; +} + +.emoji-picker .bar-wrapper { + position: relative; +} + +.emoji-text-field { + height: 100%; + position: relative; + flex: 1; +} + +.emoji-text-field input, +.emoji-text-field textarea { + width: 100%; + height: 100%; + margin: 0; + padding: 2px 20px 2px 2px; + box-sizing: border-box; + border: none; + box-shadow: none !important; + outline: none !important; +} + +.emoji-text-field.picker-hidden .emoji-picker { + display: none; + opacity: 0; + visibility: none; +} + +.emoji-text-field .emoji-picker { + position: absolute; + z-index: 1; + right: 0; + bottom: 30px; +} + +.emoji-text-field .emoji-trigger { + background-size: 16px 16px; + position: absolute; + z-index: 1; + bottom: 3px; + right: 5px; + opacity: .2; + transition: opacity .3s; +} diff --git a/front/odiparpack/app/styles/components/vendors/image-lightbox/image-lightbox.css b/front/odiparpack/app/styles/components/vendors/image-lightbox/image-lightbox.css new file mode 100644 index 0000000..9c4dfa3 --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/image-lightbox/image-lightbox.css @@ -0,0 +1,344 @@ +@keyframes closeWindow { + 0% { + opacity: 1; + } + 100% { + opacity: 0; + } +} + +.ril__outer { + background-color: rgba(0, 0, 0, 0.85); + outline: none; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1000; + width: 100%; + height: 100%; + -ms-content-zooming: none; + -ms-user-select: none; + -ms-touch-select: none; + touch-action: none; +} + +.ril__outerClosing { + opacity: 0; +} + +.ril__inner { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.ril__image, +.ril__imagePrev, +.ril__imageNext { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: auto; + max-width: none; + -ms-content-zooming: none; + -ms-user-select: none; + -ms-touch-select: none; + touch-action: none; +} + +.ril__imageDiscourager { + background-repeat: no-repeat; + background-position: center; + background-size: contain; +} + +.ril__navButtons { + border: none; + position: absolute; + top: 0; + bottom: 0; + width: 20px; + height: 34px; + padding: 40px 30px; + margin: auto; + cursor: pointer; + opacity: 0.7; +} +.ril__navButtons:hover { + opacity: 1; +} +.ril__navButtons:active { + opacity: 0.7; +} + +.ril__navButtonPrev { + left: 0; + background: rgba(0, 0, 0, 0.2) + url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwIiBoZWlnaHQ9IjM0Ij48cGF0aCBkPSJtIDE5LDMgLTIsLTIgLTE2LDE2IDE2LDE2IDEsLTEgLTE1LC0xNSAxNSwtMTUgeiIgZmlsbD0iI0ZGRiIvPjwvc3ZnPg==') + no-repeat center; +} + +.ril__navButtonNext { + right: 0; + background: rgba(0, 0, 0, 0.2) + url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwIiBoZWlnaHQ9IjM0Ij48cGF0aCBkPSJtIDEsMyAyLC0yIDE2LDE2IC0xNiwxNiAtMSwtMSAxNSwtMTUgLTE1LC0xNSB6IiBmaWxsPSIjRkZGIi8+PC9zdmc+') + no-repeat center; +} + +.ril__downloadBlocker { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-image: url('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'); + background-size: cover; +} + +.ril__caption, +.ril__toolbar { + background-color: rgba(0, 0, 0, 0.5); + position: absolute; + left: 0; + right: 0; + display: flex; + justify-content: space-between; +} + +.ril__caption { + bottom: 0; + max-height: 150px; + overflow: auto; +} + +.ril__captionContent { + padding: 10px 20px; + color: #fff; +} + +.ril__toolbar { + top: 0; + height: 50px; +} + +.ril__toolbarSide { + height: 50px; + margin: 0; +} + +.ril__toolbarLeftSide { + padding-left: 20px; + padding-right: 0; + flex: 0 1 auto; + overflow: hidden; + text-overflow: ellipsis; +} + +.ril__toolbarRightSide { + padding-left: 0; + padding-right: 20px; + flex: 0 0 auto; +} + +.ril__toolbarItem { + display: inline-block; + line-height: 50px; + padding: 0; + color: #fff; + font-size: 120%; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.ril__toolbarItemChild { + vertical-align: middle; +} + +.ril__builtinButton { + width: 40px; + height: 35px; + cursor: pointer; + border: none; + opacity: 0.7; +} +.ril__builtinButton:hover { + opacity: 1; +} +.ril__builtinButton:active { + outline: none; +} + +.ril__builtinButtonDisabled { + cursor: default; + opacity: 0.5; +} +.ril__builtinButtonDisabled:hover { + opacity: 0.5; +} + +.ril__closeButton { + background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIj48cGF0aCBkPSJtIDEsMyAxLjI1LC0xLjI1IDcuNSw3LjUgNy41LC03LjUgMS4yNSwxLjI1IC03LjUsNy41IDcuNSw3LjUgLTEuMjUsMS4yNSAtNy41LC03LjUgLTcuNSw3LjUgLTEuMjUsLTEuMjUgNy41LC03LjUgLTcuNSwtNy41IHoiIGZpbGw9IiNGRkYiLz48L3N2Zz4=') + no-repeat center; +} + +.ril__zoomInButton { + background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PGcgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTEgMTlsNi02Ii8+PHBhdGggZD0iTTkgOGg2Ii8+PHBhdGggZD0iTTEyIDV2NiIvPjwvZz48Y2lyY2xlIGN4PSIxMiIgY3k9IjgiIHI9IjciIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+') + no-repeat center; +} + +.ril__zoomOutButton { + background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PGcgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTEgMTlsNi02Ii8+PHBhdGggZD0iTTkgOGg2Ii8+PC9nPjxjaXJjbGUgY3g9IjEyIiBjeT0iOCIgcj0iNyIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4=') + no-repeat center; +} + +.ril__outerAnimating { + animation-name: closeWindow; +} + +@keyframes pointFade { + 0%, + 19.999%, + 100% { + opacity: 0; + } + 20% { + opacity: 1; + } +} + +.ril__loadingCircle { + width: 60px; + height: 60px; + position: relative; +} + +.ril__loadingCirclePoint { + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; +} +.ril__loadingCirclePoint::before { + content: ''; + display: block; + margin: 0 auto; + width: 11%; + height: 30%; + background-color: #fff; + border-radius: 30%; + animation: pointFade 800ms infinite ease-in-out both; +} +.ril__loadingCirclePoint:nth-of-type(1) { + transform: rotate(0deg); +} +.ril__loadingCirclePoint:nth-of-type(7) { + transform: rotate(180deg); +} +.ril__loadingCirclePoint:nth-of-type(1)::before, +.ril__loadingCirclePoint:nth-of-type(7)::before { + animation-delay: -800ms; +} +.ril__loadingCirclePoint:nth-of-type(2) { + transform: rotate(30deg); +} +.ril__loadingCirclePoint:nth-of-type(8) { + transform: rotate(210deg); +} +.ril__loadingCirclePoint:nth-of-type(2)::before, +.ril__loadingCirclePoint:nth-of-type(8)::before { + animation-delay: -666ms; +} +.ril__loadingCirclePoint:nth-of-type(3) { + transform: rotate(60deg); +} +.ril__loadingCirclePoint:nth-of-type(9) { + transform: rotate(240deg); +} +.ril__loadingCirclePoint:nth-of-type(3)::before, +.ril__loadingCirclePoint:nth-of-type(9)::before { + animation-delay: -533ms; +} +.ril__loadingCirclePoint:nth-of-type(4) { + transform: rotate(90deg); +} +.ril__loadingCirclePoint:nth-of-type(10) { + transform: rotate(270deg); +} +.ril__loadingCirclePoint:nth-of-type(4)::before, +.ril__loadingCirclePoint:nth-of-type(10)::before { + animation-delay: -400ms; +} +.ril__loadingCirclePoint:nth-of-type(5) { + transform: rotate(120deg); +} +.ril__loadingCirclePoint:nth-of-type(11) { + transform: rotate(300deg); +} +.ril__loadingCirclePoint:nth-of-type(5)::before, +.ril__loadingCirclePoint:nth-of-type(11)::before { + animation-delay: -266ms; +} +.ril__loadingCirclePoint:nth-of-type(6) { + transform: rotate(150deg); +} +.ril__loadingCirclePoint:nth-of-type(12) { + transform: rotate(330deg); +} +.ril__loadingCirclePoint:nth-of-type(6)::before, +.ril__loadingCirclePoint:nth-of-type(12)::before { + animation-delay: -133ms; +} +.ril__loadingCirclePoint:nth-of-type(7) { + transform: rotate(180deg); +} +.ril__loadingCirclePoint:nth-of-type(13) { + transform: rotate(360deg); +} +.ril__loadingCirclePoint:nth-of-type(7)::before, +.ril__loadingCirclePoint:nth-of-type(13)::before { + animation-delay: 0ms; +} + +.ril__loadingContainer { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} +.ril__imagePrev .ril__loadingContainer, +.ril__imageNext .ril__loadingContainer { + display: none; +} + +.ril__errorContainer { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: flex; + align-items: center; + justify-content: center; + color: #fff; +} +.ril__imagePrev .ril__errorContainer, +.ril__imageNext .ril__errorContainer { + display: none; +} + +.ril__loadingContainer__icon { + color: #fff; + position: absolute; + top: 50%; + left: 50%; + transform: translateX(-50%) translateY(-50%); +} diff --git a/front/odiparpack/app/styles/components/vendors/react-animated-slider/react-animated-slider.css b/front/odiparpack/app/styles/components/vendors/react-animated-slider/react-animated-slider.css new file mode 100644 index 0000000..135da22 --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/react-animated-slider/react-animated-slider.css @@ -0,0 +1,373 @@ +.slider { + position: relative; + width: 100%; + height: 400px; + overflow: hidden +} + +.slider a.previousButton, .slider a.nextButton { + font-size: 22px; + line-height: 0; + display: block; + position: absolute; + top: 50%; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); + -webkit-transition: all .3s linear; + transition: all .3s linear; + z-index: 1; + color: #333; + padding: 10px; + text-decoration: none; + -webkit-backface-visibility: hidden; + backface-visibility: hidden + /* prevent jump effect when scaling */ +} + +.slider a.previousButton:not(.disabled):hover, .slider a.nextButton:not(.disabled):hover { + -webkit-transform: translateY(-50%) scale(1.25); + transform: translateY(-50%) scale(1.25); + cursor: pointer; +} + +.slider a.previousButton { + left: 20px; +} + +.slider a.nextButton { + right: 20px; +} + +.slide { + width: 100%; + height: 100%; + position: absolute; + overflow: hidden +} + +.slide.hidden { + visibility: hidden; +} + +.slide.previous { + left: -100%; +} + +.slide.current { + left: 0; +} + +.slide.next { + left: 100%; +} + +.slide.animateIn, + .slide.animateOut { + -webkit-transition: all 2s ease; + transition: all 2s ease; +} + +.slide.animateIn.previous, + .slide.animateIn.next { + left: 0; + visibility: visible; +} + +.slide.animateOut.previous { + left: 100%; +} + +.slide.animateOut.next { + left: -100%; +} + +.slide h1, .slide h3 { + transition: all 0.3s ease; + -webkit-transform: translateY(-20px); + transform: translateY(-20px); + opacity: 0; +} + +.slide button { + transition: all 0.3s ease; + -webkit-transform: translateY(20px); + transform: translateY(20px); + opacity: 0; +} + +.slide p { + transition: all 0.3s ease; + -webkit-transform: translateY(20px); + transform: translateY(20px); + opacity: 0; +} + +.slide section * { + transition: all 0.3s ease; +} + +.slide section img { + -webkit-transform: translateX(-10px); + transform: translateX(-10px); + opacity: 0; +} + +.slide section span { + -webkit-transform: translateY(-10px); + transform: translateY(-10px); + opacity: 0; +} + +.slide section span strong { + -webkit-transform: translateY(10px); + transform: translateY(10px); + opacity: 0; + font-weight: 500 +} + +.slide.animateIn.previous h1, +.slide.animateIn.previous h3, +.slide.current h1, +.slide.current h3, +.slide.animateIn.next h1, +.slide.animateIn.next h3, +.slide.animateIn.previous button, +.slide.current button, +.slide.animateIn.next button, +.slide.animateIn.previous p, +.slide.current p, +.slide.animateIn.next p, +.slide.animateIn.previous section *, +.slide.current section *, +.slide.animateIn.next section * { + -webkit-transform: translateX(0); + transform: translateX(0); + -webkit-transition-delay: .9s; + transition-delay: .9s; + opacity: 1; +} + +.slide.animateIn.previous p, +.slide.animateIn.next p { + -webkit-transition-delay: 1.1s; + transition-delay: 1.1s; +} + +.slide.animateIn.previous button, +.slide.animateIn.next button { + -webkit-transition-delay: 1.3s; + transition-delay: 1.3s; +} + +.slide.animateIn.previous section img, +.slide.animateIn.next section img { + -webkit-transition-delay: 1.3s; + transition-delay: 1.3s; +} + +.slide.animateIn.previous section span, +.slide.animateIn.next section span { + -webkit-transition-delay: 1.4s; + transition-delay: 1.4s; +} + +.slide.animateIn.previous section span strong, +.slide.animateIn.next section span strong { + -webkit-transition-delay: 1.5s; + transition-delay: 1.5s; +} + +.slide.animateOut h1 { + -webkit-transition-delay: .3s; + transition-delay: .3s; +} + +.slide.animateOut p { + -webkit-transition-delay: .2s; + transition-delay: .2s; +} + +.slide.animateOut section span { + -webkit-transition-delay: .1s; + transition-delay: .1s; +} + +.slide.animateOut section span strong { + -webkit-transition-delay: 0s; + transition-delay: 0s; +} + +.slide { + height: 70vh; + background-size: cover !important; +} + +.slide::before { + content: ''; + display: block; + position: absolute; + width: 100%; + height: 100%; + background: -webkit-gradient(linear, left top, left bottom, from(transparent), to(rgba(0, 0, 0, 0.9))); + background: linear-gradient(transparent, rgba(0, 0, 0, 0.9)); + bottom: 0; + left: 0; +} + +.previousButton, +.nextButton { + position: absolute; + top: 50%; + transform: translateY(-50%); + z-index: 10; + width: 32px; + height: 32px; + transition: all .3s ease; + cursor: pointer; + text-align: center; +} + +.previousButton svg polygon, +.nextButton svg polygon { + fill: #fff; + opacity: .5; +} + +.previousButton { + left: 10px; +} + +.previousButton:hover { + left: 5px; +} + +.nextButton { + right: 10px; +} + +.nextButton:hover { + right: 5px; +} + +.slider-content { + text-align: center; +} + +.slider-content .inner { + padding: 0 70px; + box-sizing: border-box; + position: absolute; + width: 100%; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} + +.slider-content .inner button { + -webkit-transition-delay: 0s; + transition-delay: 0s; +} + +.slider-content .inner h1, +.slider-content .inner h3 { + margin-left: auto; + margin-right: auto; + max-width: 840px; + color: #FFFFFF; + font-size: 64px; + line-height: 1; +} + +.slider-content .inner p { + color: #FFFFFF; + font-size: 14px; + line-height: 1.5; + margin: 20px auto 30px; + max-width: 640px; +} + +.slider-content section { + position: absolute; + bottom: 20px; + left: 20px; +} + +.slider-content section span { + color: #FFFFFF; +} + +.slider-content section span { + color: rgba(255, 255, 255, 0.5); + font-size: 12px; + display: inline-block; + text-align: left; + line-height: 1.4; + vertical-align: middle; + margin-left: 10px; +} + +.slider-content section img { + width: 40px; + height: 40px; + border: solid 2px rgba(255, 255, 255, 0.5); + border-radius: 100%; + vertical-align: middle; +} + +.slider-content section span strong { + color: #FFFFFF; + font-size: 14px; + display: block; +} + +.slider-wrapper{ + position: relative; + overflow: hidden; + border-radius: 4px; +} + +.slider-wrapper, +.slide { + height: calc(100vh - 75px); +} + +.slider-wrapper.medium, +.slider-wrapper.medium .slide { + height: 500px; +} + +.slider-wrapper.short, +.slider-wrapper.short .slide { + height: 261px; +} + +.slider-content .inner h1 { + font-size: 32px; +} + +.slider-content .inner h3 { + font-size: 24px; +} + +@media (max-width: 640px) { + .slider-wrapper, + .slide { + height: calc(80vh - 75px); + } + .custom-nav .slick-dots{ + visibility: hidden; + } +} + +@media (max-width: 480px) { + .slider-content .inner h3, + .slider-content .inner h1 { + font-size: 18px; + line-height: 24px + } + .slider-content .inner { + padding: 0 40px + } +} + diff --git a/front/odiparpack/app/styles/components/vendors/react-big-calendar/react-big-calendar.css b/front/odiparpack/app/styles/components/vendors/react-big-calendar/react-big-calendar.css new file mode 100644 index 0000000..6562da2 --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/react-big-calendar/react-big-calendar.css @@ -0,0 +1,665 @@ +.rbc-btn { + color: inherit; + font: inherit; + margin: 0; +} +button.rbc-btn { + overflow: visible; + text-transform: none; + -webkit-appearance: button; + cursor: pointer; +} +button[disabled].rbc-btn { + cursor: not-allowed; +} +button.rbc-input::-moz-focus-inner { + border: 0; + padding: 0; +} +.rbc-calendar { + -webkit-box-sizing: border-box; + box-sizing: border-box; + height: 100%; + display: -ms-flexbox; + display: -webkit-box; + display: flex; + -ms-flex-direction: column; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + flex-direction: column; + -ms-flex-align: stretch; + -webkit-box-align: stretch; + align-items: stretch; +} +.rbc-calendar *, +.rbc-calendar *:before, +.rbc-calendar *:after { + -webkit-box-sizing: inherit; + box-sizing: inherit; +} +.rbc-abs-full, +.rbc-row-bg { + overflow: hidden; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} +.rbc-ellipsis, +.rbc-event-label, +.rbc-row-segment .rbc-event-content, +.rbc-show-more { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.rbc-rtl { + direction: rtl; +} +.rbc-off-range { + color: #cccccc; +} +.rbc-off-range-bg { + background: #f7f7f7; +} +.rbc-header { + overflow: hidden; + -ms-flex: 1 0 0%; + -webkit-box-flex: 1; + flex: 1 0 0%; + text-overflow: ellipsis; + white-space: nowrap; + padding: 5px 3px; + text-align: right; + vertical-align: middle; + font-weight: 400; + font-size: 18px; + min-height: 0; + background: #eaeaea; + color: #757575; +} +.rbc-rtl .rbc-header + .rbc-header { + border-left-width: 0; +} +.rbc-header > a, +.rbc-header > a:active, +.rbc-header > a:visited { + color: inherit; + text-decoration: none; +} +.rbc-row-content { + position: relative; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-user-select: none; + z-index: 4; +} +.rbc-today { + background-color: #eaf6ff; +} +.rbc-toolbar { + margin-bottom: 10px; + font-size: 16px; + display: -ms-flexbox; + display: -webkit-box; + display: flex; + -ms-flex-align: center; + -webkit-box-align: center; + align-items: center; +} +.rbc-toolbar .rbc-toolbar-label { + -ms-flex-positive: 1; + -webkit-box-flex: 1; + flex-grow: 1; + padding: 10px; + text-align: center; +} +.rbc-toolbar button { + color: #373a3c; + display: inline-block; + margin: 0; + text-align: center; + vertical-align: middle; + background: #EEEEEE; + border: 1px solid #f9f9f9; + background-image: none; + padding: .375rem 1rem; + border-radius: 2px; + line-height: normal; + white-space: nowrap; + font-weight: 400; + font-size: 14px; + text-transform: capitalize; +} +.rbc-toolbar button:active, +.rbc-toolbar button.rbc-active { + background-color: #2196F3 !important; + color: #FFF; +} +.rbc-toolbar button:hover { + color: #373a3c; + background-color: #e6e6e6; +} +.rbc-btn-group { + display: inline-block; + white-space: nowrap; +} +.rbc-btn-group > button:first-child:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.rbc-btn-group > button:last-child:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.rbc-rtl .rbc-btn-group > button:first-child:not(:last-child) { + border-radius: 4px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.rbc-rtl .rbc-btn-group > button:last-child:not(:first-child) { + border-radius: 4px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.rbc-btn-group > button:not(:first-child):not(:last-child) { + border-radius: 0; +} +.rbc-btn-group button + button { + margin-left: -1px; +} +.rbc-rtl .rbc-btn-group button + button { + margin-left: 0; + margin-right: -1px; +} +.rbc-btn-group + .rbc-btn-group, +.rbc-btn-group + button { + margin-left: 10px; +} +.rbc-event { + padding: 5px 8px; + background-color: #3174ad; + border-radius: 2px; + color: #3a3a3a; + cursor: pointer; + font-size: 13px; +} +.rbc-slot-selecting .rbc-event { + cursor: inherit; + pointer-events: none; +} +.rbc-event.rbc-selected { + background-color: #265985; +} +.rbc-event-label { + font-size: 80%; +} +.rbc-event-overlaps { + -webkit-box-shadow: -1px 1px 5px 0px rgba(51, 51, 51, 0.5); + box-shadow: -1px 1px 5px 0px rgba(51, 51, 51, 0.5); +} +.rbc-event-continues-prior { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.rbc-event-continues-after { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.rbc-event-continues-earlier { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.rbc-event-continues-later { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} +.rbc-event-continues-day-after { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} +.rbc-event-continues-day-prior { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.rbc-row { + display: -ms-flexbox; + display: -webkit-box; + display: flex; + -ms-flex-direction: row; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + flex-direction: row; +} +.rbc-row-segment { + padding: 0 1px 1px 1px; +} +.rbc-selected-cell { + background-color: rgba(0, 0, 0, 0.1); +} +.rbc-show-more { + z-index: 4; + font-weight: bold; + font-size: 85%; + height: auto; + color: #424242; + line-height: normal; + white-space: nowrap; +} +.rbc-month-view { + position: relative; + border: none; + display: -ms-flexbox; + display: -webkit-box; + display: flex; + -ms-flex-direction: column; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + flex-direction: column; + -ms-flex: 1 0 0px; + -webkit-box-flex: 1; + flex: 1 0 0; + width: 100%; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-user-select: none; + height: 100%; +} +.rbc-month-header { + display: -ms-flexbox; + display: -webkit-box; + display: flex; + -ms-flex-direction: row; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + flex-direction: row; +} +.rbc-month-row { + display: -ms-flexbox; + display: -webkit-box; + display: flex; + position: relative; + -ms-flex-direction: column; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + flex-direction: column; + -ms-flex: 1 0 0px; + -webkit-box-flex: 1; + flex: 1 0 0; + -ms-flex-preferred-size: 0px; + flex-basis: 0px; + overflow: hidden; + height: 100%; +} +.rbc-month-row + .rbc-month-row { + border-top: 1px solid #DDD; +} +.rbc-date-cell { + -ms-flex: 1 1 0px; + -webkit-box-flex: 1; + flex: 1 1 0; + min-width: 0; + padding-right: 5px; + text-align: right; +} +.rbc-date-cell.rbc-now { + font-weight: bold; +} +.rbc-date-cell > a, +.rbc-date-cell > a:active, +.rbc-date-cell > a:visited { + color: inherit; + text-decoration: none; +} +.rbc-row-bg { + display: -ms-flexbox; + display: -webkit-box; + display: flex; + -ms-flex-direction: row; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + flex-direction: row; + -ms-flex: 1 0 0px; + -webkit-box-flex: 1; + flex: 1 0 0; + overflow: hidden; +} +.rbc-day-bg { + -ms-flex: 1 0 0%; + -webkit-box-flex: 1; + flex: 1 0 0%; +} + +.rbc-overlay { + position: absolute; + z-index: 5; + border: 1px solid #e5e5e5; + background-color: #fff; + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.25); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.25); + padding: 10px; +} +.rbc-overlay > * + * { + margin-top: 1px; +} +.rbc-overlay-header { + border-bottom: 1px solid #e5e5e5; + margin: -10px -10px 5px -10px; + padding: 2px 10px; +} +.rbc-agenda-view { + display: -ms-flexbox; + display: -webkit-box; + display: flex; + -ms-flex-direction: column; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + flex-direction: column; + -ms-flex: 1 0 0px; + -webkit-box-flex: 1; + flex: 1 0 0; + overflow: auto; +} +.rbc-agenda-view table.rbc-agenda-table { + width: 100%; + border: 1px solid #DDD; + border-spacing: 0; + border-collapse: collapse; +} +.rbc-agenda-view table.rbc-agenda-table tbody > tr > td { + padding: 5px 10px; + vertical-align: top; +} +.rbc-agenda-view table.rbc-agenda-table .rbc-agenda-time-cell { + padding-left: 15px; + padding-right: 15px; + text-transform: lowercase; +} +.rbc-agenda-view table.rbc-agenda-table tbody > tr > td + td { + border-left: 1px solid #DDD; +} +.rbc-rtl .rbc-agenda-view table.rbc-agenda-table tbody > tr > td + td { + border-left-width: 0; + border-right: 1px solid #DDD; +} +.rbc-agenda-view table.rbc-agenda-table tbody > tr + tr { + border-top: 1px solid #DDD; +} +.rbc-agenda-view table.rbc-agenda-table thead > tr > th { + padding: 3px 5px; + text-align: left; + border-bottom: 1px solid #DDD; +} +.rbc-rtl .rbc-agenda-view table.rbc-agenda-table thead > tr > th { + text-align: right; +} +.rbc-agenda-time-cell { + text-transform: lowercase; +} +.rbc-agenda-time-cell .rbc-continues-after:after { + content: ' »'; +} +.rbc-agenda-time-cell .rbc-continues-prior:before { + content: '« '; +} +.rbc-agenda-date-cell, +.rbc-agenda-time-cell { + white-space: nowrap; +} +.rbc-agenda-event-cell { + width: 100%; +} +.rbc-time-column { + display: -ms-flexbox; + display: -webkit-box; + display: flex; + -ms-flex-direction: column; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + flex-direction: column; + min-height: 100%; +} +.rbc-time-column .rbc-timeslot-group { + -ms-flex: 1; + -webkit-box-flex: 1; + flex: 1; +} +.rbc-timeslot-group { + border-bottom: 1px solid #DDD; + min-height: 40px; + display: -ms-flexbox; + display: -webkit-box; + display: flex; + -ms-flex-flow: column nowrap; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + flex-flow: column nowrap; +} +.rbc-time-gutter, +.rbc-header-gutter { + -ms-flex: none; + -webkit-box-flex: 0; + flex: none; +} +.rbc-label { + padding: 0 5px; +} +.rbc-day-slot { + position: relative; +} +.rbc-day-slot .rbc-events-container { + bottom: 0; + left: 0; + position: absolute; + right: 10px; + top: 0; +} +.rbc-day-slot .rbc-events-container.rbc-is-rtl { + left: 10px; + right: 0; +} +.rbc-day-slot .rbc-event { + display: -ms-flexbox; + display: -webkit-box; + display: flex; + max-height: 100%; + min-height: 20px; + -ms-flex-flow: column wrap; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + flex-flow: column wrap; + -ms-flex-align: start; + -webkit-box-align: start; + align-items: flex-start; + overflow: hidden; + position: absolute; +} +.rbc-day-slot .rbc-event-label { + -ms-flex: none; + -webkit-box-flex: 0; + flex: none; + padding-right: 5px; + width: auto; +} +.rbc-day-slot .rbc-event-content { + width: 100%; + -ms-flex: 1 1 0px; + -webkit-box-flex: 1; + flex: 1 1 0; + word-wrap: break-word; + line-height: 1; + height: 100%; + min-height: 1em; +} +.rbc-day-slot .rbc-time-slot { + border-top: 1px solid #f7f7f7; +} +.rbc-time-slot { + -ms-flex: 1 0 0px; + -webkit-box-flex: 1; + flex: 1 0 0; + font-size: 11px; + padding: 3px 7px; + +} +.rbc-time-slot.rbc-now { + font-weight: bold; +} +.rbc-day-header { + text-align: center; +} +.rbc-slot-selection { + z-index: 10; + position: absolute; + background-color: rgba(0, 0, 0, 0.5); + color: white; + font-size: 75%; + width: 100%; + padding: 3px; +} +.rbc-slot-selecting { + cursor: move; +} +.rbc-time-view { + display: -ms-flexbox; + display: -webkit-box; + display: flex; + -ms-flex-direction: column; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + flex-direction: column; + -ms-flex: 1; + -webkit-box-flex: 1; + flex: 1; + width: 100%; + min-height: 0; +} +.rbc-time-view .rbc-time-gutter { + white-space: nowrap; +} +.rbc-time-view .rbc-allday-cell { + -webkit-box-sizing: content-box; + box-sizing: content-box; + width: 100%; + position: relative; +} +.rbc-time-view .rbc-allday-events { + position: relative; + z-index: 4; +} +.rbc-time-view .rbc-row { + -webkit-box-sizing: border-box; + box-sizing: border-box; + min-height: 20px; +} +.rbc-time-header { + display: -ms-flexbox; + display: -webkit-box; + display: flex; + -ms-flex: 0 0 auto; + -webkit-box-flex: 0; + flex: 0 0 auto; + -ms-flex-direction: row; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + flex-direction: row; +} +.rbc-time-header.rbc-overflowing { + border-right: 1px solid #DDD; +} +.rbc-rtl .rbc-time-header.rbc-overflowing { + border-right-width: 0; + border-left: 1px solid #DDD; +} +.rbc-time-header > .rbc-row:first-child { + border-bottom: 1px solid #DDD; +} +.rbc-time-header > .rbc-row.rbc-row-resource { + border-bottom: 1px solid #DDD; +} +.rbc-time-header-content { + -ms-flex: 1; + -webkit-box-flex: 1; + flex: 1; + min-width: 0; + -ms-flex-direction: column; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + flex-direction: column; + border-left: 1px solid #DDD; +} +.rbc-rtl .rbc-time-header-content { + border-left-width: 0; + border-right: 1px solid #DDD; +} +.rbc-time-content { + display: -ms-flexbox; + display: -webkit-box; + display: flex; + -ms-flex: 1 0 0%; + -webkit-box-flex: 1; + flex: 1 0 0%; + -ms-flex-align: start; + -webkit-box-align: start; + align-items: flex-start; + width: 100%; + border-top: 2px solid #DDD; + overflow-y: auto; + position: relative; +} +.rbc-time-content > .rbc-time-gutter { + -ms-flex: none; + -webkit-box-flex: 0; + flex: none; +} +.rbc-time-content > * + * > * { + border-left: 1px solid #DDD; +} +.rbc-rtl .rbc-time-content > * + * > * { + border-left-width: 0; + border-right: 1px solid #DDD; +} +.rbc-time-content > .rbc-day-slot { + width: 100%; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-user-select: none; +} +.rbc-current-time-indicator { + position: absolute; + z-index: 3; + height: 1px; + background-color: #74ad31; + pointer-events: none; +} + +@media only screen and (max-width: 600px) { + .rbc-toolbar { + flex-direction: column + } + .rbc-btn-group { + width: 100%; + text-align: center; + overflow: auto; + } + .rbc-header { + border-right: 1px solid #fff; + } + .rbc-header span { + display: block; + visibility: hidden; + text-align: center + } + .rbc-header span:first-letter { + visibility: visible + } +} + +.eventBlock { + line-height: 18px +}
\ No newline at end of file diff --git a/front/odiparpack/app/styles/components/vendors/react-draft-wysiwyg/react-draft-wysiwyg.css b/front/odiparpack/app/styles/components/vendors/react-draft-wysiwyg/react-draft-wysiwyg.css new file mode 100644 index 0000000..048a8f1 --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/react-draft-wysiwyg/react-draft-wysiwyg.css @@ -0,0 +1,852 @@ +.rdw-option-wrapper { + border: 1px solid #F1F1F1; + padding: 5px; + min-width: 25px; + height: 20px; + border-radius: 2px; + margin: 0 4px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + background: white; + text-transform: capitalize; +} +.rdw-option-wrapper:hover { + box-shadow: 1px 1px 0px #BFBDBD; +} +.rdw-option-wrapper:active { + box-shadow: 1px 1px 0px #BFBDBD inset; +} +.rdw-option-active { + box-shadow: 1px 1px 0px #BFBDBD inset; +} +.rdw-option-disabled { + opacity: 0.3; + cursor: default; +} +.rdw-dropdown-wrapper { + height: 30px; + background: white; + cursor: pointer; + border: 1px solid #F1F1F1; + border-radius: 2px; + margin: 0 3px; + text-transform: capitalize; + background: white; +} +.rdw-dropdown-wrapper:focus { + outline: none; +} +.rdw-dropdown-wrapper:hover { + box-shadow: 1px 1px 0px #BFBDBD; + background-color: #FFFFFF; +} +.rdw-dropdown-wrapper:active { + box-shadow: 1px 1px 0px #BFBDBD inset; +} +.rdw-dropdown-carettoopen { + height: 0px; + width: 0px; + position: absolute; + top: 35%; + right: 10%; + border-top: 6px solid black; + border-left: 5px solid transparent; + border-right: 5px solid transparent; +} +.rdw-dropdown-carettoclose { + height: 0px; + width: 0px; + position: absolute; + top: 35%; + right: 10%; + border-bottom: 6px solid black; + border-left: 5px solid transparent; + border-right: 5px solid transparent; +} +.rdw-dropdown-selectedtext { + display: flex; + position: relative; + height: 100%; + align-items: center; + padding: 0 5px; +} +.rdw-dropdown-optionwrapper { + z-index: 100; + position: relative; + border: 1px solid #F1F1F1; + width: 98%; + background: white; + border-radius: 2px; + margin: 0; + padding: 0; + max-height: 250px; + overflow-y: scroll; +} +.rdw-dropdown-optionwrapper:hover { + box-shadow: 1px 1px 0px #BFBDBD; + background-color: #FFFFFF; +} +.rdw-dropdownoption-default { + min-height: 25px; + display: flex; + align-items: center; + padding: 0 5px; +} +.rdw-dropdownoption-highlighted { + background: #F1F1F1; +} +.rdw-dropdownoption-active { + background: #f5f5f5; +} +.rdw-dropdownoption-disabled { + opacity: 0.3; + cursor: default; +} +.rdw-inline-wrapper { + display: flex; + align-items: center; + margin-bottom: 6px; +} +.rdw-inline-dropdown { + width: 50px; +} +.rdw-inline-dropdownoption { + height: 40px; + display: flex; + justify-content: center; +} +.rdw-block-wrapper { + display: flex; + align-items: center; + margin-bottom: 6px; +} +.rdw-block-dropdown { + width: 110px; +} +.rdw-fontsize-wrapper { + display: flex; + align-items: center; + margin-bottom: 6px; +} +.rdw-fontsize-dropdown { + min-width: 40px; +} +.rdw-fontsize-option { + display: flex; + justify-content: center; +} +.rdw-fontfamily-wrapper { + display: flex; + align-items: center; + margin-bottom: 6px; +} +.rdw-fontfamily-dropdown { + width: 115px; +} +.rdw-fontfamily-placeholder { + white-space: nowrap; + max-width: 90px; + overflow: hidden; + text-overflow: ellipsis; +} +.rdw-fontfamily-optionwrapper { + width: 140px; +} +.rdw-list-wrapper { + display: flex; + align-items: center; + margin-bottom: 6px; +} +.rdw-list-dropdown { + width: 50px; + z-index: 90; +} +.rdw-list-dropdownOption { + height: 40px; + display: flex; + justify-content: center; +} +.rdw-text-align-wrapper { + display: flex; + align-items: center; + margin-bottom: 6px; +} +.rdw-text-align-dropdown { + width: 50px; + z-index: 90; +} +.rdw-text-align-dropdownOption { + height: 40px; + display: flex; + justify-content: center; +} +.rdw-right-aligned-block { + text-align: right; +} +.rdw-left-aligned-block { + text-align: left !important; +} +.rdw-center-aligned-block { + text-align: center !important; +} +.rdw-justify-aligned-block { + text-align: justify !important; +} +.rdw-right-aligned-block > div { + display: inline-block; +} +.rdw-left-aligned-block > div { + display: inline-block; +} +.rdw-center-aligned-block > div { + display: inline-block; +} +.rdw-justify-aligned-block > div { + display: inline-block; +} +.rdw-colorpicker-wrapper { + display: flex; + align-items: center; + margin-bottom: 6px; + position: relative; +} +.rdw-colorpicker-modal { + position: absolute; + top: 35px; + left: 5px; + display: flex; + flex-direction: column; + width: 175px; + height: 175px; + border: 1px solid #F1F1F1; + padding: 15px; + border-radius: 2px; + z-index: 100; + background: white; + box-shadow: 3px 3px 5px #BFBDBD; +} +.rdw-colorpicker-modal-header { + display: flex; + padding-bottom: 5px; +} +.rdw-colorpicker-modal-style-label { + font-size: 15px; + width: 50%; + text-align: center; + cursor: pointer; + padding: 0 10px 5px; +} +.rdw-colorpicker-modal-style-label-active { + border-bottom: 2px solid #0a66b7; +} +.rdw-colorpicker-modal-options { + margin: 5px auto; + display: flex; + width: 100%; + height: 100%; + flex-wrap: wrap; + overflow: scroll; +} +.rdw-colorpicker-cube { + width: 22px; + height: 22px; + border: 1px solid #F1F1F1; +} +.rdw-colorpicker-option { + margin: 3px; + padding: 0; + min-height: 20px; + border: none; + width: 22px; + height: 22px; + min-width: 22px; + box-shadow: 1px 2px 1px #BFBDBD inset; +} +.rdw-colorpicker-option:hover { + box-shadow: 1px 2px 1px #BFBDBD; +} +.rdw-colorpicker-option:active { + box-shadow: -1px -2px 1px #BFBDBD; +} +.rdw-colorpicker-option-active { + box-shadow: 0px 0px 2px 2px #BFBDBD; +} +.rdw-link-wrapper { + display: flex; + align-items: center; + margin-bottom: 6px; + position: relative; +} +.rdw-link-dropdown { + width: 50px; +} +.rdw-link-dropdownOption { + height: 40px; + display: flex; + justify-content: center; +} +.rdw-link-dropdownPlaceholder { + margin-left: 8px; +} +.rdw-link-modal { + position: absolute; + top: 35px; + left: 5px; + display: flex; + flex-direction: column; + width: 235px; + height: 205px; + border: 1px solid #F1F1F1; + padding: 15px; + border-radius: 2px; + z-index: 100; + background: white; + box-shadow: 3px 3px 5px #BFBDBD; +} +.rdw-link-modal-label { + font-size: 15px; +} +.rdw-link-modal-input { + margin-top: 5px; + border-radius: 2px; + border: 1px solid #F1F1F1; + height: 25px; + margin-bottom: 15px; + padding: 0 5px; +} +.rdw-link-modal-input:focus { + outline: none; +} +.rdw-link-modal-buttonsection { + margin: 0 auto; +} +.rdw-link-modal-target-option { + margin-bottom: 20px; +} +.rdw-link-modal-target-option > span { + margin-left: 5px; +} +.rdw-link-modal-btn { + margin-left: 10px; + width: 75px; + height: 30px; + border: 1px solid #F1F1F1; + border-radius: 2px; + cursor: pointer; + background: white; + text-transform: capitalize; +} +.rdw-link-modal-btn:hover { + box-shadow: 1px 1px 0px #BFBDBD; +} +.rdw-link-modal-btn:active { + box-shadow: 1px 1px 0px #BFBDBD inset; +} +.rdw-link-modal-btn:focus { + outline: none !important; +} +.rdw-link-modal-btn:disabled { + background: #ece9e9; +} +.rdw-link-dropdownoption { + height: 40px; + display: flex; + justify-content: center; +} +.rdw-history-dropdown { + width: 50px; +} +.rdw-embedded-wrapper { + display: flex; + align-items: center; + margin-bottom: 6px; + position: relative; +} +.rdw-embedded-modal { + position: absolute; + top: 35px; + left: 5px; + display: flex; + flex-direction: column; + width: 235px; + height: 180px; + border: 1px solid #F1F1F1; + padding: 15px; + border-radius: 2px; + z-index: 100; + background: white; + justify-content: space-between; + box-shadow: 3px 3px 5px #BFBDBD; +} +.rdw-embedded-modal-header { + font-size: 15px; + display: flex; +} +.rdw-embedded-modal-header-option { + width: 50%; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} +.rdw-embedded-modal-header-label { + width: 95px; + border: 1px solid #f1f1f1; + margin-top: 5px; + background: #6EB8D4; + border-bottom: 2px solid #0a66b7; +} +.rdw-embedded-modal-link-section { + display: flex; + flex-direction: column; +} +.rdw-embedded-modal-link-input { + width: 88%; + height: 35px; + margin: 10px 0; + border: 1px solid #F1F1F1; + border-radius: 2px; + font-size: 15px; + padding: 0 5px; +} +.rdw-embedded-modal-link-input-wrapper { + display: flex; + align-items: center; +} +.rdw-embedded-modal-link-input:focus { + outline: none; +} +.rdw-embedded-modal-btn-section { + display: flex; + justify-content: center; +} +.rdw-embedded-modal-btn { + margin: 0 3px; + width: 75px; + height: 30px; + border: 1px solid #F1F1F1; + border-radius: 2px; + cursor: pointer; + background: white; + text-transform: capitalize; +} +.rdw-embedded-modal-btn:hover { + box-shadow: 1px 1px 0px #BFBDBD; +} +.rdw-embedded-modal-btn:active { + box-shadow: 1px 1px 0px #BFBDBD inset; +} +.rdw-embedded-modal-btn:focus { + outline: none !important; +} +.rdw-embedded-modal-btn:disabled { + background: #ece9e9; +} +.rdw-embedded-modal-size { + align-items: center; + display: flex; + margin: 8px 0; + justify-content: space-between; +} +.rdw-embedded-modal-size-input { + width: 80%; + height: 20px; + border: 1px solid #F1F1F1; + border-radius: 2px; + font-size: 12px; +} +.rdw-embedded-modal-size-input:focus { + outline: none; +} +.rdw-emoji-wrapper { + display: flex; + align-items: center; + margin-bottom: 6px; + position: relative; +} +.rdw-emoji-modal { + overflow: auto; + position: absolute; + top: 35px; + left: 5px; + display: flex; + flex-wrap: wrap; + width: 235px; + height: 180px; + border: 1px solid #F1F1F1; + padding: 15px; + border-radius: 2px; + z-index: 100; + background: white; + box-shadow: 3px 3px 5px #BFBDBD; +} +.rdw-emoji-icon { + margin: 2.5px; + height: 24px; + width: 24px; + cursor: pointer; + font-size: 22px; + display: flex; + justify-content: center; + align-items: center; +} +.rdw-spinner { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + width: 100%; +} +.rdw-spinner > div { + width: 12px; + height: 12px; + background-color: #333; + + border-radius: 100%; + display: inline-block; + -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; + animation: sk-bouncedelay 1.4s infinite ease-in-out both; +} +.rdw-spinner .rdw-bounce1 { + -webkit-animation-delay: -0.32s; + animation-delay: -0.32s; +} +.rdw-spinner .rdw-bounce2 { + -webkit-animation-delay: -0.16s; + animation-delay: -0.16s; +} +@-webkit-keyframes sk-bouncedelay { + 0%, 80%, 100% { -webkit-transform: scale(0) } + 40% { -webkit-transform: scale(1.0) } +} +@keyframes sk-bouncedelay { + 0%, 80%, 100% { + -webkit-transform: scale(0); + transform: scale(0); + } 40% { + -webkit-transform: scale(1.0); + transform: scale(1.0); + } +} +.rdw-image-wrapper { + display: flex; + align-items: center; + margin-bottom: 6px; + position: relative; +} +.rdw-image-modal { + position: absolute; + top: 35px; + left: 5px; + display: flex; + flex-direction: column; + width: 235px; + border: 1px solid #F1F1F1; + padding: 15px; + border-radius: 2px; + z-index: 100; + background: white; + box-shadow: 3px 3px 5px #BFBDBD; +} +.rdw-image-modal-header { + font-size: 15px; + margin: 10px 0; + display: flex; +} +.rdw-image-modal-header-option { + width: 50%; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} +.rdw-image-modal-header-label { + width: 80px; + background: #f1f1f1; + border: 1px solid #f1f1f1; + margin-top: 5px; +} +.rdw-image-modal-header-label-highlighted { + background: #6EB8D4; + border-bottom: 2px solid #0a66b7; +} +.rdw-image-modal-upload-option { + width: 100%; + color: gray; + cursor: pointer; + display: flex; + border: none; + font-size: 15px; + align-items: center; + justify-content: center; + background-color: #f1f1f1; + outline: 2px dashed gray; + outline-offset: -10px; + margin: 10px 0; + padding: 9px 0; +} +.rdw-image-modal-upload-option-highlighted { + outline: 2px dashed #0a66b7; +} +.rdw-image-modal-upload-option-label { + cursor: pointer; + height: 100%; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + padding: 15px; +} +.rdw-image-modal-upload-option-label span{ + padding: 0 20px; +} +.rdw-image-modal-upload-option-image-preview { + max-width: 100%; + max-height: 200px; +} +.rdw-image-modal-upload-option-input { + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; +} +.rdw-image-modal-url-section { + display: flex; + align-items: center; +} +.rdw-image-modal-url-input { + width: 90%; + height: 35px; + margin: 15px 0 12px; + border: 1px solid #F1F1F1; + border-radius: 2px; + font-size: 15px; + padding: 0 5px; +} +.rdw-image-modal-btn-section { + margin: 10px auto 0; +} +.rdw-image-modal-url-input:focus { + outline: none; +} +.rdw-image-modal-btn { + margin: 0 5px; + width: 75px; + height: 30px; + border: 1px solid #F1F1F1; + border-radius: 2px; + cursor: pointer; + background: white; + text-transform: capitalize; +} +.rdw-image-modal-btn:hover { + box-shadow: 1px 1px 0px #BFBDBD; +} +.rdw-image-modal-btn:active { + box-shadow: 1px 1px 0px #BFBDBD inset; +} +.rdw-image-modal-btn:focus { + outline: none !important; +} +.rdw-image-modal-btn:disabled { + background: #ece9e9; +} +.rdw-image-modal-spinner { + position: absolute; + top: -3px; + left: 0; + width: 100%; + height: 100%; + opacity: 0.5; +} +.rdw-image-modal-alt-input { + width: 70%; + height: 20px; + border: 1px solid #F1F1F1; + border-radius: 2px; + font-size: 12px; + margin-left: 5px; +} +.rdw-image-modal-alt-input:focus { + outline: none; +} +.rdw-image-modal-alt-lbl { + font-size: 12px; +} +.rdw-image-modal-size { + align-items: center; + display: flex; + margin: 8px 0; + justify-content: space-between; +} +.rdw-image-modal-size-input { + width: 40%; + height: 20px; + border: 1px solid #F1F1F1; + border-radius: 2px; + font-size: 12px; +} +.rdw-image-modal-size-input:focus { + outline: none; +} +.rdw-image-mandatory-sign { + color: red; + margin-left: 3px; + margin-right: 3px; +} +.rdw-remove-wrapper { + display: flex; + align-items: center; + margin-bottom: 6px; + position: relative; +} +.rdw-history-wrapper { + display: flex; + align-items: center; + margin-bottom: 6px; +} +.rdw-history-dropdownoption { + height: 40px; + display: flex; + justify-content: center; +} +.rdw-history-dropdown { + width: 50px; +} +.rdw-link-decorator-wrapper { + position: relative; +} +.rdw-link-decorator-icon { + position: absolute; + left: 40%; + top: 0; + cursor: pointer; + background-color: white; +} +.rdw-mention-link { + text-decoration: none; + color: #1236ff; + background-color: #f0fbff; + padding: 1px 2px; + border-radius: 2px; +} +.rdw-suggestion-wrapper { + position: relative; +} +.rdw-suggestion-dropdown { + position: absolute; + display: flex; + flex-direction: column; + border: 1px solid #F1F1F1; + min-width: 100px; + max-height: 150px; + overflow: auto; + background: white; + z-index: 100; +} +.rdw-suggestion-option { + padding: 7px 5px; + border-bottom: 1px solid #f1f1f1; +} +.rdw-suggestion-option-active { + background-color: #F1F1F1; +} +.rdw-hashtag-link { + text-decoration: none; + color: #1236ff; + background-color: #f0fbff; + padding: 1px 2px; + border-radius: 2px; +} +.rdw-image-alignment-options-popup { + position: absolute;; + background: white; + display: flex; + padding: 5px 2px; + border-radius: 2px; + border: 1px solid #F1F1F1; + width: 105px; + cursor: pointer; + z-index: 100; +} +.rdw-alignment-option-left { + justify-content: flex-start; +} +.rdw-image-alignment-option { + height: 15px; + width: 15px; + min-width: 15px; +} +.rdw-image-alignment { + position: relative; +} +.rdw-image-imagewrapper { + position: relative; +} +.rdw-image-center { + display: flex; + justify-content: center; +} +.rdw-image-left { + display: flex; +} +.rdw-image-right { + display: flex; + justify-content: flex-end; +} +.rdw-image-alignment-options-popup-right { + right: 0; +} +.rdw-editor-main { + height: 100%; + overflow: auto; + box-sizing: border-box; +} +.rdw-editor-toolbar { + padding: 6px 5px 0; + border-radius: 2px; + border: 1px solid #F1F1F1; + display: flex; + justify-content: flex-start; + background: white; + flex-wrap: wrap; + font-size: 15px; + margin-bottom: 5px; + user-select: none; +} +.public-DraftStyleDefault-block { + margin: 1em 0; +} +.rdw-editor-wrapper:focus { + outline: none; +} +.rdw-editor-wrapper { + box-sizing: content-box; +} +.rdw-editor-main blockquote { + border-left: 5px solid #f1f1f1; + padding-left: 5px; +} +.rdw-editor-main pre { + background: #f1f1f1; + border-radius: 3px; + padding: 1px 10px; +}/** + * Draft v0.9.1 + * + * Copyright (c) 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +.DraftEditor-editorContainer,.DraftEditor-root,.public-DraftEditor-content{height:inherit;text-align:initial}.public-DraftEditor-content[contenteditable=true]{-webkit-user-modify:read-write-plaintext-only}.DraftEditor-root{position:relative}.DraftEditor-editorContainer{background-color:rgba(255,255,255,0);border-left:.1px solid transparent;position:relative;z-index:1}.public-DraftEditor-block{position:relative}.DraftEditor-alignLeft .public-DraftStyleDefault-block{text-align:left}.DraftEditor-alignLeft .public-DraftEditorPlaceholder-root{left:0;text-align:left}.DraftEditor-alignCenter .public-DraftStyleDefault-block{text-align:center}.DraftEditor-alignCenter .public-DraftEditorPlaceholder-root{margin:0 auto;text-align:center;width:100%}.DraftEditor-alignRight .public-DraftStyleDefault-block{text-align:right}.DraftEditor-alignRight .public-DraftEditorPlaceholder-root{right:0;text-align:right}.public-DraftEditorPlaceholder-root{color:#9197a3;position:absolute;z-index:0}.public-DraftEditorPlaceholder-hasFocus{color:#bdc1c9}.DraftEditorPlaceholder-hidden{display:none}.public-DraftStyleDefault-block{position:relative;white-space:pre-wrap}.public-DraftStyleDefault-ltr{direction:ltr;text-align:left}.public-DraftStyleDefault-rtl{direction:rtl;text-align:right}.public-DraftStyleDefault-listLTR{direction:ltr}.public-DraftStyleDefault-listRTL{direction:rtl}.public-DraftStyleDefault-ol,.public-DraftStyleDefault-ul{margin:16px 0;padding:0}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-listLTR{margin-left:1.5em}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-listRTL{margin-right:1.5em}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-listLTR{margin-left:3em}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-listRTL{margin-right:3em}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-listLTR{margin-left:4.5em}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-listRTL{margin-right:4.5em}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-listLTR{margin-left:6em}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-listRTL{margin-right:6em}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-listLTR{margin-left:7.5em}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-listRTL{margin-right:7.5em}.public-DraftStyleDefault-unorderedListItem{list-style-type:square;position:relative}.public-DraftStyleDefault-unorderedListItem.public-DraftStyleDefault-depth0{list-style-type:disc}.public-DraftStyleDefault-unorderedListItem.public-DraftStyleDefault-depth1{list-style-type:circle}.public-DraftStyleDefault-orderedListItem{list-style-type:none;position:relative}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-listLTR:before{left:-36px;position:absolute;text-align:right;width:30px}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-listRTL:before{position:absolute;right:-36px;text-align:left;width:30px}.public-DraftStyleDefault-orderedListItem:before{content:counter(ol0) ". ";counter-increment:ol0}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth1:before{content:counter(ol1) ". ";counter-increment:ol1}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth2:before{content:counter(ol2) ". ";counter-increment:ol2}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth3:before{content:counter(ol3) ". ";counter-increment:ol3}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth4:before{content:counter(ol4) ". ";counter-increment:ol4}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-reset{counter-reset:ol0}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-reset{counter-reset:ol1}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-reset{counter-reset:ol2}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-reset{counter-reset:ol3}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-reset{counter-reset:ol4} + +/*# sourceMappingURL=react-draft-wysiwyg.css.map*/
\ No newline at end of file diff --git a/front/odiparpack/app/styles/components/vendors/react-dropzone/react-dropzone.css b/front/odiparpack/app/styles/components/vendors/react-dropzone/react-dropzone.css new file mode 100644 index 0000000..ff4ae01 --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/react-dropzone/react-dropzone.css @@ -0,0 +1,182 @@ +.dropzoneTextStyle { + text-align: center; + top: 25%; + position: relative; +} + +.dropzoneParagraph { + font-size: 24px +} + +.dropZone { + position: relative; + width: 100%; + height: 250px; + border: 2px dashed; + cursor: pointer; + padding: 20px; +} + +.stripes { + width: 100%; + height: 250px; + cursor: pointer; + border: solid; + border-color: #C8C8C8; + background-image: repeating-linear-gradient(-45deg, #F0F0F0, #F0F0F0 25px, #C8C8C8 25px, #C8C8C8 50px); + -webkit-animation: progress 2s linear infinite !important; + -moz-animation: progress 2s linear infinite !important; + animation: progress 2s linear infinite !important; + background-size: 150% 100%; +} + +.rejectStripes { + width: 100%; + height: 250px; + cursor: pointer; + border: solid; + border-color: #C8C8C8; + background-image: repeating-linear-gradient(-45deg, #fc8785, #fc8785 25px, #f4231f 25px, #f4231f 50px); + -webkit-animation: progress 2s linear infinite !important; + -moz-animation: progress 2s linear infinite !important; + animation: progress 2s linear infinite !important; + background-size: 150% 100%; +} + +.fileIconImg { + color: #909090 !important; +} + +.smallPreviewImg { + height: 100px !important; + width: initial !important; + max-width: 100%; +} + +@-webkit-keyframes progress { + 0% { + background-position: 0 0; + } + 100% { + background-position: -75px 0; + } +} + +@-moz-keyframes progress { + 0% { + background-position: 0 0; + } + 100% { + background-position: -75px 0; + } +} + +@-ms-keyframes progress { + 0% { + background-position: 0 0; + } + 100% { + background-position: -75px 0; + } +} + +@keyframes progress { + 0% { + background-position: 0 0; + } + 100% { + background-position: -70px 0; + } +} + +.imageContainer { + position: relative; + z-index: 10; + margin-bottom: 5px +} + +.imageContainer:hover .smallPreviewImg { + opacity: 0.7; +} + +.imageContainer:hover .middle { + opacity: 1; +} + +.imageContainer:hover .middleBigPic { + opacity: 1; +} + +.removeBtn { + color: white; + z-index: 3; +} + +.middle { + transition: .5s ease; + opacity: 0; + position: absolute; + top: 0; + right: 5px; + z-index: 20 +} + +.row { + margin-right: -0.5rem; + margin-left: -0.5rem; + box-sizing: border-box; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + flex: 0 1 auto; + -webkit-box-flex: 0; + -ms-flex: 0 1 auto; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + justify-content: center; +} + +.preview { + margin: 10px +} + +.imgWrap, .fileWrap { + color: #c7c7c7; + transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms !important; + box-sizing: border-box; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + box-shadow: rgba(0, 0, 0, 0.12) 0 1px 6px, rgba(0, 0, 0, 0.12) 0 1px 4px !important; + border-radius: 2px; + z-index: 5; + overflow: hidden; + text-align: center; + height: 100px; + position: relative; +} + +.imgWrap { + background: #c7c7c7; +} + +.fileWrap { + background: #e2e2e2; + color: #969696; +} + +.downloadBtn { + position: absolute; + right: 0; + top: 0; + text-align: center; + background: rgba(0, 0, 0, 0.4); + z-index: 10; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/front/odiparpack/app/styles/components/vendors/react-input-range/react-input-range.css b/front/odiparpack/app/styles/components/vendors/react-input-range/react-input-range.css new file mode 100644 index 0000000..323d18f --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/react-input-range/react-input-range.css @@ -0,0 +1,92 @@ +.input-range__slider { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: #3f51b5; + border: 1px solid #3f51b5; + border-radius: 100%; + cursor: pointer; + display: block; + height: 1rem; + margin-left: -0.5rem; + margin-top: -0.65rem; + outline: none; + position: absolute; + top: 50%; + -webkit-transition: box-shadow 0.3s ease-out, -webkit-transform 0.3s ease-out; + transition: box-shadow 0.3s ease-out, -webkit-transform 0.3s ease-out; + transition: transform 0.3s ease-out, box-shadow 0.3s ease-out; + transition: transform 0.3s ease-out, box-shadow 0.3s ease-out, -webkit-transform 0.3s ease-out; + width: 1rem; } + .input-range__slider:active { + -webkit-transform: scale(1.3); + transform: scale(1.3); } + .input-range__slider:focus { + box-shadow: 0 0 0 5px rgba(63, 81, 181, 0.2); } + .input-range--disabled .input-range__slider { + background: #cccccc; + border: 1px solid #cccccc; + box-shadow: none; + -webkit-transform: none; + transform: none; } + +.input-range__slider-container { + -webkit-transition: left 0.3s ease-out; + transition: left 0.3s ease-out; } + +.input-range__label { + color: #aaaaaa; + font-family: "Helvetica Neue", san-serif; + font-size: 0.8rem; + -webkit-transform: translateZ(0); + transform: translateZ(0); + white-space: nowrap; } + +.input-range__label--min, +.input-range__label--max { + bottom: -1.4rem; + position: absolute; } + +.input-range__label--min { + left: 0; } + +.input-range__label--max { + right: 0; } + +.input-range__label--value { + position: absolute; + top: -1.8rem; } + +.input-range__label-container { + left: -50%; + position: relative; } + .input-range__label--max .input-range__label-container { + left: 50%; } + +.input-range__track { + background: #eeeeee; + border-radius: 0.3rem; + cursor: pointer; + display: block; + height: 0.3rem; + position: relative; + -webkit-transition: left 0.3s ease-out, width 0.3s ease-out; + transition: left 0.3s ease-out, width 0.3s ease-out; } + .input-range--disabled .input-range__track { + background: #eeeeee; } + +.input-range__track--background { + left: 0; + margin-top: -0.15rem; + position: absolute; + right: 0; + top: 50%; } + +.input-range__track--active { + background: #3f51b5; } + +.input-range { + height: 1rem; + position: relative; + width: 100%; } + diff --git a/front/odiparpack/app/styles/components/vendors/react-loading-bar/index.css b/front/odiparpack/app/styles/components/vendors/react-loading-bar/index.css new file mode 100644 index 0000000..8301b01 --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/react-loading-bar/index.css @@ -0,0 +1,71 @@ +.Loading__loading___1m_fZ { + pointer-events: none; + transition: 400ms linear all; +} + +.Loading__bar___21yOt { + position: fixed; + top: 0; + left: 0; + z-index: 10002; + display: none; + width: 100%; + height: 2px; + background: #29d; + border-radius: 0 1px 1px 0; + transition: width 350ms; +} + +.Loading__peg___3Y_28 { + position: absolute; + top: 0; + right: 0; + width: 70px; + height: 2px; + border-radius: 50%; + opacity: .45; + box-shadow: #29d 1px 0 6px 1px; +} + +.Loading__spinner___11Pm4 { + position: fixed; + top: 5px; + left: 5px; + z-index: 10002; + pointer-events: none; + transition: 350ms linear all; +} + +.Loading__icon___3OOyu { + width: 14px; + height: 14px; + border: solid #29d; + border-width: 0 2px 2px 0; + border-radius: 50%; + -webkit-animation: Loading__loading-bar-spinner___1hKY9 400ms linear infinite; + animation: Loading__loading-bar-spinner___1hKY9 400ms linear infinite; +} + +@-webkit-keyframes Loading__loading-bar-spinner___1hKY9 { + 0% { + -webkit-transform: rotate(0); + transform: rotate(0); + } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes Loading__loading-bar-spinner___1hKY9 { + 0% { + -webkit-transform: rotate(0); + transform: rotate(0); + } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} diff --git a/front/odiparpack/app/styles/components/vendors/select/select.css b/front/odiparpack/app/styles/components/vendors/select/select.css new file mode 100644 index 0000000..89a11f4 --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/select/select.css @@ -0,0 +1,420 @@ +.Select { + position: relative; +} +.Select input::-webkit-contacts-auto-fill-button, +.Select input::-webkit-credentials-auto-fill-button { + display: none !important; +} +.Select input::-ms-clear { + display: none !important; +} +.Select input::-ms-reveal { + display: none !important; +} +.Select, +.Select div, +.Select input, +.Select span { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.Select.is-disabled .Select-arrow-zone { + cursor: default; + pointer-events: none; + opacity: 0.35; +} +.Select.is-disabled > .Select-control { + background-color: #f9f9f9; +} +.Select.is-disabled > .Select-control:hover { + box-shadow: none; +} +.Select.is-open > .Select-control { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + background: #fff; + border-color: #b3b3b3 #ccc #d9d9d9; +} +.Select.is-open > .Select-control .Select-arrow { + top: -2px; + border-color: transparent transparent #999; + border-width: 0 5px 5px; +} +.Select.is-searchable.is-open > .Select-control { + cursor: text; +} +.Select.is-searchable.is-focused:not(.is-open) > .Select-control { + cursor: text; +} +.Select.is-focused > .Select-control { + background: #fff; +} +.Select.is-focused:not(.is-open) > .Select-control { + border-color: #007eff; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 0 3px rgba(0, 126, 255, 0.1); + background: #fff; +} +.Select.has-value.is-clearable.Select--single > .Select-control .Select-value { + padding-right: 42px; +} +.Select.has-value.Select--single > .Select-control .Select-value .Select-value-label, +.Select.has-value.is-pseudo-focused.Select--single > .Select-control .Select-value .Select-value-label { + color: #333; +} +.Select.has-value.Select--single > .Select-control .Select-value a.Select-value-label, +.Select.has-value.is-pseudo-focused.Select--single > .Select-control .Select-value a.Select-value-label { + cursor: pointer; + text-decoration: none; +} +.Select.has-value.Select--single > .Select-control .Select-value a.Select-value-label:hover, +.Select.has-value.is-pseudo-focused.Select--single > .Select-control .Select-value a.Select-value-label:hover, +.Select.has-value.Select--single > .Select-control .Select-value a.Select-value-label:focus, +.Select.has-value.is-pseudo-focused.Select--single > .Select-control .Select-value a.Select-value-label:focus { + color: #007eff; + outline: none; + text-decoration: underline; +} +.Select.has-value.Select--single > .Select-control .Select-value a.Select-value-label:focus, +.Select.has-value.is-pseudo-focused.Select--single > .Select-control .Select-value a.Select-value-label:focus { + background: #fff; +} +.Select.has-value.is-pseudo-focused .Select-input { + opacity: 0; +} +.Select.is-open .Select-arrow, +.Select .Select-arrow-zone:hover > .Select-arrow { + border-top-color: #666; +} +.Select.Select--rtl { + direction: rtl; + text-align: right; +} +.Select-control { + background-color: #fff; + border-color: #d9d9d9 #ccc #b3b3b3; + border-radius: 4px; + border: 1px solid #ccc; + color: #333; + cursor: default; + display: table; + border-spacing: 0; + border-collapse: separate; + height: 36px; + outline: none; + overflow: hidden; + position: relative; + width: 100%; +} +.Select-control:hover { + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06); +} +.Select-control .Select-input:focus { + outline: none; + background: #fff; +} +.Select-placeholder, +.Select--single > .Select-control .Select-value { + bottom: 0; + color: #000; + left: 0; + line-height: 34px; + padding-left: 10px; + padding-right: 10px; + position: absolute; + right: 0; + top: 0; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.Select-input { + height: 34px; + padding-left: 10px; + padding-right: 10px; + vertical-align: middle; +} +.Select-input > input { + width: 100%; + background: none transparent; + border: 0 none; + box-shadow: none; + cursor: default; + display: inline-block; + font-family: inherit; + font-size: inherit; + margin: 0; + outline: none; + line-height: 17px; + /* For IE 8 compatibility */ + padding: 8px 0 12px; + /* For IE 8 compatibility */ + -webkit-appearance: none; +} +.is-focused .Select-input > input { + cursor: text; +} +.has-value.is-pseudo-focused .Select-input { + opacity: 0; +} +.Select-control:not(.is-searchable) > .Select-input { + outline: none; +} +.Select-loading-zone { + cursor: pointer; + display: table-cell; + position: relative; + text-align: center; + vertical-align: middle; + width: 16px; +} +.Select-loading { + -webkit-animation: Select-animation-spin 400ms infinite linear; + -o-animation: Select-animation-spin 400ms infinite linear; + animation: Select-animation-spin 400ms infinite linear; + width: 16px; + height: 16px; + box-sizing: border-box; + border-radius: 50%; + border: 2px solid #ccc; + border-right-color: #333; + display: inline-block; + position: relative; + vertical-align: middle; +} +.Select-clear-zone { + -webkit-animation: Select-animation-fadeIn 200ms; + -o-animation: Select-animation-fadeIn 200ms; + animation: Select-animation-fadeIn 200ms; + color: #999; + cursor: pointer; + display: table-cell; + position: relative; + text-align: center; + vertical-align: middle; + width: 17px; +} +.Select-clear-zone:hover { + color: #D0021B; +} +.Select-clear { + display: inline-block; + font-size: 18px; + line-height: 1; +} +.Select--multi .Select-clear-zone { + width: 17px; +} +.Select-arrow-zone { + cursor: pointer; + display: table-cell; + position: relative; + text-align: center; + vertical-align: middle; + width: 25px; + padding-right: 5px; +} +.Select--rtl .Select-arrow-zone { + padding-right: 0; + padding-left: 5px; +} +.Select-arrow { + border-color: #999 transparent transparent; + border-style: solid; + border-width: 5px 5px 2.5px; + display: inline-block; + height: 0; + width: 0; + position: relative; +} +.Select-control > *:last-child { + padding-right: 5px; +} +.Select--multi .Select-multi-value-wrapper { + display: inline-block; +} +.Select .Select-aria-only { + position: absolute; + display: inline-block; + height: 1px; + width: 1px; + margin: -1px; + clip: rect(0, 0, 0, 0); + overflow: hidden; + float: left; +} +@-webkit-keyframes Select-animation-fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +@keyframes Select-animation-fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +.Select-menu-outer { + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; + background-color: #fff; + border: 1px solid #ccc; + border-top-color: #e6e6e6; + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06); + box-sizing: border-box; + margin-top: -1px; + max-height: 200px; + position: absolute; + left: 0; + top: 100%; + width: 100%; + z-index: 1; + -webkit-overflow-scrolling: touch; +} +.Select-menu { + max-height: 198px; + overflow-y: auto; +} +.Select-option { + box-sizing: border-box; + background-color: #fff; + color: #666666; + cursor: pointer; + display: block; + padding: 8px 10px; +} +.Select-option:last-child { + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.Select-option.is-selected { + background-color: #f5faff; + /* Fallback color for IE 8 */ + background-color: rgba(0, 126, 255, 0.04); + color: #333; +} +.Select-option.is-focused { + background-color: #ebf5ff; + /* Fallback color for IE 8 */ + background-color: rgba(0, 126, 255, 0.08); + color: #333; +} +.Select-option.is-disabled { + color: #cccccc; + cursor: default; +} +.Select-noresults { + box-sizing: border-box; + color: #999999; + cursor: default; + display: block; + padding: 8px 10px; +} +.Select--multi .Select-input { + vertical-align: middle; + margin-left: 10px; + padding: 0; +} +.Select--multi.Select--rtl .Select-input { + margin-left: 0; + margin-right: 10px; +} +.Select--multi.has-value .Select-input { + margin-left: 5px; +} +.Select--multi .Select-value { + background-color: #ebf5ff; + /* Fallback color for IE 8 */ + background-color: rgba(0, 126, 255, 0.08); + border-radius: 2px; + border: 1px solid #c2e0ff; + /* Fallback color for IE 8 */ + border: 1px solid rgba(0, 126, 255, 0.24); + color: #007eff; + display: inline-block; + font-size: 0.9em; + line-height: 1.4; + margin-left: 5px; + margin-top: 5px; + vertical-align: top; +} +.Select--multi .Select-value-icon, +.Select--multi .Select-value-label { + display: inline-block; + vertical-align: middle; +} +.Select--multi .Select-value-label { + border-bottom-right-radius: 2px; + border-top-right-radius: 2px; + cursor: default; + padding: 2px 5px; +} +.Select--multi a.Select-value-label { + color: #007eff; + cursor: pointer; + text-decoration: none; +} +.Select--multi a.Select-value-label:hover { + text-decoration: underline; +} +.Select--multi .Select-value-icon { + cursor: pointer; + border-bottom-left-radius: 2px; + border-top-left-radius: 2px; + border-right: 1px solid #c2e0ff; + /* Fallback color for IE 8 */ + border-right: 1px solid rgba(0, 126, 255, 0.24); + padding: 1px 5px 3px; +} +.Select--multi .Select-value-icon:hover, +.Select--multi .Select-value-icon:focus { + background-color: #d8eafd; + /* Fallback color for IE 8 */ + background-color: rgba(0, 113, 230, 0.08); + color: #0071e6; +} +.Select--multi .Select-value-icon:active { + background-color: #c2e0ff; + /* Fallback color for IE 8 */ + background-color: rgba(0, 126, 255, 0.24); +} +.Select--multi.Select--rtl .Select-value { + margin-left: 0; + margin-right: 5px; +} +.Select--multi.Select--rtl .Select-value-icon { + border-right: none; + border-left: 1px solid #c2e0ff; + /* Fallback color for IE 8 */ + border-left: 1px solid rgba(0, 126, 255, 0.24); +} +.Select--multi.is-disabled .Select-value { + background-color: #fcfcfc; + border: 1px solid #e3e3e3; + color: #333; +} +.Select--multi.is-disabled .Select-value-icon { + cursor: not-allowed; + border-right: 1px solid #e3e3e3; +} +.Select--multi.is-disabled .Select-value-icon:hover, +.Select--multi.is-disabled .Select-value-icon:focus, +.Select--multi.is-disabled .Select-value-icon:active { + background-color: #fcfcfc; +} +@keyframes Select-animation-spin { + to { + transform: rotate(1turn); + } +} +@-webkit-keyframes Select-animation-spin { + to { + -webkit-transform: rotate(1turn); + } +} diff --git a/front/odiparpack/app/styles/components/vendors/slick-carousel/ajax-loader.gif b/front/odiparpack/app/styles/components/vendors/slick-carousel/ajax-loader.gif Binary files differnew file mode 100644 index 0000000..e0e6e97 --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/slick-carousel/ajax-loader.gif diff --git a/front/odiparpack/app/styles/components/vendors/slick-carousel/fonts/slick.eot b/front/odiparpack/app/styles/components/vendors/slick-carousel/fonts/slick.eot Binary files differnew file mode 100644 index 0000000..2cbab9c --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/slick-carousel/fonts/slick.eot diff --git a/front/odiparpack/app/styles/components/vendors/slick-carousel/fonts/slick.svg b/front/odiparpack/app/styles/components/vendors/slick-carousel/fonts/slick.svg new file mode 100644 index 0000000..b36a66a --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/slick-carousel/fonts/slick.svg @@ -0,0 +1,14 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg"> +<metadata>Generated by Fontastic.me</metadata> +<defs> +<font id="slick" horiz-adv-x="512"> +<font-face font-family="slick" units-per-em="512" ascent="480" descent="-32"/> +<missing-glyph horiz-adv-x="512" /> + +<glyph unicode="→" d="M241 113l130 130c4 4 6 8 6 13 0 5-2 9-6 13l-130 130c-3 3-7 5-12 5-5 0-10-2-13-5l-29-30c-4-3-6-7-6-12 0-5 2-10 6-13l87-88-87-88c-4-3-6-8-6-13 0-5 2-9 6-12l29-30c3-3 8-5 13-5 5 0 9 2 12 5z m234 143c0-40-9-77-29-110-20-34-46-60-80-80-33-20-70-29-110-29-40 0-77 9-110 29-34 20-60 46-80 80-20 33-29 70-29 110 0 40 9 77 29 110 20 34 46 60 80 80 33 20 70 29 110 29 40 0 77-9 110-29 34-20 60-46 80-80 20-33 29-70 29-110z"/> +<glyph unicode="←" d="M296 113l29 30c4 3 6 7 6 12 0 5-2 10-6 13l-87 88 87 88c4 3 6 8 6 13 0 5-2 9-6 12l-29 30c-3 3-8 5-13 5-5 0-9-2-12-5l-130-130c-4-4-6-8-6-13 0-5 2-9 6-13l130-130c3-3 7-5 12-5 5 0 10 2 13 5z m179 143c0-40-9-77-29-110-20-34-46-60-80-80-33-20-70-29-110-29-40 0-77 9-110 29-34 20-60 46-80 80-20 33-29 70-29 110 0 40 9 77 29 110 20 34 46 60 80 80 33 20 70 29 110 29 40 0 77-9 110-29 34-20 60-46 80-80 20-33 29-70 29-110z"/> +<glyph unicode="•" d="M475 256c0-40-9-77-29-110-20-34-46-60-80-80-33-20-70-29-110-29-40 0-77 9-110 29-34 20-60 46-80 80-20 33-29 70-29 110 0 40 9 77 29 110 20 34 46 60 80 80 33 20 70 29 110 29 40 0 77-9 110-29 34-20 60-46 80-80 20-33 29-70 29-110z"/> +<glyph unicode="a" d="M475 439l0-128c0-5-1-9-5-13-4-4-8-5-13-5l-128 0c-8 0-13 3-17 11-3 7-2 14 4 20l40 39c-28 26-62 39-100 39-20 0-39-4-57-11-18-8-33-18-46-32-14-13-24-28-32-46-7-18-11-37-11-57 0-20 4-39 11-57 8-18 18-33 32-46 13-14 28-24 46-32 18-7 37-11 57-11 23 0 44 5 64 15 20 9 38 23 51 42 2 1 4 3 7 3 3 0 5-1 7-3l39-39c2-2 3-3 3-6 0-2-1-4-2-6-21-25-46-45-76-59-29-14-60-20-93-20-30 0-58 5-85 17-27 12-51 27-70 47-20 19-35 43-47 70-12 27-17 55-17 85 0 30 5 58 17 85 12 27 27 51 47 70 19 20 43 35 70 47 27 12 55 17 85 17 28 0 55-5 81-15 26-11 50-26 70-45l37 37c6 6 12 7 20 4 8-4 11-9 11-17z"/> +</font></defs></svg> diff --git a/front/odiparpack/app/styles/components/vendors/slick-carousel/fonts/slick.ttf b/front/odiparpack/app/styles/components/vendors/slick-carousel/fonts/slick.ttf Binary files differnew file mode 100644 index 0000000..9d03461 --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/slick-carousel/fonts/slick.ttf diff --git a/front/odiparpack/app/styles/components/vendors/slick-carousel/fonts/slick.woff b/front/odiparpack/app/styles/components/vendors/slick-carousel/fonts/slick.woff Binary files differnew file mode 100644 index 0000000..8ee9972 --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/slick-carousel/fonts/slick.woff diff --git a/front/odiparpack/app/styles/components/vendors/slick-carousel/slick-carousel.css b/front/odiparpack/app/styles/components/vendors/slick-carousel/slick-carousel.css new file mode 100644 index 0000000..8d91736 --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/slick-carousel/slick-carousel.css @@ -0,0 +1,118 @@ +.variable-width .slick-slide p { + background: blue; + height: 100px; + color: #fff; + margin: 5px; + line-height: 100px; + text-align: center; +} +.center .slick-center h3 { + color: #e67e22; + opacity: 1; + transform: scale(1.08); +} +.center h3 { + opacity: 0.8; + transition: all 300ms ease; +} +.content { + padding: 20px; + margin: auto; + width: 90%; +} +.slick-slide { + transition: transform .3s +} +.slick-slide .image { + padding: 10px; +} +.slick-slide img { + border: 5px solid #fff; + display: block; + margin: auto; +} +.slick-slide img.slick-loading { + border: 0; +} +.slick-slider { + margin: 0 auto 32px; +} +.slick-dots { + margin-left: 0; +} +.slick-thumb { + bottom: -45px; +} +.slick-thumb li { + width: 60px; + height: 45px; +} +.slick-thumb li img { + filter: grayscale(100%); +} +.slick-thumb li.slick-active img { + filter: grayscale(0%); +} +@media (max-width: 768px) { + h3 { + font-size: 24px; + } + .center { + margin-left: -40px; + margin-right: -40px; + } + .center .slick-center h3 { + color: #e67e22; + opacity: 1; + transform: scale(1); + } + .center h3 { + opacity: 0.8; + transform: scale(0.95); + transition: all 300ms ease; + } +} +.slick-vertical .slick-slide { + height: 180px; +} +button.slick-arrow { + background-color: grey !important; + width: 40px; + height: 40px; + padding-top: 5px; + z-index: 10; +} +button.slick-arrow:before{ + font-size: 36px; +} +button.slick-arrow:hover, +button.slick-arrow:focus: { + background-color: grey; +} +.slick-center{ + transform: scale(1.2); +} +.thumb-nav .slick-dots { + margin: 20px 0; + bottom: -60px; +} +.thumb-nav .slick-dots li{ + width: 60px; + height: auto; + max-width: none; +} +.thumb-nav .slick-dots li.slick-active{ + border-bottom: 2px solid grey; +} +.custom-arrow .nav-prev, +.custom-arrow .nav-next{ + position: absolute; + bottom: -40px; + z-index: 10; +} +.custom-arrow .nav-prev{ + left: 0 +} +.custom-arrow .nav-next{ + right: 0 +} diff --git a/front/odiparpack/app/styles/components/vendors/slick-carousel/slick-theme.css b/front/odiparpack/app/styles/components/vendors/slick-carousel/slick-theme.css new file mode 100644 index 0000000..5566c15 --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/slick-carousel/slick-theme.css @@ -0,0 +1,213 @@ +@charset 'UTF-8'; +/* Slider */ +.slick-loading .slick-list +{ + background: #fff url('./ajax-loader.gif') center center no-repeat; +} + +/* Icons */ +@font-face +{ + font-family: 'slick'; + font-weight: normal; + font-style: normal; + + src: url('./fonts/slick.eot'); + src: url('./fonts/slick.eot?#iefix') format('embedded-opentype'), url('./fonts/slick.woff') format('woff'), url('./fonts/slick.ttf') format('truetype'), url('./fonts/slick.svg#slick') format('svg'); +} +/* Arrows */ +.slick-prev, +.slick-next +{ + font-size: 0; + line-height: 0; + + position: absolute; + top: 50%; + + display: block; + + width: 20px; + height: 20px; + padding: 0; + -webkit-transform: translate(0, -50%); + -ms-transform: translate(0, -50%); + transform: translate(0, -50%); + + cursor: pointer; + + color: transparent; + border: none; + outline: none; + background: transparent; +} +.slick-prev:hover, +.slick-prev:focus, +.slick-next:hover, +.slick-next:focus +{ + color: transparent; + outline: none; + background: transparent; +} +.slick-prev:hover:before, +.slick-prev:focus:before, +.slick-next:hover:before, +.slick-next:focus:before +{ + opacity: 1; +} +.slick-prev.slick-disabled:before, +.slick-next.slick-disabled:before +{ + opacity: .25; +} + +.slick-prev:before, +.slick-next:before +{ + font-family: 'slick'; + font-size: 20px; + line-height: 1; + + opacity: .75; + color: white; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.slick-prev +{ + left: -25px; +} +[dir='rtl'] .slick-prev +{ + right: -25px; + left: auto; +} +.slick-prev:before +{ + content: '←'; +} +[dir='rtl'] .slick-prev:before +{ + content: '→'; +} + +.slick-next +{ + right: -25px; +} +[dir='rtl'] .slick-next +{ + right: auto; + left: -25px; +} +.slick-next:before +{ + content: '→'; +} +[dir='rtl'] .slick-next:before +{ + content: '←'; +} + +/* Dots */ +.slick-dotted.slick-slider +{ + margin-bottom: 30px; +} + +.slick-dots +{ + position: absolute; + bottom: -25px; + + display: block; + + width: 100%; + padding: 0; + margin: 0; + + list-style: none; + + text-align: center; +} +.slick-dots li +{ + position: relative; + + display: inline-block; + + width: 20px; + height: 20px; + margin: 0 5px; + padding: 0; + + cursor: pointer; +} +.slick-dots li button +{ + font-size: 0; + line-height: 0; + + display: block; + + width: 20px; + height: 20px; + padding: 5px; + + cursor: pointer; + + color: transparent; + border: 0; + outline: none; + background: transparent; +} +.slick-dots li button:hover, +.slick-dots li button:focus +{ + outline: none; +} +.slick-dots li button:hover:before, +.slick-dots li button:focus:before +{ + opacity: 1; +} +.slick-dots li button:before +{ + font-family: 'slick'; + font-size: 6px; + line-height: 20px; + + position: absolute; + top: 0; + left: 0; + + width: 20px; + height: 20px; + + content: '•'; + text-align: center; + + opacity: .25; + color: black; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.slick-dots li.slick-active button:before +{ + opacity: .75; + color: black; +} + +@media only screen and (max-width: 960px) { + .slick-next { + right: 5px + } + .slick-prev { + left: 5px + } +}
\ No newline at end of file diff --git a/front/odiparpack/app/styles/components/vendors/slick-carousel/slick.css b/front/odiparpack/app/styles/components/vendors/slick-carousel/slick.css new file mode 100644 index 0000000..cd76a53 --- /dev/null +++ b/front/odiparpack/app/styles/components/vendors/slick-carousel/slick.css @@ -0,0 +1,119 @@ +/* Slider */ +.slick-slider +{ + position: relative; + + display: block; + box-sizing: border-box; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + -webkit-touch-callout: none; + -khtml-user-select: none; + -ms-touch-action: pan-y; + touch-action: pan-y; + -webkit-tap-highlight-color: transparent; +} + +.slick-list +{ + position: relative; + + display: block; + overflow: hidden; + + margin: 0 -4px; + padding: 0; +} +.slick-list:focus +{ + outline: none; +} +.slick-list.dragging +{ + cursor: pointer; + cursor: hand; +} + +.slick-slider .slick-track, +.slick-slider .slick-list +{ + -webkit-transform: translate3d(0, 0, 0); + -moz-transform: translate3d(0, 0, 0); + -ms-transform: translate3d(0, 0, 0); + -o-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); +} + +.slick-track +{ + position: relative; + top: 0; + left: 0; + + display: block; + margin-left: auto; + margin-right: auto; +} +.slick-track:before, +.slick-track:after +{ + display: table; + + content: ''; +} +.slick-track:after +{ + clear: both; +} +.slick-loading .slick-track +{ + visibility: hidden; +} + +.slick-slide +{ + display: none; + float: left; + + height: 100%; + min-height: 1px; +} +[dir='rtl'] .slick-slide +{ + float: right; +} +.slick-slide img +{ + display: block; +} +.slick-slide.slick-loading img +{ + display: none; +} +.slick-slide.dragging img +{ + pointer-events: none; +} +.slick-initialized .slick-slide +{ + display: block; +} +.slick-loading .slick-slide +{ + visibility: hidden; +} +.slick-vertical .slick-slide +{ + display: block; + + height: auto; + + border: 1px solid transparent; +} +.slick-arrow.slick-hidden { + display: none; +} diff --git a/front/odiparpack/app/styles/layout/_buttons.scss b/front/odiparpack/app/styles/layout/_buttons.scss new file mode 100644 index 0000000..045ba0f --- /dev/null +++ b/front/odiparpack/app/styles/layout/_buttons.scss @@ -0,0 +1,38 @@ +$_button-background-color: $action-color; +$_button-background-color-hover: shade($action-color, 20%); + +#{$all-buttons} { + appearance: none; + background-color: $_button-background-color; + border: 0; + border-radius: $base-border-radius; + color: contrast-switch($_button-background-color); + cursor: pointer; + display: inline-block; + font-family: $base-font-family; + font-size: 16px; + -webkit-font-smoothing: antialiased; + font-weight: 600; + line-height: 1; + padding: $small-spacing $base-spacing; + text-align: center; + text-decoration: none; + transition: background-color $base-duration $base-timing; + user-select: none; + vertical-align: middle; + white-space: nowrap; + + &:focus { + outline: none; + outline-offset: inherit; + } + + &:disabled { + cursor: not-allowed; + opacity: 0.5; + + &:hover { + background-color: $_button-background-color; + } + } +} diff --git a/front/odiparpack/app/styles/layout/_forms.scss b/front/odiparpack/app/styles/layout/_forms.scss new file mode 100644 index 0000000..7f943e4 --- /dev/null +++ b/front/odiparpack/app/styles/layout/_forms.scss @@ -0,0 +1,47 @@ +$_form-background-color: #fff; +$_form-box-shadow: inset 0 1px 3px rgba(#000, 0.06); +$_form-box-shadow-focus: $_form-box-shadow, 0 0 5px rgba($action-color, 0.7); + +fieldset { + background-color: transparent; + border: 0; + margin: 0; + padding: 0; +} + +legend { + font-weight: 600; + margin-bottom: $small-spacing / 2; + padding: 0; +} + +textarea { + resize: vertical; + font-family: $base-font-family; +} + +[type="checkbox"], +[type="radio"] { + display: inline; + margin-right: $small-spacing / 2; +} + +[type="file"] { + margin-bottom: $small-spacing; + width: 100%; +} + +select { + margin-bottom: $small-spacing; + width: 100%; +} + +[type="checkbox"], +[type="radio"], +[type="file"], +select { + &:focus { + outline: $focus-outline; + outline-offset: $focus-outline-offset; + } +} diff --git a/front/odiparpack/app/styles/layout/_layout.scss b/front/odiparpack/app/styles/layout/_layout.scss new file mode 100644 index 0000000..4f4001e --- /dev/null +++ b/front/odiparpack/app/styles/layout/_layout.scss @@ -0,0 +1,19 @@ +html { + background-color: $viewport-background-color; + box-sizing: border-box; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + +html, +body { + height: 100%; +} + +body { + margin: 0; +} diff --git a/front/odiparpack/app/styles/layout/_lists.scss b/front/odiparpack/app/styles/layout/_lists.scss new file mode 100644 index 0000000..06a7c0a --- /dev/null +++ b/front/odiparpack/app/styles/layout/_lists.scss @@ -0,0 +1,19 @@ +ul, +ol { + list-style-type: none; + margin: 0; + padding: 0; +} + +dl { + margin: 0; +} + +dt { + font-weight: 600; + margin: 0; +} + +dd { + margin: 0; +} diff --git a/front/odiparpack/app/styles/layout/_media.scss b/front/odiparpack/app/styles/layout/_media.scss new file mode 100644 index 0000000..dfa22ea --- /dev/null +++ b/front/odiparpack/app/styles/layout/_media.scss @@ -0,0 +1,9 @@ +figure { + margin: 0; +} + +img, +picture { + margin: 0; + max-width: 100%; +} diff --git a/front/odiparpack/app/styles/layout/_tables.scss b/front/odiparpack/app/styles/layout/_tables.scss new file mode 100644 index 0000000..8287bcf --- /dev/null +++ b/front/odiparpack/app/styles/layout/_tables.scss @@ -0,0 +1,32 @@ +table { + border-collapse: collapse; + margin: $base-spacing 0; + text-align: left; + width: 100%; +} + +thead { + line-height: $heading-line-height; + vertical-align: bottom; +} + +tbody { + vertical-align: top; +} + +tr { + border-bottom: $base-border; +} + +th { + font-weight: 600; +} + +th, +td { + padding: $small-spacing $base-spacing; + @media screen and (max-width: 1400px) { + padding: $small-spacing; + padding-left: $base-spacing + } +} diff --git a/front/odiparpack/app/styles/layout/_typography.scss b/front/odiparpack/app/styles/layout/_typography.scss new file mode 100644 index 0000000..49a10c6 --- /dev/null +++ b/front/odiparpack/app/styles/layout/_typography.scss @@ -0,0 +1,58 @@ +@import "../../styles/mixins"; + +html { + color: $base-font-color; + font-family: $base-font-family; + font-size: 100%; + line-height: $base-line-height; +} + +h1, +h2, +h3, +h4, +h6 { + font-family: $heading-font-family; + font-size: modular-scale(1); + line-height: $heading-line-height; + margin: 0 0 $small-spacing; +} + +h5 { + font-family: $heading-font-family; + font-size: modular-scale(1); + line-height: $base-line-height; + font-weight: 600; + color: #202A75; + margin: 0 0 $small-spacing; +} + + +p { + margin: 0 0 $small-spacing; +} + +a { + color: material-color('blue', '500'); + ttext-decoration-skip-ink: auto; + transition: color $base-duration $base-timing; + + &:focus { + outline: none; + outline-offset: none; + } +} + +hr { + border-bottom: $base-border; + border-left: 0; + border-right: 0; + border-top: 0; + margin: $base-spacing 0; +} + + +[textalign="center"]{text-align: center !important} +[textalign="left"]{text-align: left} +[textalign="right"]{text-align: right} +[textalign="justify"]{text-align: justify}
\ No newline at end of file diff --git a/front/odiparpack/app/styles/layout/_variables.scss b/front/odiparpack/app/styles/layout/_variables.scss new file mode 100644 index 0000000..cc20acf --- /dev/null +++ b/front/odiparpack/app/styles/layout/_variables.scss @@ -0,0 +1,41 @@ +// Typography +$base-font-family: $font-stack-system; +$heading-font-family: $base-font-family; + +// Line height +$base-line-height: 1.5; +$heading-line-height: 1.2; + +// Other Sizes +$base-border-radius: 3px; +$base-spacing: 1.5em; +$big-spacing: $base-spacing * 2; +$small-spacing: $base-spacing / 2; +$base-z-index: 0; + +// Colors +$white: #FFF; +$grey-light: #F5F5F5; +$grey: #E0E0E0; +$grey-dark: #424242; + +// Font Colors +$base-font-color: $grey-dark; +$action-color: $grey; + +// Border +$base-border-color: $grey; +$base-border: 1px solid $base-border-color; + +// Background Colors +$viewport-background-color: #fff; + +// Focus +$focus-outline-color: transparentize($action-color, 0.4); +$focus-outline-width: 3px; +$focus-outline: $focus-outline-width solid $focus-outline-color; +$focus-outline-offset: 2px; + +// Animations +$base-duration: 150ms; +$base-timing: ease; diff --git a/front/odiparpack/app/styles/layout/base.scss b/front/odiparpack/app/styles/layout/base.scss new file mode 100644 index 0000000..8daf825 --- /dev/null +++ b/front/odiparpack/app/styles/layout/base.scss @@ -0,0 +1,15 @@ +// Bitters 1.7.0 +// http://bitters.bourbon.io +// Copyright 2013-2017 thoughtbot, inc. +// MIT License + +@import "../variables"; +@import "variables"; + +@import "buttons"; +@import "forms"; +@import "layout"; +@import "lists"; +@import "media"; +@import "tables"; +@import "typography"; diff --git a/front/odiparpack/app/styles/mixins.scss b/front/odiparpack/app/styles/mixins.scss new file mode 100644 index 0000000..ac97b6b --- /dev/null +++ b/front/odiparpack/app/styles/mixins.scss @@ -0,0 +1,3 @@ +@import "mixins/bourbon"; +@import "mixins/neat"; +@import "mixins/pallete"; diff --git a/front/odiparpack/app/styles/mixins/bourbon.scss b/front/odiparpack/app/styles/mixins/bourbon.scss new file mode 100644 index 0000000..8d07bec --- /dev/null +++ b/front/odiparpack/app/styles/mixins/bourbon.scss @@ -0,0 +1 @@ +@import "node_modules/bourbon/core/_bourbon.scss"; diff --git a/front/odiparpack/app/styles/mixins/neat.scss b/front/odiparpack/app/styles/mixins/neat.scss new file mode 100644 index 0000000..af35e4a --- /dev/null +++ b/front/odiparpack/app/styles/mixins/neat.scss @@ -0,0 +1 @@ +@import "node_modules/bourbon-neat/core/_neat.scss";
\ No newline at end of file diff --git a/front/odiparpack/app/styles/mixins/pallete.scss b/front/odiparpack/app/styles/mixins/pallete.scss new file mode 100644 index 0000000..34f1fc9 --- /dev/null +++ b/front/odiparpack/app/styles/mixins/pallete.scss @@ -0,0 +1,8 @@ +// See the material colot pallete list here https://material-ui-next.com/style/color/ +@import 'node_modules/sass-material-colors/sass/sass-material-colors'; + +// Colors +$white: #FFF; +$grey-light: #F5F5F5; +$grey: #E0E0E0; +$grey-dark: #424242;
\ No newline at end of file diff --git a/front/odiparpack/app/styles/variables.scss b/front/odiparpack/app/styles/variables.scss new file mode 100644 index 0000000..1527f67 --- /dev/null +++ b/front/odiparpack/app/styles/variables.scss @@ -0,0 +1,3 @@ +@import "variables/bitters"; +@import "variables/neat"; +@import "variables/custom_pallete"; diff --git a/front/odiparpack/app/styles/variables/bitters.scss b/front/odiparpack/app/styles/variables/bitters.scss new file mode 100644 index 0000000..4f8f996 --- /dev/null +++ b/front/odiparpack/app/styles/variables/bitters.scss @@ -0,0 +1,4 @@ +$font-stack-system: "Roboto", "Helvetica", "Arial", sans-serif; +$heading-font-family: "Roboto", "Helvetica", "Arial", sans-serif; +$all-buttons: 'button, input[type="button"], input[type="submit"]'; +$all-text-inputs: 'input[type="text"], input[type="password"], input[type="email"], input[type="tel"]'; diff --git a/front/odiparpack/app/styles/variables/custom_pallete.scss b/front/odiparpack/app/styles/variables/custom_pallete.scss new file mode 100644 index 0000000..e5c0d0d --- /dev/null +++ b/front/odiparpack/app/styles/variables/custom_pallete.scss @@ -0,0 +1,31 @@ +@function grey(){ + @return material-color('blue-grey', '400'); +} + +@function greyLight(){ + @return material-color('blue-grey', '200'); +} + +@function greyLigther(){ + @return material-color('blue-grey', '100'); +} + +@function greyDark(){ + @return material-color('blue-grey', '600'); +} + +@function greyDarker(){ + @return material-color('blue-grey', '800'); +} + +@function black(){ + @return material-color('blue-grey', '900'); +} + +@function white(){ + @return "#FFFFFF"; +} + +@function primaryColor(){ + @return material-color('indigo', '900'); +}
\ No newline at end of file diff --git a/front/odiparpack/app/styles/variables/neat.scss b/front/odiparpack/app/styles/variables/neat.scss new file mode 100644 index 0000000..e02bb9f --- /dev/null +++ b/front/odiparpack/app/styles/variables/neat.scss @@ -0,0 +1 @@ +$border-box-sizing: false; diff --git a/front/odiparpack/app/translations/en.json b/front/odiparpack/app/translations/en.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/front/odiparpack/app/translations/en.json @@ -0,0 +1 @@ +[] diff --git a/front/odiparpack/app/utils/checkStore.js b/front/odiparpack/app/utils/checkStore.js new file mode 100644 index 0000000..610ea0f --- /dev/null +++ b/front/odiparpack/app/utils/checkStore.js @@ -0,0 +1,21 @@ +import { conformsTo, isFunction, isObject } from 'lodash'; +import invariant from 'invariant'; + +/** + * Validate the shape of redux store + */ +export default function checkStore(store) { + const shape = { + dispatch: isFunction, + subscribe: isFunction, + getState: isFunction, + replaceReducer: isFunction, + runSaga: isFunction, + injectedReducers: isObject, + injectedSagas: isObject, + }; + invariant( + conformsTo(store, shape), + '(app/utils...) injectors: Expected a valid redux store', + ); +} diff --git a/front/odiparpack/app/utils/constants.js b/front/odiparpack/app/utils/constants.js new file mode 100644 index 0000000..97ece0f --- /dev/null +++ b/front/odiparpack/app/utils/constants.js @@ -0,0 +1,3 @@ +export const RESTART_ON_REMOUNT = '@@saga-injector/restart-on-remount'; +export const DAEMON = '@@saga-injector/daemon'; +export const ONCE_TILL_UNMOUNT = '@@saga-injector/once-till-unmount'; diff --git a/front/odiparpack/app/utils/history.js b/front/odiparpack/app/utils/history.js new file mode 100644 index 0000000..ee3abb7 --- /dev/null +++ b/front/odiparpack/app/utils/history.js @@ -0,0 +1,3 @@ +import { createBrowserHistory } from 'history'; +const history = createBrowserHistory(); +export default history; diff --git a/front/odiparpack/app/utils/injectReducer.js b/front/odiparpack/app/utils/injectReducer.js new file mode 100644 index 0000000..13833c2 --- /dev/null +++ b/front/odiparpack/app/utils/injectReducer.js @@ -0,0 +1,45 @@ +import React from 'react'; +import hoistNonReactStatics from 'hoist-non-react-statics'; +import { ReactReduxContext } from 'react-redux'; + +import getInjectors from './reducerInjectors'; + +/** + * Dynamically injects a reducer + * + * @param {string} key A key of the reducer + * @param {function} reducer A reducer that will be injected + * + */ +export default ({ key, reducer }) => WrappedComponent => { + class ReducerInjector extends React.Component { + static WrappedComponent = WrappedComponent; + + static contextType = ReactReduxContext; + + static displayName = `withReducer(${WrappedComponent.displayName + || WrappedComponent.name + || 'Component'})`; + + constructor(props, context) { + super(props, context); + + getInjectors(context.store).injectReducer(key, reducer); + } + + render() { + return <WrappedComponent {...this.props} />; + } + } + + return hoistNonReactStatics(ReducerInjector, WrappedComponent); +}; + +const useInjectReducer = ({ key, reducer }) => { + const context = React.useContext(ReactReduxContext); + React.useEffect(() => { + getInjectors(context.store).injectReducer(key, reducer); + }, []); +}; + +export { useInjectReducer }; diff --git a/front/odiparpack/app/utils/injectSaga.js b/front/odiparpack/app/utils/injectSaga.js new file mode 100644 index 0000000..3f8752b --- /dev/null +++ b/front/odiparpack/app/utils/injectSaga.js @@ -0,0 +1,59 @@ +import React from 'react'; +import hoistNonReactStatics from 'hoist-non-react-statics'; +import { ReactReduxContext } from 'react-redux'; + +import getInjectors from './sagaInjectors'; + +/** + * Dynamically injects a saga, passes component's props as saga arguments + * + * @param {string} key A key of the saga + * @param {function} saga A root saga that will be injected + * @param {string} [mode] By default (constants.DAEMON) the saga will be started + * on component mount and never canceled or started again. Another two options: + * - constants.RESTART_ON_REMOUNT — the saga will be started on component mount and + * cancelled with `task.cancel()` on component unmount for improved performance, + * - constants.ONCE_TILL_UNMOUNT — behaves like 'RESTART_ON_REMOUNT' but never runs it again. + * + */ +export default ({ key, saga, mode }) => WrappedComponent => { + class InjectSaga extends React.Component { + static WrappedComponent = WrappedComponent; + + static contextType = ReactReduxContext; + + static displayName = `withSaga(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`; + + constructor(props, context) { + super(props, context); + + this.injectors = getInjectors(context.store); + + this.injectors.injectSaga(key, { saga, mode }, this.props); + } + + componentWillUnmount() { + this.injectors.ejectSaga(key); + } + + render() { + return <WrappedComponent {...this.props} />; + } + } + + return hoistNonReactStatics(InjectSaga, WrappedComponent); +}; + +const useInjectSaga = ({ key, saga, mode }) => { + const context = React.useContext(ReactReduxContext); + React.useEffect(() => { + const injectors = getInjectors(context.store); + injectors.injectSaga(key, { saga, mode }); + + return () => { + injectors.ejectSaga(key); + }; + }, []); +}; + +export { useInjectSaga }; diff --git a/front/odiparpack/app/utils/reducerInjectors.js b/front/odiparpack/app/utils/reducerInjectors.js new file mode 100644 index 0000000..d664680 --- /dev/null +++ b/front/odiparpack/app/utils/reducerInjectors.js @@ -0,0 +1,33 @@ +import invariant from 'invariant'; +import { isEmpty, isFunction, isString } from 'lodash'; + +import checkStore from './checkStore'; +import createReducer from '../redux/reducers'; + +export function injectReducerFactory(store, isValid) { + return function injectReducer(key, reducer) { + if (!isValid) checkStore(store); + + invariant( + isString(key) && !isEmpty(key) && isFunction(reducer), + '(app/utils...) injectReducer: Expected `reducer` to be a reducer function', + ); + + // Check `store.injectedReducers[key] === reducer` for hot reloading when a key is the same but a reducer is different + if ( + Reflect.has(store.injectedReducers, key) + && store.injectedReducers[key] === reducer + ) return; + + store.injectedReducers[key] = reducer; // eslint-disable-line no-param-reassign + store.replaceReducer(createReducer(store.injectedReducers)); + }; +} + +export default function getInjectors(store) { + checkStore(store); + + return { + injectReducer: injectReducerFactory(store, true), + }; +} diff --git a/front/odiparpack/app/utils/sagaInjectors.js b/front/odiparpack/app/utils/sagaInjectors.js new file mode 100644 index 0000000..edb4626 --- /dev/null +++ b/front/odiparpack/app/utils/sagaInjectors.js @@ -0,0 +1,92 @@ +import invariant from 'invariant'; +import { + isEmpty, isFunction, isString, conformsTo +} from 'lodash'; + +import checkStore from './checkStore'; +import { DAEMON, ONCE_TILL_UNMOUNT, RESTART_ON_REMOUNT } from './constants'; + +const allowedModes = [RESTART_ON_REMOUNT, DAEMON, ONCE_TILL_UNMOUNT]; + +const checkKey = key => invariant( + isString(key) && !isEmpty(key), + '(app/utils...) injectSaga: Expected `key` to be a non empty string', +); + +const checkDescriptor = descriptor => { + const shape = { + saga: isFunction, + mode: mode => isString(mode) && allowedModes.includes(mode), + }; + invariant( + conformsTo(descriptor, shape), + '(app/utils...) injectSaga: Expected a valid saga descriptor', + ); +}; + +export function injectSagaFactory(store, isValid) { + return function injectSaga(key, descriptor = {}, args) { + if (!isValid) checkStore(store); + + const newDescriptor = { + ...descriptor, + mode: descriptor.mode || DAEMON, + }; + const { saga, mode } = newDescriptor; + + checkKey(key); + checkDescriptor(newDescriptor); + + let hasSaga = Reflect.has(store.injectedSagas, key); + + if (process.env.NODE_ENV !== 'production') { + const oldDescriptor = store.injectedSagas[key]; + // enable hot reloading of daemon and once-till-unmount sagas + if (hasSaga && oldDescriptor.saga !== saga) { + oldDescriptor.task.cancel(); + hasSaga = false; + } + } + + if ( + !hasSaga + || (hasSaga && mode !== DAEMON && mode !== ONCE_TILL_UNMOUNT) + ) { + /* eslint-disable no-param-reassign */ + store.injectedSagas[key] = { + ...newDescriptor, + task: store.runSaga(saga, args), + }; + /* eslint-enable no-param-reassign */ + } + }; +} + +export function ejectSagaFactory(store, isValid) { + return function ejectSaga(key) { + if (!isValid) checkStore(store); + + checkKey(key); + + if (Reflect.has(store.injectedSagas, key)) { + const descriptor = store.injectedSagas[key]; + if (descriptor.mode && descriptor.mode !== DAEMON) { + descriptor.task.cancel(); + // Clean up in production; in development we need `descriptor.saga` for hot reloading + if (process.env.NODE_ENV === 'production') { + // Need some value to be able to detect `ONCE_TILL_UNMOUNT` sagas in `injectSaga` + store.injectedSagas[key] = 'done'; // eslint-disable-line no-param-reassign + } + } + } + }; +} + +export default function getInjectors(store) { + checkStore(store); + + return { + injectSaga: injectSagaFactory(store, true), + ejectSaga: ejectSagaFactory(store, true), + }; +} diff --git a/front/odiparpack/app/utils/sagas.js b/front/odiparpack/app/utils/sagas.js new file mode 100644 index 0000000..f5d3e78 --- /dev/null +++ b/front/odiparpack/app/utils/sagas.js @@ -0,0 +1,15 @@ +import { all } from 'redux-saga/effects'; +import taskSagas from 'enl-containers/SampleFullstackApps//Todo/reducers/todoSagas'; +import contactSagas from 'enl-containers/SampleFullstackApps/Contact/reducers/contactSagas'; +import emailSagas from 'enl-containers/SampleFullstackApps/Email/reducers/emailSagas'; +import authSagas from 'enl-redux/modules/authSagas'; + + +export default function* sagas() { + yield all([ + ...authSagas, + ...contactSagas, + ...taskSagas, + ...emailSagas + ]); +} diff --git a/front/odiparpack/app/utils/tests/.DS_Store b/front/odiparpack/app/utils/tests/.DS_Store Binary files differnew file mode 100644 index 0000000..5008ddf --- /dev/null +++ b/front/odiparpack/app/utils/tests/.DS_Store |
