summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.css60
-rw-r--r--src/App.js100
-rw-r--r--src/App.test.js8
-rw-r--r--src/Gantt.js102
-rw-r--r--src/index.css5
-rw-r--r--src/index.js9
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')
+);