├── .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 | 
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 |
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 |
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 |
320 |
333 |
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 |
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 |
--------------------------------------------------------------------------------