Skip to content

Commit 56ffb8b

Browse files
committed
added overview for availabilities #33
1 parent 4fd0831 commit 56ffb8b

File tree

6 files changed

+135
-1
lines changed

6 files changed

+135
-1
lines changed

client/components/Availabilities.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React from 'react';
2+
import _ from 'lodash';
3+
import Timeline from 'react-calendar-timeline';
4+
import moment from 'moment';
5+
import { Spinner } from 'elemental';
6+
import * as http from '../lib/http';
7+
import groupsJSON from '../../shared/groups.json';
8+
9+
export default React.createClass({
10+
11+
propTypes: {
12+
start: React.PropTypes.number,
13+
end: React.PropTypes.number,
14+
},
15+
16+
getDefaultProps() {
17+
return {
18+
start: Date.now(),
19+
end: Date.now(),
20+
};
21+
},
22+
23+
getInitialState() {
24+
return {
25+
items: [],
26+
groups: [],
27+
volunteers: null,
28+
};
29+
},
30+
31+
componentDidMount() {
32+
http.get('/api/volunteers')
33+
.then(({ volunteers }) => this.setState({ volunteers }, this.generateItems));
34+
},
35+
36+
generateItems() {
37+
const groups = groupsJSON.map(name => ({ id: name, title: _.startCase(name) }));
38+
const items = [];
39+
const now = new Date();
40+
const start = new Date(this.props.start);
41+
const end = new Date(this.props.end);
42+
43+
_.each(this.state.volunteers, volunteer => _.each(volunteer.availabilities, (av) => {
44+
const className = new Date(av.from) <= start && new Date(av.till) >= end
45+
? new Date(av.confirmationTill) >= now ? 'available' : 'expired'
46+
: 'unavailable';
47+
48+
items.push({
49+
start_time: moment(av.from),
50+
end_time: moment(av.till),
51+
id: av._id,
52+
group: volunteer.group,
53+
title: 'Name' || `${volunteer.name.first || ''} ${volunteer.name.last || ''}`.trim(),
54+
className,
55+
});
56+
}));
57+
58+
this.setState({ items, groups });
59+
},
60+
61+
render() {
62+
if (this.state.volunteers === null) {
63+
return <div style={{ marginTop: 100, textAlign: 'center' }}><Spinner size="lg" /></div>;
64+
}
65+
66+
return (
67+
<Timeline
68+
groups={this.state.groups}
69+
items={this.state.items}
70+
defaultTimeStart={moment(this.props.start).add(-1, 'day')}
71+
defaultTimeEnd={moment(this.props.end).add(1, 'day')}
72+
canMove={false}
73+
canChangeGroup={false}
74+
canResize={false}
75+
stackItems
76+
/>
77+
);
78+
},
79+
80+
});

client/components/MissionForm.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ export default React.createClass({
139139
<Alert type={this.state.message.type}>{this.state.message.text}</Alert>
140140
}
141141

142+
<div style={{ textAlign: 'right' }}>
143+
<a href={`/availabilities?start=${+new Date(mission.start)}&end=${+new Date(mission.end)}`} target="_blank">Availabilities</a>
144+
</div>
145+
142146
<Form onChange={this.onChange} onSubmit={this.onSubmit}>
143147

144148
<FormField label="Name">

client/pages/availabilities.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom';
3+
import queryString from 'query-string';
4+
import Availabilities from '../components/Availabilities';
5+
6+
const parsed = queryString.parse(window.location.search);
7+
const start = +parsed.start || Date.now();
8+
const end = +parsed.end || Date.now();
9+
10+
ReactDOM.render(<Availabilities start={start} end={end} />, document.getElementById('app'));

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"babel-preset-es2015": "^6.24.0",
2323
"babel-preset-react": "^6.23.0",
2424
"babelify": "^7.3.0",
25+
"browserify-css": "^0.10.1",
2526
"browserify-middleware": "^7.0.0",
2627
"cookie-parser": "^1.4.3",
2728
"dotenv": "^4.0.0",
@@ -36,7 +37,9 @@
3637
"model-transform": "^2.0.0",
3738
"moment": "^2.18.1",
3839
"nodemailer": "^3.1.8",
40+
"query-string": "^4.3.4",
3941
"react": "^15.4.2",
42+
"react-calendar-timeline": "^0.11.1",
4043
"react-datepicker": "^0.43.0",
4144
"react-dom": "^15.4.2",
4245
"react-leaflet": "^1.1.4",

public/styles/styles.less

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,34 @@ footer {
4848
height: 400px;
4949
width: 100%;
5050
}
51+
52+
53+
// Timeline
54+
55+
body .react-calendar-timeline {
56+
.rct-sidebar .rct-sidebar-header {
57+
background: transparent url(/images/logo.svg) center no-repeat;
58+
background-size: 80%;
59+
}
60+
61+
.rct-header .rct-label-group,
62+
.rct-header .rct-label.rct-label-only { // gray
63+
background-color: #e0e0e0;
64+
color: #333;
65+
}
66+
67+
.rct-items .rct-item.available { // green
68+
background-color: #388e3c;
69+
}
70+
71+
.rct-items .rct-item.expired { // orange
72+
background-color: #fb8c00;
73+
}
74+
75+
.rct-items .rct-item.unavailable { // gray
76+
background-color: #eee;
77+
border-color: #757575;
78+
color: #757575;
79+
}
80+
}
81+

routes/index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const babelify = require('babelify');
2+
const browserifycss = require('browserify-css');
23
const browserify = require('browserify-middleware');
34
const express = require('express');
45
const path = require('path');
@@ -19,6 +20,7 @@ exports = module.exports = (app) => {
1920
app.get('/', (req, res) => res.render('react', { page: 'signup' }));
2021
app.get('/missions', (req, res) => res.render('react', { page: 'missions' }));
2122
app.get('/volunteer', (req, res) => res.render('react', { page: 'volunteer' }));
23+
app.get('/availabilities', (req, res) => res.render('react', { page: 'availabilities' }));
2224

2325
app.get('/volunteer/:token', api.volunteers.setToken);
2426

@@ -42,8 +44,12 @@ exports = module.exports = (app) => {
4244

4345
// Serve script bundles
4446
app.use('/js', browserify('./client/pages', {
47+
debug: true,
4548
external: commonPackages,
46-
transform: [babelify.configure({ presets: ['es2015', 'react', 'stage-3'] })],
49+
transform: [
50+
[browserifycss, { global: true }],
51+
[babelify.configure({ presets: ['es2015', 'react', 'stage-3'] })],
52+
],
4753
}));
4854

4955
app.disable('x-powered-by');

0 commit comments

Comments
 (0)