diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/App.css | 60 | ||||
-rw-r--r-- | src/App.js | 100 | ||||
-rw-r--r-- | src/App.test.js | 8 | ||||
-rw-r--r-- | src/Gantt.js | 102 | ||||
-rw-r--r-- | src/index.css | 5 | ||||
-rw-r--r-- | src/index.js | 9 |
6 files changed, 284 insertions, 0 deletions
diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..d6de87c --- /dev/null +++ b/src/App.css @@ -0,0 +1,60 @@ +.zoom-bar { + background: #fafafa; + border-bottom: 2px solid #828282; + height: 40px; + line-height: 40px; + padding: 5px 10px; +} + +.radio-label { + background: #bdc3c7; + box-shadow: inset -2px -2px #acb2b7; + box-sizing: border-box; + color: #fff; + cursor: pointer; + display: inline-block; + font-family: Tahoma, Geneva, sans-serif; + font-size: 15px; + height: 30px; + line-height: 30px; + margin-left: 6px; + text-align: center; + width: 70px; +} + +.radio-label > input[type=radio] { + height: 0; + margin: 0; + visibility: hidden; + width: 0; +} + +.radio-label-active { + background: #9ea4a8; + box-shadow: none; +} + +.gantt-container { + height: 500px; + width: 100%; +} + +.message-area { + background: #ebebeb; + border: 1px solid #a4a4a4; + font-family: Courier, monospace; + height: 200px; + overflow: auto; + padding: 10px; +} + +.message-area ul{ + margin: 0; + padding: 0; + list-style: none; +} + +.message-area li:before { + content: "\003e"; + padding-right: 10px; +} diff --git a/src/App.js b/src/App.js new file mode 100644 index 0000000..e6ccfb2 --- /dev/null +++ b/src/App.js @@ -0,0 +1,100 @@ +import React, { Component } from 'react'; +import Gantt from './Gantt'; +import './App.css'; + +var data = { + data: [ + {id: 1, text: 'Task #1', start_date: '15-04-2017', duration: 3, progress: 0.6}, + {id: 2, text: 'Task #2', start_date: '18-04-2017', duration: 3, progress: 0.4} + ], + links: [ + {id: 1, source: 1, target: 2, type: '0'} + ] +}; + +class App extends Component { + constructor(props) { + super(props); + this.state = { + currentZoom: 'Days', + messages: [] + }; + + this.handleZoomChange = this.handleZoomChange.bind(this); + this.logTaskUpdate = this.logTaskUpdate.bind(this); + this.logLinkUpdate = this.logLinkUpdate.bind(this); + } + + addMessage(message) { + var messages = this.state.messages.slice(); + var prevKey = messages.length ? messages[0].key: 0; + + messages.unshift({key: prevKey + 1, message}); + if(messages.length > 40){ + messages.pop(); + } + this.setState({messages}); + } + + logTaskUpdate(id, mode, task) { + let text = task && task.text ? ` (${task.text})`: ''; + let message = `Task ${mode}: ${id} ${text}`; + this.addMessage(message); + } + + logLinkUpdate(id, mode, link) { + let message = `Link ${mode}: ${id}`; + if (link) { + message += ` ( source: ${link.source}, target: ${link.target} )`; + } + this.addMessage(message) + } + + handleZoomChange(e) { + this.setState({ + currentZoom: e.target.value + }); + } + + render() { + let zoomRadios = ['Hours', 'Days', 'Months'].map((value) => { + let isActive = this.state.currentZoom === value; + return ( + <label key={value} className={`radio-label ${isActive ? 'radio-label-active': ''}`}> + <input type='radio' + checked={isActive} + onChange={this.handleZoomChange} + value={value}/> + {value} + </label> + ); + }); + + let messages = this.state.messages.map(({key, message}) => { + return <li key={key}>{message}</li> + }); + + return ( + <div> + <div className="zoom-bar"> + <b>Zooming: </b> + {zoomRadios} + </div> + <div className="gantt-container"> + <Gantt + tasks={data} + zoom={this.state.currentZoom} + onTaskUpdated={this.logTaskUpdate} + onLinkUpdated={this.logLinkUpdate} + /> + </div> + <div className="message-area"> + <ul> + {messages} + </ul> + </div> + </div> + ); + } +} +export default App; diff --git a/src/App.test.js b/src/App.test.js new file mode 100644 index 0000000..b84af98 --- /dev/null +++ b/src/App.test.js @@ -0,0 +1,8 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(<App />, div); +}); diff --git a/src/Gantt.js b/src/Gantt.js new file mode 100644 index 0000000..63f672b --- /dev/null +++ b/src/Gantt.js @@ -0,0 +1,102 @@ +/*global gantt*/ +import React, { Component } from 'react'; +import 'dhtmlx-gantt'; +import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'; + +export default class Gantt extends Component { + setZoom(value){ + switch (value){ + case 'Hours': + gantt.config.scale_unit = 'day'; + gantt.config.date_scale = '%d %M'; + + gantt.config.scale_height = 60; + gantt.config.min_column_width = 30; + gantt.config.subscales = [ + {unit:'hour', step:1, date:'%H'} + ]; + break; + case 'Days': + gantt.config.min_column_width = 70; + gantt.config.scale_unit = "week"; + gantt.config.date_scale = "#%W"; + gantt.config.subscales = [ + {unit: "day", step: 1, date: "%d %M"} + ]; + gantt.config.scale_height = 60; + break; + case 'Months': + gantt.config.min_column_width = 70; + gantt.config.scale_unit = "month"; + gantt.config.date_scale = "%F"; + gantt.config.scale_height = 60; + gantt.config.subscales = [ + {unit:"week", step:1, date:"#%W"} + ]; + break; + default: + break; + } + } + + componentWillReceiveProps(nextProps){ + this.needGanttRedraw = this.props.zoom !== nextProps.zoom; + } + + componentDidUpdate() { + if (this.needGanttRedraw) { + gantt.render(); + } + } + + componentDidMount() { + gantt.attachEvent('onAfterTaskAdd', (id, task) => { + if(this.props.onTaskUpdated) { + this.props.onTaskUpdated(id, 'inserted', task); + } + }); + + gantt.attachEvent('onAfterTaskUpdate', (id, task) => { + if(this.props.onTaskUpdated) { + this.props.onTaskUpdated(id, 'updated', task); + } + }); + + gantt.attachEvent('onAfterTaskDelete', (id) => { + if(this.props.onTaskUpdated) { + this.props.onTaskUpdated(id, 'deleted'); + } + }); + + gantt.attachEvent('onAfterLinkAdd', (id, link) => { + if(this.props.onLinkUpdated) { + this.props.onLinkUpdated(id, 'inserted', link); + } + }); + + gantt.attachEvent('onAfterLinkUpdate', (id, link) => { + if(this.props.onLinkUpdated) { + this.props.onLinkUpdated(id, 'updated', link); + } + }); + + gantt.attachEvent('onAfterLinkDelete', (id, link) => { + if(this.props.onLinkUpdated) { + this.props.onLinkUpdated(id, 'deleted'); + } + }); + gantt.init(this.ganttContainer); + gantt.parse(this.props.tasks); + } + + render() { + this.setZoom(this.props.zoom); + + return ( + <div + ref={(input) => { this.ganttContainer = input }} + style={{width: '100%', height: '100%'}} + ></div> + ); + } +}
\ No newline at end of file diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..b4cc725 --- /dev/null +++ b/src/index.css @@ -0,0 +1,5 @@ +body { + margin: 0; + padding: 0; + font-family: sans-serif; +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..54c5ef1 --- /dev/null +++ b/src/index.js @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; +import './index.css'; + +ReactDOM.render( + <App />, + document.getElementById('root') +); |