├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── package.json ├── public ├── favicon.ico └── index.html └── src ├── components ├── common │ └── loader │ │ └── index.js └── navigation │ ├── topbarButtons.js │ └── topbarLogout.js ├── config └── constants.js ├── context └── profileContext │ └── index.js ├── helpers ├── auth.js └── db.js ├── index.css ├── index.js ├── layout ├── Private.js ├── Public.js └── index.js ├── modules ├── auth │ ├── pages │ │ ├── login │ │ │ └── index.js │ │ └── register │ │ │ └── index.js │ └── routes.js └── web │ ├── pages │ ├── dashboard │ │ ├── Calendar.js │ │ ├── EventChip.js │ │ ├── Footer.js │ │ ├── Modal.js │ │ ├── Sidebar.js │ │ ├── events.js │ │ ├── index.js │ │ └── styles │ │ │ ├── css │ │ │ └── react-big-calendar.css │ │ │ ├── dragAndDrop │ │ │ └── styles.less │ │ │ └── less │ │ │ ├── agenda.less │ │ │ ├── event.less │ │ │ ├── month.less │ │ │ ├── reset.less │ │ │ ├── styles.less │ │ │ ├── time-column.less │ │ │ ├── time-grid.less │ │ │ ├── toolbar.less │ │ │ └── variables.less │ └── home │ │ └── index.js │ └── routes.js ├── registerServiceWorker.js └── routes ├── Private.js ├── Public.js ├── index.js └── routes.js /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # testing 5 | coverage 6 | 7 | # production 8 | build 9 | 10 | # misc 11 | .DS_Store 12 | .env 13 | npm-debug.log 14 | .idea/* 15 | yarn.lock 16 | package-lock.json 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tim Zhao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-scheduler-firebase 2 | 3 | [**DEMO**](https://tim1023.github.io/react-scheduler-firebase/) 4 | ![Dashboard](https://github.com/Tim1023/react-scheduler-firebase/blob/gh-pages/screenshot.jpeg) 5 | 6 | ## Features 7 | * Material Design 8 | * Register 9 | * Login 10 | * Forgot Password 11 | * CRUD Calendar 12 | * Drag and Drop from outside 13 | * Export as ical 14 | ## Todo 15 | * Bugfix react-router historyUrl in Github Page 16 | * Refactor Code 17 | * Real time database 18 | 19 | 20 | 21 | 22 | ## Getting Started 23 | 24 | 25 | ### Prerequisites 26 | 27 | * https://github.com/nodejs/node 28 | * https://www.npmjs.com/get-npm 29 | 30 | 31 | ### Installing 32 | 33 | * Install node and npm 34 | * Navigate to root project directory 35 | * Install all the packages 36 | ``` 37 | npm install 38 | ``` 39 | Development enviroment 40 | 41 | ``` 42 | npm start 43 | ``` 44 | 45 | Deployment build 46 | 47 | ``` 48 | npm run build 49 | ``` 50 | 51 | ## Built With 52 | 53 | * [React](https://github.com/facebook/react) - JavaScript Library 54 | * [React-Big-Calendar](https://github.com/intljusticemission/react-big-calendar) - Events calendar component 55 | * [Firebase](https://firebase.google.com/) - Realtime database with Auth 56 | * [Material-UI](https://material-ui-next.com/) - React components 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-scheduler-firebase", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://github.com/Tim1023/react-scheduler-firebase", 6 | "dependencies": { 7 | "firebase": "4.6.0", 8 | "gh-pages": "^1.1.0", 9 | "history": "^4.7.2", 10 | "ical-generator": "^0.2.10", 11 | "material-ui": "^0.19.4", 12 | "moment": "^2.21.0", 13 | "node-less-chokidar": "^0.1.2", 14 | "npm-run-all": "^4.1.2", 15 | "prop-types": "^15.6.0", 16 | "react": "^16.3.0", 17 | "react-big-calendar": "^0.19.0", 18 | "react-dnd": "^2.6.0", 19 | "react-dnd-html5-backend": "^2.6.0", 20 | "react-dom": "^16.3.0", 21 | "react-file-download": "^0.3.5", 22 | "react-loadable": "^5.3.1", 23 | "react-router-dom": "^4.2.2", 24 | "react-scripts": "1.1.1", 25 | "uuid": "^3.2.1" 26 | }, 27 | "scripts": { 28 | "predeploy": "npm run build", 29 | "deploy": "gh-pages -d build", 30 | "start": "npm run build-css && run-p -ncr watch-css start-js", 31 | "start-js": "react-scripts start", 32 | "build": "run-s -n build-css build-js", 33 | "build-js": "react-scripts build", 34 | "test": "run-s -n build-css test-js", 35 | "test-js": "react-scripts test --env=jsdom", 36 | "build-css": "node-less-chokidar src", 37 | "watch-css": "node-less-chokidar src --watch", 38 | "eject": "react-scripts eject" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tim1023/react-scheduler-firebase/ef0e154e309acb371946af9c790c016c419d6616/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 17 | 18 | 20 | 22 | React-Scheduler 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/components/common/loader/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | // set display name for component 5 | const displayName = 'CommonLoader' 6 | 7 | // validate component properties 8 | const propTypes = { 9 | isLoading: PropTypes.bool, 10 | error: PropTypes.object, 11 | } 12 | 13 | const LoadingComponent = ({isLoading, error}) => { 14 | // Handle the loading state 15 | if (isLoading) { 16 | return
Loading...
17 | } 18 | // Handle the error state 19 | else if (error) { 20 | return
Sorry, there was a problem loading the page.
21 | } 22 | else { 23 | return null 24 | } 25 | } 26 | 27 | LoadingComponent.displayName = displayName 28 | LoadingComponent.propTypes = propTypes 29 | 30 | export default LoadingComponent -------------------------------------------------------------------------------- /src/components/navigation/topbarButtons.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from "react-router-dom"; 3 | import FlatButton from 'material-ui/FlatButton'; 4 | 5 | const topbarButtons = ( 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | ); 23 | 24 | export default topbarButtons 25 | -------------------------------------------------------------------------------- /src/components/navigation/topbarLogout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import FlatButton from 'material-ui/FlatButton'; 3 | import { Link } from "react-router-dom"; 4 | import { logout } from "../../helpers/auth"; 5 | 6 | const topbarLogout = ( 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | { 18 | logout(); 19 | }} 20 | style={{color: '#fff'}} 21 | /> 22 | 23 |
24 | ); 25 | 26 | export default topbarLogout 27 | -------------------------------------------------------------------------------- /src/config/constants.js: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase'; 2 | // Required for side-effects 3 | require('firebase/firestore'); 4 | 5 | const config = { 6 | apiKey: process.env.REACT_APP_FIREBASE_KEY, 7 | authDomain: process.env.REACT_APP_AUTH_DOMAIN, 8 | databaseURL: process.env.REACT_APP_DATABASE_URL, 9 | projectId: process.env.REACT_APP_PROJECT_ID, 10 | storageBucket: process.env.REACT_APP_STORAGE_BUCKET, 11 | messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID 12 | }; 13 | 14 | firebase.initializeApp(config); 15 | 16 | export const storageKey = 'KEY_FOR_LOCAL_STORAGE'; 17 | export const db = firebase.firestore(); 18 | export const firebaseAuth = firebase.auth; 19 | export const minTime = new Date(); 20 | minTime.setHours(7, 0, 0); 21 | export const maxTime = new Date(); 22 | maxTime.setHours(20, 0, 0); 23 | export const calendarInitialState = { 24 | events: [], 25 | equipments: [], 26 | people: [], 27 | modal: { 28 | id: null, 29 | title: null, 30 | desc: null, 31 | start: new Date(2018, 4, 4, 7, 0, 0), 32 | end: new Date(2018, 4, 4, 8, 0, 0), 33 | }, 34 | modalOpen: false, 35 | equipmentsOpen: false, 36 | peopleOpen: false, 37 | 38 | } -------------------------------------------------------------------------------- /src/context/profileContext/index.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react' 2 | 3 | const ProfileContext = createContext({}) 4 | 5 | export const ProfileProvider = ProfileContext.Provider 6 | 7 | export const ProfilerConsumer = ProfileContext.Consumer 8 | 9 | -------------------------------------------------------------------------------- /src/helpers/auth.js: -------------------------------------------------------------------------------- 1 | import { db, firebaseAuth, storageKey } from '../config/constants' 2 | 3 | 4 | export function auth(email, pw) { 5 | return firebaseAuth() 6 | .createUserWithEmailAndPassword(email, pw) 7 | .then(saveUser) 8 | .then(localStorage.setItem(storageKey, email)) 9 | } 10 | 11 | export function logout() { 12 | localStorage.removeItem(storageKey) 13 | return firebaseAuth().signOut() 14 | } 15 | 16 | export function login(email, pw) { 17 | return firebaseAuth() 18 | .signInWithEmailAndPassword(email, pw) 19 | .then(localStorage.setItem(storageKey, email)) 20 | } 21 | 22 | export function resetPassword(email) { 23 | return firebaseAuth().sendPasswordResetEmail(email) 24 | } 25 | 26 | export function saveUser(user) { 27 | return db 28 | .collection(`users`) 29 | .add({ 30 | email: user.email, 31 | uid: user.uid 32 | }) 33 | .then(docRef => docRef) 34 | .catch(function (error) { 35 | console.error('Error adding document: ', error) 36 | }) 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/helpers/db.js: -------------------------------------------------------------------------------- 1 | import { db } from '../config/constants'; 2 | 3 | export function GetEvents(uid) { 4 | return db.collection('events').where('ownerId','==',uid).get(); 5 | } 6 | export function GetEquipments(uid) { 7 | return db.collection('equipments').where('ownerId','==',uid).get(); 8 | } 9 | export function GetPeople(uid) { 10 | return db.collection('people').where('ownerId','==',uid).get(); 11 | } 12 | export function UpdateEvents(id) { 13 | return db.collection('events').doc(id) 14 | } 15 | export function UpdateEquipments(id) { 16 | return db.collection('equipments').doc(id) 17 | } 18 | export function UpdatePeople(id) { 19 | return db.collection('people').doc(id) 20 | } -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Routes from './routes'; 4 | import registerServiceWorker from './registerServiceWorker'; 5 | import './index.css'; 6 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root') 13 | ); 14 | registerServiceWorker(); 15 | -------------------------------------------------------------------------------- /src/layout/Private.js: -------------------------------------------------------------------------------- 1 | //import libs 2 | import React from 'react' 3 | import PropTypes from 'prop-types' 4 | 5 | // import components 6 | import AppBar from 'material-ui/AppBar'; 7 | import topbarLogout from '../components/navigation/topbarLogout' 8 | 9 | const propTypes = { 10 | children: PropTypes.node.isRequired, 11 | authed: PropTypes.bool.isRequired, 12 | 13 | } 14 | 15 | function PrivateLayout({ children }) { 16 | return
17 | 26 |
27 |
28 |
29 | {children} 30 | 31 |
32 |
33 |
34 |
35 | } 36 | 37 | PrivateLayout.propTypes = propTypes 38 | 39 | export default PrivateLayout 40 | -------------------------------------------------------------------------------- /src/layout/Public.js: -------------------------------------------------------------------------------- 1 | //import libs 2 | import React from 'react' 3 | import PropTypes from 'prop-types' 4 | 5 | // import components 6 | import AppBar from 'material-ui/AppBar'; 7 | import topbarButtons from '../components/navigation/topbarButtons' 8 | 9 | const propTypes = { 10 | children: PropTypes.node.isRequired, 11 | } 12 | 13 | function PublicLayout({children}) { 14 | return
15 | 24 |
25 |
26 |
27 | {children} 28 | 29 |
30 |
31 |
32 |
33 | } 34 | 35 | PublicLayout.propTypes = propTypes 36 | 37 | export default PublicLayout 38 | -------------------------------------------------------------------------------- /src/layout/index.js: -------------------------------------------------------------------------------- 1 | //import libs 2 | import React, { Component } from 'react' 3 | import { withRouter } from 'react-router-dom' 4 | 5 | // import components 6 | import PrivateLayout from './Private' 7 | import PublicLayout from './Public' 8 | 9 | class Layout extends Component { 10 | 11 | 12 | render() { 13 | const {children, authed} = this.props 14 | if (authed) { 15 | return {children} 16 | } 17 | return {children} 18 | } 19 | } 20 | 21 | 22 | export default withRouter(Layout) 23 | -------------------------------------------------------------------------------- /src/modules/auth/pages/login/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { login, resetPassword } from '../../../../helpers/auth'; 3 | 4 | import RaisedButton from 'material-ui/RaisedButton'; 5 | import TextField from 'material-ui/TextField'; 6 | 7 | function setErrorMsg(error) { 8 | return { 9 | loginMessage: error 10 | }; 11 | } 12 | 13 | export default class Login extends Component { 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | email: '', 18 | password: '', 19 | loginMessage: null 20 | }; 21 | } 22 | 23 | handleSubmit = e => { 24 | e.preventDefault(); 25 | login(this.state.email, this.state.password).catch(error => { 26 | this.setState(setErrorMsg('Invalid username/password.')); 27 | }); 28 | }; 29 | resetPassword = e => { 30 | e.preventDefault(); 31 | resetPassword(this.state.email) 32 | .then(() => 33 | this.setState( 34 | setErrorMsg(`Password reset email sent to ${this.state.email}.`) 35 | ) 36 | ) 37 | .catch(error => this.setState(setErrorMsg(`Email address not found.`))); 38 | }; 39 | 40 | render() { 41 | return ( 42 |
this.handleSubmit(event)} 45 | > 46 |

Login

47 | this.setState({email: newValue})} 51 | /> 52 |
53 | this.setState({password: newValue})} 58 | /> 59 |
60 | {this.state.loginMessage && ( 61 |
62 |
72 | )} 73 | 79 | 80 | ); 81 | } 82 | } 83 | 84 | const raisedBtn = { 85 | margin: 15 86 | }; 87 | 88 | const container = { 89 | textAlign: 'center' 90 | }; 91 | 92 | const style = { 93 | raisedBtn, 94 | container 95 | }; 96 | -------------------------------------------------------------------------------- /src/modules/auth/pages/register/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { auth } from '../../../../helpers/auth'; 3 | 4 | import RaisedButton from 'material-ui/RaisedButton'; 5 | import TextField from 'material-ui/TextField'; 6 | 7 | function setErrorMsg(error) { 8 | return { 9 | registerError: error.message 10 | }; 11 | } 12 | 13 | export default class Register extends Component { 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | registerError: null, 18 | email: '', 19 | password: '' 20 | }; 21 | } 22 | 23 | handleSubmit = e => { 24 | e.preventDefault(); 25 | auth(this.state.email, this.state.password).catch(e => 26 | this.setState(setErrorMsg(e)) 27 | ); 28 | }; 29 | 30 | render() { 31 | return ( 32 |
33 |

Register

34 | this.setState({email: newValue})} 38 | /> 39 |
40 | this.setState({password: newValue})} 45 | /> 46 |
47 | {this.state.registerError && ( 48 |
49 |
56 | )} 57 | 63 | 64 | ); 65 | } 66 | } 67 | 68 | const raisedBtn = { 69 | margin: 15 70 | }; 71 | 72 | const container = { 73 | textAlign: 'center' 74 | }; 75 | 76 | const style = { 77 | raisedBtn, 78 | container 79 | }; 80 | -------------------------------------------------------------------------------- /src/modules/auth/routes.js: -------------------------------------------------------------------------------- 1 | // import lib 2 | import Loadable from 'react-loadable' 3 | 4 | // import components 5 | import LoadingComponent from '../../components/common/loader' 6 | 7 | 8 | export default [ 9 | { 10 | path: '/login', 11 | exact: true, 12 | auth: false, 13 | component: Loadable({ 14 | loader: () => import('./pages/login'), 15 | loading: LoadingComponent, 16 | }), 17 | }, 18 | { 19 | path: '/register', 20 | exact: true, 21 | auth: false, 22 | component: Loadable({ 23 | loader: () => import('./pages/register'), 24 | loading: LoadingComponent, 25 | }), 26 | }, 27 | ] -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/Calendar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import HTML5Backend from 'react-dnd-html5-backend' 3 | import { DragDropContext } from 'react-dnd' 4 | import withDragAndDrop from 'react-big-calendar/lib/addons/dragAndDrop' 5 | import moment from 'moment' 6 | import uuidV4 from 'uuid/v4' 7 | import { minTime, maxTime, calendarInitialState } from '../../../../config/constants' 8 | //Compoments 9 | import BigCalendar from 'react-big-calendar' 10 | import Dialog from 'material-ui/Dialog' 11 | import Modal from './Modal' 12 | import Sidebar from './Sidebar' 13 | import Footer from './Footer' 14 | import FloatingActionButton from 'material-ui/FloatingActionButton' 15 | import ContentAdd from 'material-ui/svg-icons/content/add' 16 | //Actions 17 | import { 18 | GetEvents, 19 | UpdateEvents, 20 | GetEquipments, 21 | UpdateEquipments, 22 | GetPeople, 23 | UpdatePeople 24 | } from "../../../../helpers/db"; 25 | //Styles 26 | import './styles/dragAndDrop/styles.css' 27 | import './styles/less/styles.css' 28 | import './styles/css/react-big-calendar.css' 29 | 30 | 31 | BigCalendar.momentLocalizer(moment); // or globalizeLocalizer 32 | 33 | const DragAndDropCalendar = withDragAndDrop(BigCalendar, {backend: false}) 34 | 35 | class Dnd extends Component { 36 | 37 | constructor(props) { 38 | super(props) 39 | 40 | this.state = calendarInitialState 41 | 42 | this.moveEvent = this.moveEvent.bind(this) 43 | } 44 | 45 | componentDidMount() { 46 | const newEvents = [] 47 | const newEquipments = [] 48 | const newPeople = [] 49 | 50 | GetEvents(this.props.uid).then(querySnapshot => { 51 | querySnapshot.forEach(doc => { 52 | newEvents.push(doc.data()) 53 | this.setState({ 54 | events: newEvents, 55 | }) 56 | }); 57 | }) 58 | GetEquipments(this.props.uid).then(querySnapshot => { 59 | querySnapshot.forEach(doc => { 60 | newEquipments.push(doc.data()) 61 | this.setState({ 62 | equipments: newEquipments, 63 | }) 64 | }); 65 | }) 66 | GetPeople(this.props.uid).then(querySnapshot => { 67 | querySnapshot.forEach(doc => { 68 | newPeople.push(doc.data()) 69 | this.setState({ 70 | people: newPeople, 71 | }) 72 | }); 73 | }) 74 | } 75 | 76 | moveEvent({event, start, end}) { 77 | const {events} = this.state 78 | const idx = events.indexOf(event) 79 | let updatedEvent = {...event, start, end} 80 | const nextEvents = [...events] 81 | if (idx > -1) { 82 | nextEvents.splice(idx, 1, updatedEvent) 83 | UpdateEvents(event.id).update({start, end}).then( 84 | this.setState({ 85 | events: nextEvents, 86 | }) 87 | ).catch(error => { 88 | console.error('Update error', error); 89 | }); 90 | } 91 | else { 92 | const newEventId = uuidV4() 93 | updatedEvent = {...updatedEvent, id: newEventId, ownerId: this.props.uid} 94 | console.log(updatedEvent) 95 | nextEvents.push(updatedEvent) 96 | UpdateEvents(newEventId).set(updatedEvent).then( 97 | this.setState({ 98 | events: nextEvents, 99 | }) 100 | ).catch(error => { 101 | console.error('Create New Event error', error); 102 | }); 103 | } 104 | } 105 | 106 | selectEvent = (event) => { 107 | this.handleOpen(event) 108 | } 109 | 110 | resizeEvent = (resizeType, {event, start, end}) => { 111 | const {events} = this.state 112 | 113 | const nextEvents = events.map(existingEvent => { 114 | return existingEvent.id === event.id 115 | ? {...existingEvent, start, end} 116 | : existingEvent 117 | }) 118 | 119 | UpdateEvents(event.id).update({start, end}).then( 120 | this.setState({ 121 | events: nextEvents, 122 | }) 123 | ).catch(error => { 124 | console.error('Update error', error); 125 | }); 126 | } 127 | createEquipment = ({title, desc}) => { 128 | const {equipments} = this.state 129 | const newEquipmentId = uuidV4() 130 | const updatedEquipment = {...this.state.modal, id: newEquipmentId, ownerId: this.props.uid, title, desc} 131 | const nextEquipments = [...equipments] 132 | nextEquipments.push(updatedEquipment) 133 | UpdateEquipments(newEquipmentId).set(updatedEquipment).then( 134 | this.setState({ 135 | equipments: nextEquipments, 136 | }) 137 | ).catch(error => { 138 | console.error('Create New Equipment error', error); 139 | }); 140 | } 141 | createPeople = ({title, desc, phone}) => { 142 | const {people} = this.state 143 | const newPeopleId = uuidV4() 144 | const updatedPeople = {...this.state.modal, id: newPeopleId, ownerId: this.props.uid, title, desc, phone} 145 | const nextPeople = [...people] 146 | nextPeople.push(updatedPeople) 147 | UpdatePeople(newPeopleId).set(updatedPeople).then( 148 | this.setState({ 149 | people: nextPeople, 150 | }) 151 | ).catch(error => { 152 | console.error('Create New People error', error); 153 | }); 154 | } 155 | editEvent = ({id, title, desc}) => { 156 | const {events} = this.state 157 | 158 | const nextEvents = events.map(existingEvent => { 159 | return existingEvent.id === id 160 | ? {...existingEvent, title, desc} 161 | : existingEvent 162 | }) 163 | 164 | UpdateEvents(id).update({title, desc}).then( 165 | this.setState({ 166 | events: nextEvents, 167 | }) 168 | ).catch(error => { 169 | console.error('Update Event error', error); 170 | }); 171 | } 172 | editEquipment = ({id, title, desc}) => { 173 | const {equipments} = this.state 174 | 175 | const nextEquipments = equipments.map(existingEquipment => { 176 | return existingEquipment.id === id 177 | ? {...existingEquipment, title, desc} 178 | : existingEquipment 179 | }) 180 | UpdateEquipments(id).update({title, desc}).then( 181 | this.setState({ 182 | equipments: nextEquipments, 183 | }) 184 | ).catch(error => { 185 | console.error('Update Equipment error', error); 186 | }); 187 | } 188 | editPeople = ({id, title, desc, phone}) => { 189 | const {people} = this.state 190 | 191 | const nextPeople = people.map(existingPeople => { 192 | return existingPeople.id === id 193 | ? {...existingPeople, title, desc} 194 | : existingPeople 195 | }) 196 | UpdatePeople(id).update({title, desc, phone}).then( 197 | this.setState({ 198 | people: nextPeople, 199 | }) 200 | ).catch(error => { 201 | console.error('Update Equipment error', error); 202 | }); 203 | } 204 | deleteEvent = ({id}) => { 205 | const {events} = this.state 206 | 207 | const nextEvents = events.filter(existingEvent => { 208 | return existingEvent.id !== id 209 | }) 210 | 211 | UpdateEvents(id).delete().then( 212 | this.setState({ 213 | events: nextEvents, 214 | }) 215 | ).catch(error => { 216 | console.error('Delete Event error', error); 217 | }); 218 | } 219 | deleteEquipment = ({id}) => { 220 | const {equipments} = this.state 221 | 222 | const nextEquipments = equipments.filter(existingEquipment => { 223 | return existingEquipment.id !== id 224 | }) 225 | 226 | UpdateEquipments(id).delete().then( 227 | this.setState({ 228 | equipments: nextEquipments, 229 | }) 230 | ).catch(error => { 231 | console.error('Delete error', error); 232 | }); 233 | } 234 | deletePeople = ({id}) => { 235 | const {people} = this.state 236 | 237 | const nextPeople = people.filter(existingPeople => { 238 | return existingPeople.id !== id 239 | }) 240 | 241 | UpdatePeople(id).delete().then( 242 | this.setState({ 243 | people: nextPeople, 244 | }) 245 | ).catch(error => { 246 | console.error('Delete error', error); 247 | }); 248 | } 249 | handleClose = () => { 250 | this.setState({ 251 | modalOpen: false, 252 | equipmentsOpen: false, 253 | peopleOpen: false, 254 | modal: calendarInitialState.modal, 255 | }); 256 | }; 257 | handleOpen = (event) => { 258 | this.setState({ 259 | modalOpen: true, 260 | modal: event, 261 | }); 262 | }; 263 | handleEquipments = (event) => { 264 | this.setState({ 265 | modal: event ? event : this.state.modal, 266 | equipmentsOpen: true 267 | }); 268 | } 269 | handlePeople = (event) => { 270 | this.setState({ 271 | modal: event ? event : {...this.state.modal, phone: null}, 272 | peopleOpen: true 273 | }); 274 | } 275 | 276 | render() { 277 | if (this.state.events) { 278 | return ( 279 |
280 |
281 | Equipments: 282 | this.handleEquipments()} 286 | > 287 | 288 | 289 | 292 |
293 |
294 | 295 | 307 | 313 | 319 | 320 | 326 | 332 | 333 | 339 | 345 | 346 |
347 |
348 | People: 349 |
350 | this.handlePeople()} 354 | > 355 | 356 | 357 |
358 | 361 |
362 |
363 |
364 | ) 365 | } 366 | 367 | } 368 | } 369 | 370 | export default DragDropContext(HTML5Backend)(Dnd) -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/EventChip.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react' 3 | import { DragSource } from 'react-dnd'; 4 | import BigCalendar from 'react-big-calendar' 5 | import Chip from 'material-ui/Chip'; 6 | 7 | 8 | /* drag sources */ 9 | let eventSource = { 10 | beginDrag(props) { 11 | return Object.assign({}, 12 | {event: props.event}, 13 | 14 | {anchor: 'drop'} 15 | ) 16 | } 17 | } 18 | 19 | function collectSource(connect, monitor) { 20 | return { 21 | connectDragSource: connect.dragSource(), 22 | isDragging: monitor.isDragging() 23 | }; 24 | } 25 | 26 | const propTypes = { 27 | connectDragSource: PropTypes.func.isRequired, 28 | isDragging: PropTypes.bool.isRequired, 29 | event: PropTypes.object.isRequired 30 | } 31 | 32 | class DraggableSidebarEvent extends Component { 33 | 34 | 35 | render() { 36 | let {connectDragSource, isDragging, event} = this.props; 37 | let EventWrapper = BigCalendar.components.eventWrapper; 38 | let {title} = event; 39 | 40 | 41 | return ( 42 | 43 | {connectDragSource(
44 | {this.props.onClickEvent(event)}} 45 | >{title} 46 |
)} 47 |
48 | 49 | ); 50 | } 51 | } 52 | 53 | DraggableSidebarEvent.propTypes = propTypes; 54 | 55 | 56 | export default DragSource('event', eventSource, collectSource)(DraggableSidebarEvent); 57 | -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ical from 'ical-generator'; 3 | import fileDownload from 'react-file-download'; 4 | import RaisedButton from 'material-ui/RaisedButton'; 5 | 6 | 7 | export default class Footer extends React.Component { 8 | handleClick() { 9 | console.log(this.props.selectedEvents); 10 | let cal = ical({domain: 'github.com', name: 'Scheduler Export'}); 11 | 12 | this.props.selectedEvents.map((event) => { 13 | let { 14 | title, 15 | start, 16 | end, 17 | description, 18 | phone 19 | } = event; 20 | 21 | cal.createEvent({ 22 | start, 23 | end, 24 | summary: title+' '+phone?phone:'', 25 | description, 26 | }); 27 | 28 | return(event); 29 | }) 30 | 31 | const data = new Blob([cal.toString()], {type: 'text/calendar'}); 32 | fileDownload(data, 'download.ics'); 33 | } 34 | render() { 35 | return ( 36 |
37 | {this.handleClick()}}/> 38 | 39 |
40 | ) 41 | } 42 | } -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/Modal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TextField from 'material-ui/TextField'; 3 | import RaisedButton from 'material-ui/RaisedButton'; 4 | 5 | 6 | export default class Modal extends React.Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | ...props 12 | } 13 | } 14 | 15 | handleSubmit = e => { 16 | e.preventDefault(); 17 | this.state.onRequestClose() 18 | this.props.event.title ? this.state.onEditEvent(this.state.event) : 19 | this.state.onCreatEvent(this.state.event) 20 | } 21 | 22 | handleDelete = e => { 23 | e.preventDefault(); 24 | this.state.onRequestClose() 25 | this.state.onDeleteEvent(this.state.event) 26 | } 27 | handleCancel = e => { 28 | e.preventDefault(); 29 | this.state.onRequestClose() 30 | } 31 | 32 | render() { 33 | return ( 34 |
this.handleSubmit(e)} 36 | > 37 | 44 |
45 | this.setState({event: {...this.state.event, title: newValue}})} 49 | /> 50 | this.setState({event: {...this.state.event, desc: newValue}})} 54 | /> 55 | {'phone' in this.state.event ? this.setState({event: {...this.state.event, phone: newValue}})} 59 | /> : ''} 60 | 61 |
62 |
63 | 69 | 75 | 81 | 82 |
83 | 84 | 85 | ); 86 | } 87 | } -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import EventChip from './EventChip' 3 | 4 | 5 | export default class Sidebar extends React.Component { 6 | 7 | render() { 8 | 9 | const eventList = !!this.props.events ? this.props.events.map((event) => { 10 | return ( 15 | ) 16 | }) : null 17 | 18 | return ( 19 |
20 | {eventList} 21 |
22 | ); 23 | } 24 | } -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/events.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | id: 0, 4 | title: 'All Day Event very long title', 5 | allDay: true, 6 | start: new Date(2015, 3, 0), 7 | end: new Date(2015, 3, 1), 8 | }, 9 | { 10 | id: 1, 11 | title: 'Long Event', 12 | start: new Date(2015, 3, 7), 13 | end: new Date(2015, 3, 10), 14 | }, 15 | 16 | { 17 | id: 2, 18 | title: 'DTS STARTS', 19 | start: new Date(2016, 2, 13, 0, 0, 0), 20 | end: new Date(2016, 2, 20, 0, 0, 0), 21 | }, 22 | 23 | { 24 | id: 3, 25 | title: 'DTS ENDS', 26 | start: new Date(2016, 10, 6, 0, 0, 0), 27 | end: new Date(2016, 10, 13, 0, 0, 0), 28 | }, 29 | 30 | { 31 | id: 4, 32 | title: 'Some Event', 33 | start: new Date(2015, 3, 9, 0, 0, 0), 34 | end: new Date(2015, 3, 9, 0, 0, 0), 35 | }, 36 | { 37 | id: 5, 38 | title: 'Conference', 39 | start: new Date(2015, 3, 11), 40 | end: new Date(2015, 3, 13), 41 | desc: 'Big conference for important people', 42 | }, 43 | { 44 | id: 6, 45 | title: 'Meeting', 46 | start: new Date(2015, 3, 12, 10, 30, 0, 0), 47 | end: new Date(2015, 3, 12, 12, 30, 0, 0), 48 | desc: 'Pre-meeting meeting, to prepare for the meeting', 49 | }, 50 | { 51 | id: 7, 52 | title: 'Lunch', 53 | start: new Date(2015, 3, 12, 12, 0, 0, 0), 54 | end: new Date(2015, 3, 12, 13, 0, 0, 0), 55 | desc: 'Power lunch', 56 | }, 57 | { 58 | id: 8, 59 | title: 'Meeting', 60 | start: new Date(2015, 3, 12, 14, 0, 0, 0), 61 | end: new Date(2015, 3, 12, 15, 0, 0, 0), 62 | }, 63 | { 64 | id: 9, 65 | title: 'Happy Hour', 66 | start: new Date(2015, 3, 12, 17, 0, 0, 0), 67 | end: new Date(2015, 3, 12, 17, 30, 0, 0), 68 | desc: 'Most important meal of the day', 69 | }, 70 | { 71 | id: 10, 72 | title: 'Dinner', 73 | start: new Date(2015, 3, 12, 20, 0, 0, 0), 74 | end: new Date(2015, 3, 12, 21, 0, 0, 0), 75 | }, 76 | { 77 | id: 11, 78 | title: 'Birthday Party', 79 | start: new Date(2015, 3, 13, 7, 0, 0), 80 | end: new Date(2015, 3, 13, 10, 30, 0), 81 | }, 82 | { 83 | id: 12, 84 | title: 'Late Night Event', 85 | start: new Date(2015, 3, 17, 19, 30, 0), 86 | end: new Date(2015, 3, 18, 2, 0, 0), 87 | }, 88 | { 89 | id: 13, 90 | title: 'Multi-day Event', 91 | start: new Date(2015, 3, 20, 19, 30, 0), 92 | end: new Date(2015, 3, 22, 2, 0, 0), 93 | }, 94 | ] -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | // import context 3 | import { ProfilerConsumer } from '../../../../context/profileContext' 4 | // import components 5 | import Calendar from './Calendar' 6 | 7 | class Dashboard extends Component { 8 | 9 | render() { 10 | return ( 11 | 12 | {context => { 13 | if (context.email) { 14 | return ( 15 |
16 |
Scheduler: {context.email}
17 | 18 |
19 | ) 20 | } 21 | }} 22 |
23 | ) 24 | } 25 | 26 | } 27 | 28 | export default (Dashboard); 29 | -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/styles/css/react-big-calendar.css: -------------------------------------------------------------------------------- 1 | .rbc-btn { 2 | color: inherit; 3 | font: inherit; 4 | margin: 0; 5 | } 6 | button.rbc-btn { 7 | overflow: visible; 8 | text-transform: none; 9 | -webkit-appearance: button; 10 | cursor: pointer; 11 | } 12 | button[disabled].rbc-btn { 13 | cursor: not-allowed; 14 | } 15 | button.rbc-input::-moz-focus-inner { 16 | border: 0; 17 | padding: 0; 18 | } 19 | .rbc-calendar { 20 | box-sizing: border-box; 21 | height: 100%; 22 | display: -webkit-flex; 23 | display: -ms-flexbox; 24 | display: flex; 25 | -webkit-flex-direction: column; 26 | -ms-flex-direction: column; 27 | flex-direction: column; 28 | -webkit-align-items: stretch; 29 | -ms-flex-align: stretch; 30 | align-items: stretch; 31 | } 32 | .rbc-calendar *, 33 | .rbc-calendar *:before, 34 | .rbc-calendar *:after { 35 | box-sizing: inherit; 36 | } 37 | .rbc-abs-full, 38 | .rbc-row-bg { 39 | overflow: hidden; 40 | position: absolute; 41 | top: 0; 42 | left: 0; 43 | right: 0; 44 | bottom: 0; 45 | } 46 | .rbc-ellipsis, 47 | .rbc-event-label, 48 | .rbc-row-segment .rbc-event-content, 49 | .rbc-show-more { 50 | display: block; 51 | overflow: hidden; 52 | text-overflow: ellipsis; 53 | white-space: nowrap; 54 | } 55 | .rbc-rtl { 56 | direction: rtl; 57 | } 58 | .rbc-off-range { 59 | color: #999999; 60 | } 61 | .rbc-off-range-bg { 62 | background: #e5e5e5; 63 | } 64 | .rbc-header { 65 | overflow: hidden; 66 | -webkit-flex: 1 0 0%; 67 | -ms-flex: 1 0 0%; 68 | flex: 1 0 0%; 69 | text-overflow: ellipsis; 70 | white-space: nowrap; 71 | padding: 0 3px; 72 | text-align: center; 73 | vertical-align: middle; 74 | font-weight: bold; 75 | font-size: 90%; 76 | min-height: 0; 77 | border-bottom: 1px solid #DDD; 78 | } 79 | .rbc-header + .rbc-header { 80 | border-left: 1px solid #DDD; 81 | } 82 | .rbc-rtl .rbc-header + .rbc-header { 83 | border-left-width: 0; 84 | border-right: 1px solid #DDD; 85 | } 86 | .rbc-header > a, 87 | .rbc-header > a:active, 88 | .rbc-header > a:visited { 89 | color: inherit; 90 | text-decoration: none; 91 | } 92 | .rbc-row-content { 93 | position: relative; 94 | -moz-user-select: none; 95 | -ms-user-select: none; 96 | user-select: none; 97 | -webkit-user-select: none; 98 | z-index: 4; 99 | } 100 | .rbc-today { 101 | background-color: #eaf6ff; 102 | } 103 | .rbc-toolbar { 104 | display: -webkit-flex; 105 | display: -ms-flexbox; 106 | display: flex; 107 | -webkit-align-items: center; 108 | -ms-flex-align: center; 109 | align-items: center; 110 | margin-bottom: 10px; 111 | font-size: 16px; 112 | } 113 | .rbc-toolbar .rbc-toolbar-label { 114 | -webkit-flex-grow: 1; 115 | -ms-flex-positive: 1; 116 | flex-grow: 1; 117 | padding: 0 10px; 118 | text-align: center; 119 | } 120 | .rbc-toolbar button { 121 | color: #373a3c; 122 | display: inline-block; 123 | margin: 0; 124 | text-align: center; 125 | vertical-align: middle; 126 | background: none; 127 | background-image: none; 128 | border: 1px solid #ccc; 129 | padding: .375rem 1rem; 130 | border-radius: 4px; 131 | line-height: normal; 132 | white-space: nowrap; 133 | } 134 | .rbc-toolbar button:active, 135 | .rbc-toolbar button.rbc-active { 136 | background-image: none; 137 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 138 | background-color: #e6e6e6; 139 | border-color: #adadad; 140 | } 141 | .rbc-toolbar button:active:hover, 142 | .rbc-toolbar button.rbc-active:hover, 143 | .rbc-toolbar button:active:focus, 144 | .rbc-toolbar button.rbc-active:focus { 145 | color: #373a3c; 146 | background-color: #d4d4d4; 147 | border-color: #8c8c8c; 148 | } 149 | .rbc-toolbar button:focus { 150 | color: #373a3c; 151 | background-color: #e6e6e6; 152 | border-color: #adadad; 153 | } 154 | .rbc-toolbar button:hover { 155 | color: #373a3c; 156 | background-color: #e6e6e6; 157 | border-color: #adadad; 158 | } 159 | .rbc-btn-group { 160 | display: inline-block; 161 | white-space: nowrap; 162 | } 163 | .rbc-btn-group > button:first-child:not(:last-child) { 164 | border-top-right-radius: 0; 165 | border-bottom-right-radius: 0; 166 | } 167 | .rbc-btn-group > button:last-child:not(:first-child) { 168 | border-top-left-radius: 0; 169 | border-bottom-left-radius: 0; 170 | } 171 | .rbc-rtl .rbc-btn-group > button:first-child:not(:last-child) { 172 | border-radius: 4px; 173 | border-top-left-radius: 0; 174 | border-bottom-left-radius: 0; 175 | } 176 | .rbc-rtl .rbc-btn-group > button:last-child:not(:first-child) { 177 | border-radius: 4px; 178 | border-top-right-radius: 0; 179 | border-bottom-right-radius: 0; 180 | } 181 | .rbc-btn-group > button:not(:first-child):not(:last-child) { 182 | border-radius: 0; 183 | } 184 | .rbc-btn-group button + button { 185 | margin-left: -1px; 186 | } 187 | .rbc-rtl .rbc-btn-group button + button { 188 | margin-left: 0; 189 | margin-right: -1px; 190 | } 191 | .rbc-btn-group + .rbc-btn-group, 192 | .rbc-btn-group + button { 193 | margin-left: 10px; 194 | } 195 | .rbc-event { 196 | padding: 2px 5px; 197 | background-color: #3174ad; 198 | border-radius: 5px; 199 | color: #fff; 200 | cursor: pointer; 201 | } 202 | .rbc-slot-selecting .rbc-event { 203 | cursor: inherit; 204 | pointer-events: none; 205 | } 206 | .rbc-event.rbc-selected { 207 | background-color: #265985; 208 | } 209 | .rbc-event-label { 210 | font-size: 80%; 211 | } 212 | .rbc-event-overlaps { 213 | box-shadow: -1px 1px 5px 0px rgba(51, 51, 51, 0.5); 214 | } 215 | .rbc-event-continues-prior { 216 | border-top-left-radius: 0; 217 | border-bottom-left-radius: 0; 218 | } 219 | .rbc-event-continues-after { 220 | border-top-right-radius: 0; 221 | border-bottom-right-radius: 0; 222 | } 223 | .rbc-event-continues-earlier { 224 | border-top-left-radius: 0; 225 | border-top-right-radius: 0; 226 | } 227 | .rbc-event-continues-later { 228 | border-bottom-left-radius: 0; 229 | border-bottom-right-radius: 0; 230 | } 231 | .rbc-event-continues-day-after { 232 | border-bottom-left-radius: 0; 233 | border-bottom-right-radius: 0; 234 | } 235 | .rbc-event-continues-day-prior { 236 | border-top-left-radius: 0; 237 | border-top-right-radius: 0; 238 | } 239 | .rbc-row { 240 | display: -webkit-flex; 241 | display: -ms-flexbox; 242 | display: flex; 243 | -webkit-flex-direction: row; 244 | -ms-flex-direction: row; 245 | flex-direction: row; 246 | } 247 | .rbc-row-segment { 248 | padding: 0 1px 1px 1px; 249 | } 250 | .rbc-selected-cell { 251 | background-color: rgba(0, 0, 0, 0.1); 252 | } 253 | .rbc-show-more { 254 | background-color: rgba(255, 255, 255, 0.3); 255 | z-index: 4; 256 | font-weight: bold; 257 | font-size: 85%; 258 | height: auto; 259 | line-height: normal; 260 | white-space: nowrap; 261 | } 262 | .rbc-month-view { 263 | position: relative; 264 | border: 1px solid #DDD; 265 | display: -webkit-flex; 266 | display: -ms-flexbox; 267 | display: flex; 268 | -webkit-flex-direction: column; 269 | -ms-flex-direction: column; 270 | flex-direction: column; 271 | -webkit-flex: 1 0 0; 272 | -ms-flex: 1 0 0px; 273 | flex: 1 0 0; 274 | width: 100%; 275 | -moz-user-select: none; 276 | -ms-user-select: none; 277 | user-select: none; 278 | -webkit-user-select: none; 279 | height: 100%; 280 | } 281 | .rbc-month-header { 282 | display: -webkit-flex; 283 | display: -ms-flexbox; 284 | display: flex; 285 | -webkit-flex-direction: row; 286 | -ms-flex-direction: row; 287 | flex-direction: row; 288 | } 289 | .rbc-month-row { 290 | display: -webkit-flex; 291 | display: -ms-flexbox; 292 | display: flex; 293 | position: relative; 294 | -webkit-flex-direction: column; 295 | -ms-flex-direction: column; 296 | flex-direction: column; 297 | -webkit-flex: 1 0 0; 298 | -ms-flex: 1 0 0px; 299 | flex: 1 0 0; 300 | -webkit-flex-basis: 0px; 301 | -ms-flex-preferred-size: 0px; 302 | flex-basis: 0px; 303 | overflow: hidden; 304 | height: 100%; 305 | } 306 | .rbc-month-row + .rbc-month-row { 307 | border-top: 1px solid #DDD; 308 | } 309 | .rbc-date-cell { 310 | -webkit-flex: 1 1 0; 311 | -ms-flex: 1 1 0px; 312 | flex: 1 1 0; 313 | min-width: 0; 314 | padding-right: 5px; 315 | text-align: right; 316 | } 317 | .rbc-date-cell.rbc-now { 318 | font-weight: bold; 319 | } 320 | .rbc-date-cell > a, 321 | .rbc-date-cell > a:active, 322 | .rbc-date-cell > a:visited { 323 | color: inherit; 324 | text-decoration: none; 325 | } 326 | .rbc-row-bg { 327 | display: -webkit-flex; 328 | display: -ms-flexbox; 329 | display: flex; 330 | -webkit-flex-direction: row; 331 | -ms-flex-direction: row; 332 | flex-direction: row; 333 | -webkit-flex: 1 0 0; 334 | -ms-flex: 1 0 0px; 335 | flex: 1 0 0; 336 | overflow: hidden; 337 | } 338 | .rbc-day-bg { 339 | -webkit-flex: 1 0 0%; 340 | -ms-flex: 1 0 0%; 341 | flex: 1 0 0%; 342 | } 343 | .rbc-day-bg + .rbc-day-bg { 344 | border-left: 1px solid #DDD; 345 | } 346 | .rbc-rtl .rbc-day-bg + .rbc-day-bg { 347 | border-left-width: 0; 348 | border-right: 1px solid #DDD; 349 | } 350 | .rbc-overlay { 351 | position: absolute; 352 | z-index: 5; 353 | border: 1px solid #e5e5e5; 354 | background-color: #fff; 355 | box-shadow: 0 5px 15px rgba(0, 0, 0, 0.25); 356 | padding: 10px; 357 | } 358 | .rbc-overlay > * + * { 359 | margin-top: 1px; 360 | } 361 | .rbc-overlay-header { 362 | border-bottom: 1px solid #e5e5e5; 363 | margin: -10px -10px 5px -10px; 364 | padding: 2px 10px; 365 | } 366 | .rbc-agenda-view { 367 | display: -webkit-flex; 368 | display: -ms-flexbox; 369 | display: flex; 370 | -webkit-flex-direction: column; 371 | -ms-flex-direction: column; 372 | flex-direction: column; 373 | -webkit-flex: 1 0 0; 374 | -ms-flex: 1 0 0px; 375 | flex: 1 0 0; 376 | overflow: auto; 377 | } 378 | .rbc-agenda-view table.rbc-agenda-table { 379 | width: 100%; 380 | border: 1px solid #DDD; 381 | border-spacing: 0; 382 | border-collapse: collapse; 383 | } 384 | .rbc-agenda-view table.rbc-agenda-table tbody > tr > td { 385 | padding: 5px 10px; 386 | vertical-align: top; 387 | } 388 | .rbc-agenda-view table.rbc-agenda-table .rbc-agenda-time-cell { 389 | padding-left: 15px; 390 | padding-right: 15px; 391 | text-transform: lowercase; 392 | } 393 | .rbc-agenda-view table.rbc-agenda-table tbody > tr > td + td { 394 | border-left: 1px solid #DDD; 395 | } 396 | .rbc-rtl .rbc-agenda-view table.rbc-agenda-table tbody > tr > td + td { 397 | border-left-width: 0; 398 | border-right: 1px solid #DDD; 399 | } 400 | .rbc-agenda-view table.rbc-agenda-table tbody > tr + tr { 401 | border-top: 1px solid #DDD; 402 | } 403 | .rbc-agenda-view table.rbc-agenda-table thead > tr > th { 404 | padding: 3px 5px; 405 | text-align: left; 406 | border-bottom: 1px solid #DDD; 407 | } 408 | .rbc-rtl .rbc-agenda-view table.rbc-agenda-table thead > tr > th { 409 | text-align: right; 410 | } 411 | .rbc-agenda-time-cell { 412 | text-transform: lowercase; 413 | } 414 | .rbc-agenda-time-cell .rbc-continues-after:after { 415 | content: ' »'; 416 | } 417 | .rbc-agenda-time-cell .rbc-continues-prior:before { 418 | content: '« '; 419 | } 420 | .rbc-agenda-date-cell, 421 | .rbc-agenda-time-cell { 422 | white-space: nowrap; 423 | } 424 | .rbc-agenda-event-cell { 425 | width: 100%; 426 | } 427 | .rbc-time-column { 428 | display: -webkit-flex; 429 | display: -ms-flexbox; 430 | display: flex; 431 | -webkit-flex-direction: column; 432 | -ms-flex-direction: column; 433 | flex-direction: column; 434 | min-height: 100%; 435 | } 436 | .rbc-time-column .rbc-timeslot-group { 437 | -webkit-flex: 1; 438 | -ms-flex: 1; 439 | flex: 1; 440 | } 441 | .rbc-timeslot-group { 442 | border-bottom: 1px solid #DDD; 443 | min-height: 40px; 444 | display: -webkit-flex; 445 | display: -ms-flexbox; 446 | display: flex; 447 | -webkit-flex-flow: column nowrap; 448 | -ms-flex-flow: column nowrap; 449 | flex-flow: column nowrap; 450 | } 451 | .rbc-time-gutter, 452 | .rbc-header-gutter { 453 | -webkit-flex: none; 454 | -ms-flex: none; 455 | flex: none; 456 | } 457 | .rbc-label { 458 | padding: 0 5px; 459 | } 460 | .rbc-day-slot { 461 | position: relative; 462 | } 463 | .rbc-day-slot .rbc-events-container { 464 | bottom: 0; 465 | left: 0; 466 | position: absolute; 467 | right: 10px; 468 | top: 0; 469 | } 470 | .rbc-day-slot .rbc-events-container.rbc-is-rtl { 471 | left: 10px; 472 | right: 0; 473 | } 474 | .rbc-day-slot .rbc-event { 475 | border: 1px solid #265985; 476 | display: -webkit-flex; 477 | display: -ms-flexbox; 478 | display: flex; 479 | max-height: 100%; 480 | min-height: 20px; 481 | -webkit-flex-flow: column wrap; 482 | -ms-flex-flow: column wrap; 483 | flex-flow: column wrap; 484 | -webkit-align-items: flex-start; 485 | -ms-flex-align: start; 486 | align-items: flex-start; 487 | overflow: hidden; 488 | position: absolute; 489 | } 490 | .rbc-day-slot .rbc-event-label { 491 | -webkit-flex: none; 492 | -ms-flex: none; 493 | flex: none; 494 | padding-right: 5px; 495 | width: auto; 496 | } 497 | .rbc-day-slot .rbc-event-content { 498 | width: 100%; 499 | -webkit-flex: 1 1 0; 500 | -ms-flex: 1 1 0px; 501 | flex: 1 1 0; 502 | word-wrap: break-word; 503 | line-height: 1; 504 | height: 100%; 505 | min-height: 1em; 506 | } 507 | .rbc-day-slot .rbc-time-slot { 508 | border-top: 1px solid #f7f7f7; 509 | } 510 | .rbc-time-slot { 511 | -webkit-flex: 1 0 0; 512 | -ms-flex: 1 0 0px; 513 | flex: 1 0 0; 514 | } 515 | .rbc-time-slot.rbc-now { 516 | font-weight: bold; 517 | } 518 | .rbc-day-header { 519 | text-align: center; 520 | } 521 | .rbc-slot-selection { 522 | z-index: 10; 523 | position: absolute; 524 | background-color: rgba(0, 0, 0, 0.5); 525 | color: white; 526 | font-size: 75%; 527 | width: 100%; 528 | padding: 3px; 529 | } 530 | .rbc-slot-selecting { 531 | cursor: move; 532 | } 533 | .rbc-time-view { 534 | display: -webkit-flex; 535 | display: -ms-flexbox; 536 | display: flex; 537 | -webkit-flex-direction: column; 538 | -ms-flex-direction: column; 539 | flex-direction: column; 540 | -webkit-flex: 1; 541 | -ms-flex: 1; 542 | flex: 1; 543 | width: 100%; 544 | border: 1px solid #DDD; 545 | min-height: 0; 546 | } 547 | .rbc-time-view .rbc-time-gutter { 548 | white-space: nowrap; 549 | } 550 | .rbc-time-view .rbc-allday-cell { 551 | box-sizing: content-box; 552 | width: 100%; 553 | position: relative; 554 | } 555 | .rbc-time-view .rbc-allday-events { 556 | position: relative; 557 | z-index: 4; 558 | } 559 | .rbc-time-view .rbc-row { 560 | box-sizing: border-box; 561 | min-height: 20px; 562 | } 563 | .rbc-time-header { 564 | display: -webkit-flex; 565 | display: -ms-flexbox; 566 | display: flex; 567 | -webkit-flex: 0 0 auto; 568 | -ms-flex: 0 0 auto; 569 | flex: 0 0 auto; 570 | -webkit-flex-direction: row; 571 | -ms-flex-direction: row; 572 | flex-direction: row; 573 | } 574 | .rbc-time-header.rbc-overflowing { 575 | border-right: 1px solid #DDD; 576 | } 577 | .rbc-rtl .rbc-time-header.rbc-overflowing { 578 | border-right-width: 0; 579 | border-left: 1px solid #DDD; 580 | } 581 | .rbc-time-header > .rbc-row:first-child { 582 | border-bottom: 1px solid #DDD; 583 | } 584 | .rbc-time-header > .rbc-row.rbc-row-resource { 585 | border-bottom: 1px solid #DDD; 586 | } 587 | .rbc-time-header-content { 588 | -webkit-flex: 1; 589 | -ms-flex: 1; 590 | flex: 1; 591 | min-width: 0; 592 | -webkit-flex-direction: column; 593 | -ms-flex-direction: column; 594 | flex-direction: column; 595 | border-left: 1px solid #DDD; 596 | } 597 | .rbc-rtl .rbc-time-header-content { 598 | border-left-width: 0; 599 | border-right: 1px solid #DDD; 600 | } 601 | .rbc-time-content { 602 | display: -webkit-flex; 603 | display: -ms-flexbox; 604 | display: flex; 605 | -webkit-flex: 1 0 0%; 606 | -ms-flex: 1 0 0%; 607 | flex: 1 0 0%; 608 | -webkit-align-items: flex-start; 609 | -ms-flex-align: start; 610 | align-items: flex-start; 611 | width: 100%; 612 | border-top: 2px solid #DDD; 613 | overflow-y: auto; 614 | position: relative; 615 | } 616 | .rbc-time-content > .rbc-time-gutter { 617 | -webkit-flex: none; 618 | -ms-flex: none; 619 | flex: none; 620 | } 621 | .rbc-time-content > * + * > * { 622 | border-left: 1px solid #DDD; 623 | } 624 | .rbc-rtl .rbc-time-content > * + * > * { 625 | border-left-width: 0; 626 | border-right: 1px solid #DDD; 627 | } 628 | .rbc-time-content > .rbc-day-slot { 629 | width: 100%; 630 | -moz-user-select: none; 631 | -ms-user-select: none; 632 | user-select: none; 633 | -webkit-user-select: none; 634 | } 635 | .rbc-current-time-indicator { 636 | position: absolute; 637 | z-index: 3; 638 | height: 1px; 639 | background-color: #74ad31; 640 | pointer-events: none; 641 | } 642 | -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/styles/dragAndDrop/styles.less: -------------------------------------------------------------------------------- 1 | @import '../less/variables.less'; 2 | 3 | .rbc-addons-dnd { 4 | .rbc-row-content { 5 | pointer-events: none; 6 | 7 | & .rbc-show-more, 8 | & .rbc-event { 9 | pointer-events: all; 10 | } 11 | } 12 | 13 | .rbc-addons-dnd-over { 14 | background-color: rgba( 15 | red(@date-selection-bg-color), 16 | green(@date-selection-bg-color), 17 | blue(@date-selection-bg-color), 18 | .3 19 | ); 20 | } 21 | 22 | .rbc-events-container { 23 | pointer-events: none; 24 | } 25 | 26 | .rbc-event { 27 | transition: opacity 150ms; 28 | pointer-events: all; 29 | 30 | &:hover { 31 | .rbc-addons-dnd-resize-ns-icon, .rbc-addons-dnd-resize-ew-icon { display: block; } 32 | } 33 | } 34 | 35 | &.rbc-addons-dnd-is-dragging .rbc-event { 36 | pointer-events: none; 37 | opacity: .50; 38 | } 39 | 40 | .rbc-addons-dnd-resizable { 41 | position: relative; 42 | width: 100%; 43 | height: 100%; 44 | } 45 | 46 | .rbc-addons-dnd-resize-ns-anchor { 47 | width: 100%; 48 | text-align: center; 49 | position: absolute; 50 | &:first-child { top: 0; } 51 | &:last-child { bottom: 0; } 52 | 53 | .rbc-addons-dnd-resize-ns-icon { 54 | display: none; 55 | border-top: 3px double; 56 | margin: 0 auto; 57 | width: 10px; 58 | cursor: ns-resize; 59 | } 60 | } 61 | 62 | .rbc-addons-dnd-resize-ew-anchor { 63 | position: absolute; 64 | top: 4px; 65 | bottom: 0; 66 | &:first-child { left: 0; } 67 | &:last-child { right: 0; } 68 | 69 | .rbc-addons-dnd-resize-ew-icon { 70 | display: none; 71 | border-left: 3px double; 72 | margin-top: auto; 73 | margin-bottom: auto; 74 | height: 10px; 75 | cursor: ew-resize; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/styles/less/agenda.less: -------------------------------------------------------------------------------- 1 | @import 'variables.less'; 2 | 3 | .rbc-agenda-view { 4 | display: flex; 5 | flex-direction: column; 6 | flex: 1 0 0; 7 | overflow: auto; 8 | 9 | table.rbc-agenda-table { 10 | width: 100%; 11 | border: 1px solid @cell-border; 12 | border-spacing: 0; 13 | border-collapse: collapse; 14 | 15 | tbody > tr > td { 16 | padding: 5px 10px; 17 | vertical-align: top; 18 | } 19 | 20 | .rbc-agenda-time-cell { 21 | padding-left: 15px; 22 | padding-right: 15px; 23 | text-transform: lowercase; 24 | } 25 | 26 | tbody > tr > td + td { 27 | border-left: 1px solid @cell-border; 28 | } 29 | 30 | .rbc-rtl & { 31 | tbody > tr > td + td { 32 | border-left-width: 0; 33 | border-right: 1px solid @cell-border; 34 | } 35 | } 36 | 37 | tbody > tr + tr { 38 | border-top: 1px solid @cell-border; 39 | } 40 | 41 | thead > tr > th { 42 | padding: 3px 5px; 43 | text-align: left; 44 | border-bottom: 1px solid @cell-border; 45 | 46 | .rbc-rtl & { 47 | text-align: right; 48 | } 49 | } 50 | } 51 | } 52 | 53 | .rbc-agenda-time-cell { 54 | text-transform: lowercase; 55 | 56 | .rbc-continues-after:after { 57 | content: ' »' 58 | } 59 | .rbc-continues-prior:before { 60 | content: '« ' 61 | } 62 | } 63 | 64 | .rbc-agenda-date-cell, 65 | .rbc-agenda-time-cell { 66 | white-space: nowrap; 67 | } 68 | 69 | 70 | 71 | .rbc-agenda-event-cell { 72 | width: 100% 73 | } 74 | -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/styles/less/event.less: -------------------------------------------------------------------------------- 1 | @import 'variables.less'; 2 | 3 | .rbc-event { 4 | padding: @event-padding; 5 | background-color: @event-bg; 6 | border-radius: @event-border-radius; 7 | color: @event-color; 8 | cursor: pointer; 9 | 10 | .rbc-slot-selecting & { 11 | cursor: inherit; 12 | pointer-events: none; 13 | } 14 | 15 | &.rbc-selected { 16 | background-color: darken(@event-bg, 10%); 17 | } 18 | } 19 | 20 | .rbc-event-label { 21 | &:extend(.rbc-ellipsis); 22 | font-size: 80%; 23 | } 24 | 25 | .rbc-event-overlaps { 26 | box-shadow: -1px 1px 5px 0px rgba(51,51,51,.5); 27 | } 28 | 29 | .rbc-event-continues-prior { 30 | border-top-left-radius: 0; 31 | border-bottom-left-radius: 0; 32 | } 33 | .rbc-event-continues-after { 34 | border-top-right-radius: 0; 35 | border-bottom-right-radius: 0; 36 | } 37 | 38 | 39 | .rbc-event-continues-earlier { 40 | border-top-left-radius: 0; 41 | border-top-right-radius: 0; 42 | } 43 | .rbc-event-continues-later { 44 | border-bottom-left-radius: 0; 45 | border-bottom-right-radius: 0; 46 | } 47 | 48 | .rbc-event-continues-day-after { 49 | border-bottom-left-radius: 0; 50 | border-bottom-right-radius: 0; 51 | } 52 | 53 | .rbc-event-continues-day-prior { 54 | border-top-left-radius: 0; 55 | border-top-right-radius: 0; 56 | } 57 | -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/styles/less/month.less: -------------------------------------------------------------------------------- 1 | @import 'variables.less'; 2 | 3 | 4 | .rbc-row { 5 | display: flex; 6 | flex-direction: row; 7 | } 8 | 9 | .rbc-row-segment { 10 | padding: 0 1px 1px 1px; 11 | 12 | .rbc-event-content { 13 | &:extend(.rbc-ellipsis); 14 | } 15 | } 16 | 17 | .rbc-selected-cell { 18 | background-color: @date-selection-bg-color; 19 | } 20 | 21 | 22 | .rbc-show-more { 23 | &:extend(.rbc-ellipsis); 24 | background-color: rgba(255, 255, 255, 0.3); 25 | z-index: @event-zindex; 26 | font-weight: bold; 27 | font-size: 85%; 28 | height: auto; 29 | line-height: normal; 30 | white-space: nowrap; 31 | } 32 | 33 | .rbc-month-view { 34 | position: relative; 35 | border: 1px solid @calendar-border; 36 | display: flex; 37 | flex-direction: column; 38 | flex: 1 0 0; 39 | width: 100%; 40 | user-select: none; 41 | -webkit-user-select: none; 42 | 43 | height: 100%; // ie-fix 44 | } 45 | 46 | .rbc-month-header { 47 | display: flex; 48 | flex-direction: row; 49 | } 50 | 51 | .rbc-month-row { 52 | display: flex; 53 | position: relative; 54 | flex-direction: column; 55 | flex: 1 0 0; // postcss will remove the 0px here hence the duplication below 56 | flex-basis: 0px; 57 | overflow: hidden; 58 | 59 | height: 100%; // ie-fix 60 | 61 | & + & { 62 | border-top: 1px solid @cell-border; 63 | } 64 | } 65 | 66 | .rbc-date-cell { 67 | flex: 1 1 0; 68 | min-width: 0; 69 | padding-right: 5px; 70 | text-align: right; 71 | 72 | &.rbc-now { 73 | font-weight: bold; 74 | } 75 | 76 | > a { 77 | &, &:active, &:visited { 78 | color: inherit; 79 | text-decoration: none; 80 | } 81 | } 82 | } 83 | 84 | .rbc-row-bg { 85 | &:extend(.rbc-abs-full); 86 | display: flex; 87 | flex-direction: row; 88 | flex: 1 0 0; 89 | overflow: hidden; 90 | } 91 | 92 | .rbc-day-bg { 93 | flex: 1 0 0%; 94 | 95 | & + & { 96 | border-left: 1px solid @cell-border; 97 | } 98 | 99 | .rbc-rtl & + & { 100 | border-left-width: 0; 101 | border-right: 1px solid @cell-border; 102 | } 103 | } 104 | 105 | .rbc-overlay { 106 | position: absolute; 107 | z-index: @event-zindex + 1; 108 | border: 1px solid #e5e5e5; 109 | background-color: #fff; 110 | box-shadow: 0 5px 15px rgba(0,0,0,.25); 111 | padding: 10px; 112 | 113 | > * + * { 114 | margin-top: 1px; 115 | } 116 | } 117 | 118 | .rbc-overlay-header { 119 | border-bottom: 1px solid #e5e5e5; 120 | margin: -10px -10px 5px -10px ; 121 | padding: 2px 10px; 122 | } 123 | -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/styles/less/reset.less: -------------------------------------------------------------------------------- 1 | .rbc-btn { 2 | color: inherit; 3 | font: inherit; 4 | margin: 0; 5 | } 6 | 7 | button.rbc-btn { 8 | overflow: visible; 9 | text-transform: none; 10 | -webkit-appearance: button; 11 | cursor: pointer; 12 | } 13 | 14 | button[disabled].rbc-btn { 15 | cursor: not-allowed; 16 | } 17 | 18 | button.rbc-input::-moz-focus-inner { 19 | border: 0; 20 | padding: 0; 21 | } 22 | -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/styles/less/styles.less: -------------------------------------------------------------------------------- 1 | @import 'variables.less'; 2 | @import 'reset.less'; 3 | 4 | 5 | .rbc-calendar { 6 | box-sizing: border-box; 7 | height: 100%; 8 | display: flex; 9 | flex-direction: column; 10 | align-items: stretch; 11 | } 12 | 13 | .rbc-calendar *, 14 | .rbc-calendar *:before, 15 | .rbc-calendar *:after { 16 | box-sizing: inherit; 17 | } 18 | 19 | .rbc-abs-full { 20 | overflow: hidden; 21 | position: absolute; 22 | top: 0; 23 | left: 0; 24 | right: 0; 25 | bottom: 0; 26 | } 27 | 28 | .rbc-ellipsis { 29 | display: block; 30 | overflow: hidden; 31 | text-overflow: ellipsis; 32 | white-space: nowrap; 33 | } 34 | 35 | .rbc-rtl { 36 | direction: rtl; 37 | } 38 | 39 | .rbc-off-range { 40 | color: @out-of-range-color; 41 | } 42 | 43 | .rbc-off-range-bg { 44 | background: @out-of-range-bg-color; 45 | } 46 | 47 | .rbc-header { 48 | overflow: hidden; 49 | flex: 1 0 0%; 50 | text-overflow: ellipsis; 51 | white-space: nowrap; 52 | padding: 0 3px; 53 | text-align: center; 54 | vertical-align: middle; 55 | font-weight: bold; 56 | font-size: 90%; 57 | min-height: 0; 58 | border-bottom: 1px solid @cell-border; 59 | 60 | & + & { 61 | border-left: 1px solid @cell-border; 62 | } 63 | 64 | .rbc-rtl & + & { 65 | border-left-width: 0; 66 | border-right: 1px solid @cell-border; 67 | } 68 | 69 | & > a { 70 | &, &:active, &:visited { 71 | color: inherit; 72 | text-decoration: none; 73 | } 74 | } 75 | } 76 | 77 | .rbc-row-content { 78 | position: relative; 79 | user-select: none; 80 | -webkit-user-select: none; 81 | z-index: 4; 82 | } 83 | 84 | .rbc-today { 85 | background-color: @today-highlight-bg; 86 | } 87 | 88 | 89 | @import 'toolbar.less'; 90 | @import 'event.less'; 91 | @import 'month.less'; 92 | @import 'agenda.less'; 93 | @import 'time-grid.less'; 94 | -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/styles/less/time-column.less: -------------------------------------------------------------------------------- 1 | @import 'variables.less'; 2 | 3 | 4 | .rbc-time-column { 5 | display: flex; 6 | flex-direction: column; 7 | min-height: 100%; 8 | 9 | .rbc-timeslot-group { 10 | flex: 1; 11 | } 12 | } 13 | 14 | 15 | .rbc-timeslot-group { 16 | border-bottom: 1px solid @cell-border; 17 | min-height: 40px; 18 | display: flex; 19 | flex-flow: column nowrap; 20 | } 21 | 22 | .rbc-time-gutter, 23 | .rbc-header-gutter { 24 | flex: none; 25 | } 26 | 27 | .rbc-label { 28 | padding: 0 5px; 29 | } 30 | 31 | .rbc-day-slot { 32 | position: relative; 33 | 34 | .rbc-events-container { 35 | bottom: 0; 36 | left: 0; 37 | position: absolute; 38 | right: 10px; 39 | top: 0; 40 | 41 | &.rbc-is-rtl { 42 | left: 10px; 43 | right: 0; 44 | } 45 | } 46 | 47 | .rbc-event { 48 | border: 1px solid @event-border; 49 | display: flex; 50 | max-height: 100%; 51 | min-height: 20px; 52 | flex-flow: column wrap; 53 | align-items: flex-start; 54 | overflow: hidden; 55 | position: absolute; 56 | } 57 | 58 | .rbc-event-label { 59 | flex: none; 60 | padding-right: 5px; 61 | width: auto; 62 | } 63 | 64 | .rbc-event-content { 65 | width: 100%; 66 | flex: 1 1 0; 67 | word-wrap: break-word; 68 | line-height: 1; 69 | height: 100%; 70 | min-height: 1em; 71 | } 72 | 73 | .rbc-time-slot { 74 | border-top: 1px solid lighten(@cell-border, 10%); 75 | } 76 | } 77 | 78 | .rbc-time-slot { 79 | flex: 1 0 0; 80 | 81 | &.rbc-now { 82 | font-weight: bold; 83 | } 84 | } 85 | 86 | .rbc-day-header { 87 | text-align: center; 88 | } 89 | -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/styles/less/time-grid.less: -------------------------------------------------------------------------------- 1 | @import 'variables.less'; 2 | @import 'time-column.less'; 3 | 4 | .rbc-slot-selection { 5 | z-index: 10; 6 | position: absolute; 7 | background-color: @time-selection-bg-color; 8 | color: @time-selection-color; 9 | font-size: 75%; 10 | width: 100%; 11 | padding: 3px; 12 | } 13 | 14 | .rbc-slot-selecting { 15 | cursor: move; 16 | } 17 | 18 | .rbc-time-view { 19 | display: flex; 20 | flex-direction: column; 21 | flex: 1; 22 | width: 100%; 23 | border: 1px solid @calendar-border; 24 | min-height: 0; 25 | 26 | .rbc-time-gutter { 27 | white-space: nowrap; 28 | } 29 | 30 | .rbc-allday-cell { 31 | box-sizing: content-box; 32 | width: 100%; 33 | position: relative; 34 | } 35 | 36 | .rbc-allday-events { 37 | position: relative; 38 | z-index: 4; 39 | } 40 | 41 | .rbc-row { 42 | box-sizing: border-box; 43 | min-height: 20px; 44 | } 45 | } 46 | 47 | 48 | .rbc-time-header { 49 | display: flex; 50 | flex: 0 0 auto; // should not shrink below height 51 | flex-direction: row; 52 | 53 | &.rbc-overflowing { 54 | border-right: 1px solid @cell-border; 55 | } 56 | 57 | .rbc-rtl &.rbc-overflowing { 58 | border-right-width: 0; 59 | border-left: 1px solid @cell-border; 60 | } 61 | 62 | > .rbc-row:first-child { 63 | border-bottom: 1px solid @cell-border; 64 | } 65 | 66 | > .rbc-row.rbc-row-resource { 67 | border-bottom: 1px solid @cell-border; 68 | } 69 | 70 | // .rbc-gutter-cell { 71 | // flex: none; 72 | // } 73 | 74 | // > .rbc-gutter-cell + * { 75 | // width: 100%; 76 | // } 77 | } 78 | 79 | .rbc-time-header-content { 80 | flex: 1; 81 | min-width: 0; 82 | flex-direction: column; 83 | border-left: 1px solid @cell-border; 84 | 85 | .rbc-rtl & { 86 | border-left-width: 0; 87 | border-right: 1px solid @cell-border; 88 | } 89 | } 90 | 91 | .rbc-time-content { 92 | display: flex; 93 | flex: 1 0 0%; 94 | align-items: flex-start; 95 | width: 100%; 96 | border-top: 2px solid @calendar-border; 97 | overflow-y: auto; 98 | position: relative; 99 | 100 | > .rbc-time-gutter { 101 | flex: none; 102 | } 103 | 104 | > * + * > * { 105 | border-left: 1px solid @cell-border; 106 | } 107 | 108 | .rbc-rtl & > * + * > * { 109 | border-left-width: 0; 110 | border-right: 1px solid @cell-border; 111 | } 112 | 113 | > .rbc-day-slot { 114 | width: 100%; 115 | user-select: none; 116 | -webkit-user-select: none; 117 | } 118 | } 119 | 120 | .rbc-current-time-indicator { 121 | position: absolute; 122 | z-index: 3; 123 | height: 1px; 124 | 125 | background-color: @current-time-color; 126 | pointer-events: none; 127 | } 128 | -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/styles/less/toolbar.less: -------------------------------------------------------------------------------- 1 | @import 'variables.less'; 2 | 3 | @active-background: darken(@btn-bg, 10%); 4 | @active-border: darken(@btn-border, 12%); 5 | 6 | .rbc-toolbar { 7 | display: flex; 8 | align-items: center; 9 | margin-bottom: 10px; 10 | font-size: 16px; 11 | 12 | .rbc-toolbar-label { 13 | flex-grow:1; 14 | padding: 0 10px; 15 | text-align: center; 16 | } 17 | 18 | & button { 19 | color: @btn-color; 20 | display: inline-block; 21 | margin: 0; 22 | text-align: center; 23 | vertical-align: middle; 24 | background: none; 25 | background-image: none; 26 | border: 1px solid @btn-border; 27 | padding: .375rem 1rem; 28 | border-radius: 4px; 29 | line-height: normal; 30 | white-space: nowrap; 31 | 32 | &:active, 33 | &.rbc-active { 34 | background-image: none; 35 | box-shadow: inset 0 3px 5px rgba(0,0,0,.125); 36 | background-color: @active-background; 37 | border-color: @active-border; 38 | 39 | &:hover, 40 | &:focus { 41 | color: @btn-color; 42 | background-color: darken(@btn-bg, 17%); 43 | border-color: darken(@btn-border, 25%); 44 | } 45 | } 46 | 47 | &:focus { 48 | color: @btn-color; 49 | background-color: @active-background; 50 | border-color: @active-border; 51 | } 52 | 53 | &:hover { 54 | color: @btn-color; 55 | background-color: @active-background; 56 | border-color: @active-border; 57 | } 58 | } 59 | } 60 | 61 | .rbc-btn-group { 62 | display: inline-block; 63 | white-space: nowrap; 64 | 65 | > button:first-child:not(:last-child) { 66 | border-top-right-radius: 0; 67 | border-bottom-right-radius: 0; 68 | } 69 | 70 | > button:last-child:not(:first-child) { 71 | border-top-left-radius: 0; 72 | border-bottom-left-radius: 0; 73 | } 74 | 75 | .rbc-rtl & > button:first-child:not(:last-child) { 76 | border-radius: 4px; 77 | border-top-left-radius: 0; 78 | border-bottom-left-radius: 0; 79 | } 80 | 81 | .rbc-rtl & > button:last-child:not(:first-child) { 82 | border-radius: 4px; 83 | border-top-right-radius: 0; 84 | border-bottom-right-radius: 0; 85 | } 86 | 87 | > button:not(:first-child):not(:last-child) { 88 | border-radius: 0; 89 | } 90 | 91 | button + button { 92 | margin-left: -1px; 93 | } 94 | 95 | .rbc-rtl & button + button { 96 | margin-left: 0; 97 | margin-right: -1px; 98 | } 99 | 100 | & + &, 101 | & + button { 102 | margin-left: 10px; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/modules/web/pages/dashboard/styles/less/variables.less: -------------------------------------------------------------------------------- 1 | 2 | @out-of-range-color: lighten(#333, 40%); 3 | @out-of-range-bg-color: lighten(#333, 70%); 4 | 5 | @calendar-border: #DDD; 6 | @cell-border: #DDD; 7 | 8 | @border-color: #CCC; 9 | 10 | @segment-width: percentage(1 / 7); 11 | 12 | @time-selection-color: white; 13 | @time-selection-bg-color: rgba(0,0,0, .50); 14 | @date-selection-bg-color: rgba(0,0,0, .10); 15 | 16 | 17 | @event-bg: #3174ad; 18 | @event-border: darken(#3174ad, 10%); 19 | @event-color: #fff; 20 | @event-border-radius: 5px; 21 | @event-padding: 2px 5px; 22 | @event-zindex: 4; 23 | 24 | @btn-color: #373a3c; 25 | @btn-bg: #fff; 26 | @btn-border: #ccc; 27 | 28 | @current-time-color: #74ad31; 29 | 30 | @rbc-css-prefix: rbc-i; 31 | 32 | @today-highlight-bg: #eaf6ff; 33 | -------------------------------------------------------------------------------- /src/modules/web/pages/home/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class Home extends Component { 4 | render() { 5 | return
Home. Not Protected. Anyone can see this.
; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/modules/web/routes.js: -------------------------------------------------------------------------------- 1 | // import lib 2 | import Loadable from 'react-loadable' 3 | 4 | // import components 5 | import LoadingComponent from '../../components/common/loader' 6 | 7 | const routes = [ 8 | { 9 | path: '/', 10 | exact: true, 11 | component: Loadable({ 12 | loader: () => import('./pages/home'), 13 | loading: LoadingComponent, 14 | }), 15 | }, 16 | { 17 | path: '/dashboard', 18 | exact: true, 19 | auth: true, 20 | component: Loadable({ 21 | loader: () => import('./pages/dashboard'), 22 | loading: LoadingComponent, 23 | }), 24 | }, 25 | 26 | ] 27 | 28 | export default routes 29 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | } else { 39 | // Is not local host. Just register service worker 40 | registerValidSW(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/routes/Private.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Route, Redirect } from 'react-router-dom' 4 | 5 | const PrivateRoute = ({component: Component, authed, ...rest}) => { 6 | return 7 | authed === true ? ( 8 | 9 | ) : ( 10 | 13 | )} 14 | /> 15 | 16 | } 17 | 18 | PrivateRoute.propTypes = { 19 | component: PropTypes.func.isRequired, 20 | location: PropTypes.object, 21 | authed: PropTypes.bool.isRequired, 22 | } 23 | 24 | export default PrivateRoute 25 | -------------------------------------------------------------------------------- /src/routes/Public.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Route, Redirect } from 'react-router-dom' 4 | 5 | const PublicRoutes = ({component: Component, authed, routeAuth, ...rest}) => { 6 | 7 | return 8 | authed === false || routeAuth === undefined? ( 9 | 10 | ) : ( 11 | 12 | ) 13 | } /> 14 | } 15 | 16 | PublicRoutes.propTypes = { 17 | component: PropTypes.func.isRequired, 18 | location: PropTypes.object, 19 | }; 20 | 21 | export default PublicRoutes 22 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | // import libs 2 | import React, { Component } from 'react' 3 | import { BrowserRouter as Router, Switch } from 'react-router-dom' 4 | import createBrowserHistory from 'history/createBrowserHistory' 5 | // import services actions 6 | import { firebaseAuth, storageKey } from '../config/constants'; 7 | // import context 8 | import { ProfileProvider } from '../context/profileContext' 9 | // import components 10 | import routes from './routes' 11 | import PrivateRoute from './Private' 12 | import PublicRoute from './Public' 13 | 14 | import Layout from '../layout' 15 | 16 | const history = createBrowserHistory() 17 | 18 | class Routes extends Component { 19 | state = { 20 | authed: !!localStorage[storageKey], 21 | user: { 22 | email: null, 23 | uid: null, 24 | } 25 | }; 26 | 27 | componentDidMount() { 28 | this.removeListener = firebaseAuth().onAuthStateChanged(user => { 29 | if (user) { 30 | this.setState({ 31 | authed: true, 32 | user: { 33 | email: user.email, 34 | uid: user.uid, 35 | }, 36 | }); 37 | } else { 38 | this.setState({ 39 | authed: false, 40 | user: { 41 | email: null, 42 | uid: null, 43 | } 44 | }); 45 | } 46 | }); 47 | } 48 | 49 | componentWillUnmount() { 50 | this.removeListener(); 51 | } 52 | 53 | render() { 54 | return 55 | 56 | 57 | 58 | {routes.map((route, i) => { 59 | if (route.auth) { 60 | return 61 | } 62 | return 63 | })} 64 | 65 | 66 | 67 | 68 | 69 | } 70 | } 71 | 72 | export default Routes 73 | -------------------------------------------------------------------------------- /src/routes/routes.js: -------------------------------------------------------------------------------- 1 | // import modular routes 2 | import webRoutes from "../modules/web/routes" 3 | import authRoutes from "../modules/auth/routes" 4 | // import userRoutes from "../modules/user/routes" 5 | 6 | export default [...webRoutes, ...authRoutes] 7 | --------------------------------------------------------------------------------