├── .editorconfig ├── .gitignore ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── README.md ├── package.json ├── public ├── favicon.png ├── index.html ├── oauth-complete.html ├── site.css └── user-menu-arrow.svg └── src ├── components ├── activity │ └── index.js ├── admin │ ├── edit_task.js │ ├── index.js │ └── show_task.js ├── app │ ├── app_header.js │ ├── app_sidebar.js │ └── index.js ├── shared │ ├── create_task_modal.js │ ├── error_modal.js │ ├── login.js │ ├── manage_users_modal.js │ ├── settings_modal.js │ └── success_modal.js ├── stats │ ├── index.js │ ├── stats_graph.js │ ├── stats_header.js │ └── stats_summary.js └── task │ ├── controls │ └── task.js │ ├── edit_bar.js │ └── index.js ├── config ├── config_development.js ├── config_production.js └── index.js ├── constants ├── activity_constants.js ├── admin_constants.js ├── items_constants.js ├── loading_constants.js ├── modals_constants.js ├── settings_constants.js ├── stats_constants.js ├── tasks_constants.js └── user_constants.js ├── index.js ├── routes.js ├── services ├── index.js └── server.js ├── stores ├── activity_action_creators.js ├── activity_reducer.js ├── activity_selectors.js ├── admin_action_creators.js ├── admin_reducer.js ├── admin_selectors.js ├── async_action.js ├── items_action_creators.js ├── items_reducer.js ├── items_selectors.js ├── loading_action_creators.js ├── loading_reducer.js ├── loading_selectors.js ├── modals_action_creators.js ├── modals_reducer.js ├── modals_selectors.js ├── schemas.js ├── settings_action_creators.js ├── settings_reducer.js ├── settings_selectors.js ├── stats_action_creators.js ├── stats_reducer.js ├── stats_selectors.js ├── store.js ├── tasks_action_creators.js ├── tasks_reducer.js ├── tasks_selectors.js ├── user_action_creators.js ├── user_reducer.js └── user_selectors.js └── utils ├── d3_graph.js └── safe_storage.js /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.js] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | .DS_Store 5 | .env 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ## I'm submitting a _(bug report/feature request/ support or question)_ 12 | 13 | ## *Brief Description* 14 | 15 | 16 | ## What is the motivation / use case for this feature? 17 | 18 | 19 | ## What is the current behaviour, (attach relevant screenshots) ? 20 | 21 | 22 | ## What is the expected behaviour ? 23 | 24 | 25 | ## When does this occur ? 26 | 27 | 28 | ## How do we replicate the issue ? 29 | 30 | 1. 31 | 2. 32 | 3. 33 | 4. 34 | 35 | ## Please tell us about your environment: 36 | 37 | 38 | ## Other Information / context: 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Aaron Lidman 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of [project] nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | ____ __ ____ __ _ _ 3 | (_ _)/ \ ___( __)( )( \/ ) 4 | )( ( O )(___)) _) )( ) ( 5 | (__) \__/ (__) (__)(_/\_) 6 | ``` 7 | 8 | A task manager for OpenStreetMap. 9 | 10 | Coordinate with other users and work down a queue of tasks without getting in each other’s way. 11 | 12 | ![to-fix](https://user-images.githubusercontent.com/11095038/28570267-09741cb0-715b-11e7-805c-361a28607e26.png) 13 | 14 | ## Related tools 15 | 16 | - A JOSM plugin is available at [tofix-plugin](https://github.com/JOSM/tofix-plugin). 17 | - A CLI for creating and updating tasks is available at [tofix-cli](https://github.com/Rub21/tofix-cli). 18 | 19 | ## Workflow 20 | 21 | This is the frontend component to `to-fix`. The server 22 | component is at [to-fix-backend](https://github.com/osmlab/to-fix-backend). 23 | 24 | This is a React + Redux application bootstrapped with [create-react-app](https://github.com/facebookincubator/create-react-app). 25 | 26 | To start development, you will require node.js to be installed. 27 | 28 | ```sh 29 | npm install && npm start 30 | ``` 31 | 32 | To build and publish to `gh-pages`, run 33 | 34 | ``` 35 | npm deploy 36 | ``` 37 | 38 | You can configure some details at [src/config](src/config). 39 | 40 | 41 | ## Wiki 42 | 43 | * [How to create a new to-fix task](https://github.com/osmlab/to-fix/wiki/Creating-and-updating-tasks) 44 | * [How to work on an existing to-fix tasks](https://github.com/osmlab/to-fix/wiki/Working-on-a-task) 45 | * [List of open tasks](https://github.com/osmlab/to-fix/wiki/List-of-open-tasks) 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TO-FIX", 3 | "version": "1.0.0", 4 | "description": "A task manager for OpenStreetMap", 5 | "private": true, 6 | "dependencies": { 7 | "@turf/bbox": "^3.7.5", 8 | "@turf/bbox-polygon": "^3.7.5", 9 | "@turf/centroid": "^3.5.3", 10 | "@turf/helpers": "^3.5.3", 11 | "d3": "^3.5.17", 12 | "file-size": "^1.0.0", 13 | "keymirror": "^0.1.1", 14 | "lodash.omit": "^4.5.0", 15 | "lodash.pick": "^4.4.0", 16 | "lodash.union": "^4.6.0", 17 | "mapbox-gl": "^0.32.1", 18 | "normalizr": "^2.2.1", 19 | "react": "^15.3.2", 20 | "react-dom": "^15.3.2", 21 | "react-keybinding": "^3.0.0", 22 | "react-redux": "^4.4.5", 23 | "react-router": "^3.0.0", 24 | "redux": "^3.6.0", 25 | "redux-logger": "^2.7.4", 26 | "redux-thunk": "^2.1.0", 27 | "reselect": "^2.5.4", 28 | "whatwg-fetch": "^1.0.0" 29 | }, 30 | "devDependencies": { 31 | "gh-pages": "^0.11.0", 32 | "react-scripts": "0.8.1" 33 | }, 34 | "scripts": { 35 | "start": "HTTPS=true react-scripts start", 36 | "build": "react-scripts build", 37 | "test": "react-scripts test --env=jsdom", 38 | "deploy": "npm run build && gh-pages -d build" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "https://github.com/osmlab/to-fix.git" 43 | }, 44 | "author": "Aaron Lidman", 45 | "license": "BSD", 46 | "bugs": { 47 | "url": "https://github.com/osmlab/to-fix/issues" 48 | }, 49 | "homepage": "https://osmlab.github.io/to-fix" 50 | } 51 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osmlab/to-fix/0ed4e7c32983eab62456e82493c883cad6be366b/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TO-FIX 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /public/oauth-complete.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/site.css: -------------------------------------------------------------------------------- 1 | /* Base overrides 2 | ------------------------- */ 3 | body { 4 | font:13px/20px 'Helvetica Neue', Helvetica, sans-serif; 5 | color:rgba(0,0,0,0.75); 6 | } 7 | h1, h2, h3, h4, h5, h6, 8 | strong, 9 | svg, 10 | button, 11 | .strong, 12 | .tabs a, 13 | input[type], 14 | label, 15 | form fieldset label, 16 | .button { 17 | font-family:'Helvetica Neue', Helvetica, sans-serif; 18 | } 19 | .strong { font-weight:500; } 20 | strong { font-weight:bold} 21 | .tabs a, 22 | input[type='submit'], 23 | button, 24 | .button { 25 | font-weight:bold; 26 | font-size:13px; 27 | line-height:18px; 28 | } 29 | h1, h2, h3 { font-weight:500; } 30 | h4, h5, h6 { 31 | font-size:15px; 32 | line-height:28px; 33 | } 34 | h2 { padding-top:0; padding-bottom:0; } 35 | .editbar .tabs > * { 36 | border-width:0 0 0 1px; 37 | background-color:rgba(0,0,0,0.75); 38 | color:#fff; 39 | border-radius:0; 40 | } 41 | .editbar .tabs > *:first-child { 42 | border-width:0; 43 | border-radius:3px 0 0 0; 44 | } 45 | .editbar .tabs > *:last-child { 46 | border-radius:0 3px 0 0; 47 | } 48 | .editbar .tabs > *:only-child { 49 | border-radius:3px 3px 0 0; 50 | } 51 | .editbar .tabs > a:hover, 52 | .editbar .tabs > button:hover { 53 | background-color:rgba(0,0,0,0.9); 54 | color:#fff; 55 | } 56 | .editbar .tabs > .settings { 57 | border-width:0px; 58 | height:38px; 59 | width:08.3333% !important; 60 | padding:7px !important; 61 | } 62 | .button.small { 63 | font-size:11px; 64 | line-height:20px; 65 | padding:5px 10px; 66 | font-weight:bold; 67 | height:34px; 68 | } 69 | 70 | .loading:before { 71 | background-color:rgba(0,0,0,0.25); 72 | z-index:100; 73 | } 74 | .loading:after { position:fixed; } 75 | /* Base candidates 76 | ------------------------- */ 77 | .row-60 { height:60px; } 78 | .space-top6 { top:60px; } 79 | .scroll-styled { height:100%; } 80 | .z1000 { z-index:1000; } 81 | .z10000 { z-index:10000; } 82 | 83 | .scroll-styled::-webkit-scrollbar { 84 | width:5px; 85 | height:5px; 86 | border-left:0; 87 | background:rgba(0,0,0,0.1); 88 | } 89 | .scroll-styled::-webkit-scrollbar-track { 90 | background:none; 91 | } 92 | .scroll-styled::-webkit-scrollbar-thumb { 93 | background:rgba(0,0,0,0.4); 94 | border-radius:0; 95 | } 96 | .scroll-styled::-webkit-scrollbar:hover { 97 | background:rgba(0,0,0,0.4); 98 | } 99 | 100 | .uppercase { text-transform:uppercase; } 101 | .underline { text-decoration:underline; } 102 | 103 | .no-select { user-select: none; } 104 | 105 | /* Layout 106 | ------------------------- */ 107 | .sidebar.active ~ .main { width:83.3334%; left:16.6666%; } 108 | .map { 109 | width:100%; 110 | height:100%; 111 | bottom:0; 112 | } 113 | 114 | /* Colors 115 | ------------------------- */ 116 | button, 117 | .button, 118 | [type=button], 119 | [type=submit] { 120 | background-color:#6E65B3; 121 | } 122 | button:hover, 123 | .button:hover, 124 | [type=button]:hover, 125 | [type=submit]:hover { 126 | background-color:#534c87; 127 | } 128 | 129 | .progress, 130 | header .button.active, 131 | header .button.active:hover, 132 | .fill-purple { background-color:#6E65B3; } 133 | .area { fill:rgb(110, 101, 179); } 134 | 135 | /* Components 136 | ------------------------- */ 137 | .fancy.title { 138 | font-size:22px; 139 | line-height:24px; 140 | margin:0; 141 | } 142 | header .fancy.title { 143 | line-height:60px; 144 | color:rgba(0,0,0,0.75); 145 | } 146 | 147 | header nav .button { 148 | background-color:transparent; 149 | color:rgba(0,0,0,0.5); 150 | } 151 | header .button:hover { 152 | background-color:rgba(0,0,0,0.05); 153 | color:rgba(0,0,0,0.75); 154 | } 155 | header .button.active { 156 | color:#fff; 157 | } 158 | header a { 159 | color:#6E65B3; 160 | } 161 | header a:hover { 162 | color:#26198A; 163 | } 164 | 165 | .sidebar-toggle:hover, 166 | .sidebar-toggle.active { 167 | background-color:rgba(0,0,0,0.05); 168 | } 169 | .sidebar-toggle.active { 170 | border-color:rgba(0,0,0,0.25); 171 | } 172 | 173 | .sidebar nav a:hover { 174 | background-color:rgba(0,0,0,0.20); 175 | } 176 | .sidebar nav a.active { 177 | background-color:rgba(0,0,0,0.5); 178 | color:#fff; 179 | } 180 | 181 | .dialog .close { 182 | background-color:transparent; 183 | color:rgba(0,0,0,0.5); 184 | } 185 | .dialog .close:hover { 186 | background-color:transparent; 187 | color:rgba(0,0,0,0.75); 188 | } 189 | 190 | .avatar-wrap { 191 | border: 2px solid rgba(255, 255, 255, 0.25); 192 | border-radius: 20px; 193 | margin-top: -2px; 194 | } 195 | .avatar { 196 | width: 20px; 197 | margin-right:5px !important; 198 | } 199 | 200 | .progress-bar { 201 | border-radius:50px; 202 | height:10px; 203 | } 204 | .progress { 205 | border-radius:50px; 206 | width:0; 207 | height:10px; 208 | top:0; 209 | left:0; 210 | right:0; 211 | min-width:1%; 212 | max-width:100%; 213 | } 214 | 215 | .rows > * { margin-bottom:1px; } 216 | .rows > *:first-child { border-radius:3px 3px 0 0; } 217 | 218 | .editor-key { width:80px; text-align:center; } 219 | 220 | .contributions > div { 221 | border-bottom:1px solid rgba(0,0,0,0.25); 222 | } 223 | .contributions > div:first-child { border-width:2px; } 224 | .contributions > div:last-child { border-bottom:none; } 225 | 226 | .user-menu { 227 | position:relative; 228 | top:0px; 229 | right:0px; 230 | padding:10px 0px 10px 0px; 231 | border:1px solid rgba(26,53,71,.12); 232 | border-radius:4px; 233 | box-shadow:0 1px 2px rgba(26,53,71,.1); 234 | opacity:0; 235 | pointer-events:none; 236 | -webkit-transform:scale(.8) translateY(-30%); 237 | transform:scale(.8) translateY(-30%); 238 | transition:.4s cubic-bezier(.3, 0, 0, 1.3); 239 | background:white; 240 | z-index:1000; 241 | width:130px; 242 | } 243 | 244 | .user-menu.visible { 245 | opacity:1; 246 | pointer-events:auto; 247 | -webkit-transform:none; 248 | transform:none; 249 | } 250 | 251 | .user-menu::before { 252 | content:""; 253 | position:absolute; 254 | top:-7px; 255 | left:calc(50% - 7px); 256 | width:13px; 257 | height:7px; 258 | background:url(user-menu-arrow.svg); 259 | } 260 | 261 | .user-menu li { 262 | list-style:none; 263 | text-align:left; 264 | } 265 | 266 | .select-container { 267 | display: inline-flex; 268 | position: relative; 269 | color: #fff; 270 | align-items: center; 271 | } 272 | 273 | .select { 274 | appearance: none; 275 | line-height: inherit; 276 | font-size: inherit; 277 | font-weight: bold; 278 | color: white; 279 | padding: 6px 30px 6px 12px; 280 | cursor: pointer; 281 | display: inline-block; 282 | border-radius: 4px; 283 | background-color: rgba(0, 0, 0, 0.25); 284 | } 285 | 286 | .select-arrow { 287 | position: absolute; 288 | right: 12px; 289 | top: 50%; 290 | pointer-events: none; 291 | border-left: 4px solid transparent; 292 | border-right: 4px solid transparent; 293 | border-top: 5px solid white; 294 | width: 8px; 295 | height: 8px; 296 | margin-top: -1px; 297 | } 298 | 299 | /* iD container 300 | * elements while editing 301 | ------------------------- */ 302 | .ideditor { 303 | position:absolute; 304 | top:0; 305 | left:0; 306 | height:100%; 307 | width:100%; 308 | z-index:2000; 309 | } 310 | .ideditor-done { 311 | position:absolute; 312 | right:10px; 313 | top:10px; 314 | color:#fff; 315 | font-weight:bold; 316 | } 317 | 318 | /* D3 Dashboards 319 | ------------------------- */ 320 | svg { 321 | font-size:10px; 322 | font-weight:bold; 323 | } 324 | text { 325 | font-family:Consolas, monospace; 326 | fill:rgba(255,255,255,0.5); 327 | } 328 | .axis path, 329 | .axis line { 330 | fill:none; 331 | stroke:transparent; 332 | shape-rendering:crispEdges; 333 | } 334 | .brush .extent { 335 | stroke:#fff; 336 | stroke-dasharray:4px 2px; 337 | fill-opacity:0.10; 338 | shape-rendering:crispEdges; 339 | } 340 | 341 | /* Small Screen Layout 342 | ------------------------- */ 343 | @media only screen and (max-width:900px) { 344 | .sidebar { width:33.3333%; } 345 | .sidebar.active ~ .main { width:66.6666%; left:33.3333%; } 346 | } 347 | 348 | /* Tablet Layout 349 | ------------------------- */ 350 | @media only screen and (max-width:770px) {} 351 | 352 | /* Mobile Layout 353 | ------------------------- */ 354 | @media only screen and (max-width:640px) { 355 | .sidebar { width:100%; } 356 | .sidebar.active ~ .main { width:0; left:100%; } 357 | } 358 | -------------------------------------------------------------------------------- /public/user-menu-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | user-menu-arrow 4 | Created with Sketch. 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/activity/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import d3 from 'd3'; 4 | 5 | import { USER_PROFILE_URL } from '../../config'; 6 | import TasksSelectors from '../../stores/tasks_selectors'; 7 | import ActivitySelectors from '../../stores/activity_selectors'; 8 | import ActivityActionCreators from '../../stores/activity_action_creators'; 9 | 10 | const mapStateToProps = (state) => ({ 11 | currentTaskId: TasksSelectors.getCurrentTaskId(state), 12 | currentTask: TasksSelectors.getCurrentTask(state), 13 | currentTaskExtent: TasksSelectors.getCurrentTaskExtent(state), 14 | activity: ActivitySelectors.getData(state), 15 | }); 16 | 17 | const mapDispatchToProps = { 18 | fetchRecentActivity: ActivityActionCreators.fetchRecentActivity, 19 | }; 20 | 21 | class Activity extends Component { 22 | state = { 23 | loadCount: 10, 24 | } 25 | 26 | resetLoadCount() { 27 | this.setState({ 28 | loadCount: 10, 29 | }); 30 | } 31 | 32 | loadMore() { 33 | this.setState({ 34 | loadCount: this.state.loadCount + 10, 35 | }); 36 | } 37 | 38 | fetchData() { 39 | const { currentTaskId, fetchRecentActivity } = this.props; 40 | 41 | fetchRecentActivity({ taskId: currentTaskId }) 42 | .then(() => this.resetLoadCount()); 43 | } 44 | 45 | componentDidMount() { 46 | this.fetchData(); 47 | } 48 | 49 | componentDidUpdate(prevProps) { 50 | // Refetch activity when a new task is selected 51 | if (prevProps.currentTaskId !== this.props.currentTaskId) { 52 | this.fetchData(); 53 | } 54 | } 55 | 56 | renderActivityList() { 57 | const { activity } = this.props; 58 | const { loadCount } = this.state; 59 | 60 | if (activity.length === 0) { 61 | return No recent activity found.; 62 | } 63 | 64 | return activity.slice(0, loadCount).map((data, i) => { 65 | const { time, key, action, editor, user } = data; 66 | 67 | const permalink = `key-${key}`; 68 | const profile = `${USER_PROFILE_URL}/${user}`; 69 | 70 | const dateDisplay = d3.time.format('%B %-d'); 71 | const timeDisplay = d3.time.format('%-I:%-M%p'); 72 | 73 | const actionDate = dateDisplay(new Date(time * 1000)); 74 | const actionTime = timeDisplay(new Date(time * 1000)); 75 | 76 | const mapAction = { 77 | 'edit': 'Edited', 78 | 'fixed': 'Fixed', 79 | 'skip': 'Skipped', 80 | 'noterror': 'Not an error', 81 | }; 82 | 83 | return ( 84 |
85 |
86 | {mapAction[action]} 87 |
88 |
89 | 90 | {user} 91 | 92 | {editor} 93 |
94 |
95 | {actionDate} 96 | {actionTime} 97 |
98 |
99 | ); 100 | }); 101 | } 102 | 103 | renderLoadMoreButton() { 104 | const { activity } = this.props; 105 | const { loadCount } = this.state; 106 | 107 | if (activity.length === 0) return null; 108 | 109 | if (loadCount >= activity.length) { 110 | return ; 111 | } else { 112 | return ; 113 | } 114 | } 115 | 116 | render() { 117 | const { currentTask, currentTaskExtent } = this.props; 118 | const { fromDate, toDate } = currentTaskExtent; 119 | 120 | const taskName = currentTask.value.name; 121 | const activityList = this.renderActivityList(); 122 | const loadMoreButton = this.renderLoadMoreButton(); 123 | 124 | return ( 125 |
126 |
127 |
128 |

{taskName}

129 |

130 | {`Task last updated on ${fromDate}.`} 131 |

132 |
133 |
134 | {activityList} 135 | {loadMoreButton} 136 |
137 |
138 |
139 | ); 140 | } 141 | } 142 | 143 | Activity.propTypes = { 144 | currentTaskId: PropTypes.string.isRequired, 145 | currentTask: PropTypes.object.isRequired, 146 | currentTaskExtent: PropTypes.object.isRequired, 147 | activity: PropTypes.array.isRequired, 148 | fetchRecentActivity: PropTypes.func.isRequired, 149 | }; 150 | 151 | Activity = connect( 152 | mapStateToProps, 153 | mapDispatchToProps 154 | )(Activity); 155 | 156 | export default Activity; 157 | -------------------------------------------------------------------------------- /src/components/admin/edit_task.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import filesize from 'file-size'; 4 | 5 | import { AsyncStatus } from '../../stores/async_action'; 6 | import UserSelectors from '../../stores/user_selectors'; 7 | import ModalsActionCreators from '../../stores/modals_action_creators'; 8 | 9 | const mapStateToProps = (state) => ({ 10 | token: UserSelectors.getToken(state), 11 | }); 12 | 13 | const mapDispatchToProps = { 14 | openSuccessModal: ModalsActionCreators.openSuccessModal, 15 | }; 16 | 17 | class EditTask extends Component { 18 | state = { 19 | name: '', 20 | description: '', 21 | changesetComment: '', 22 | file: null, 23 | } 24 | 25 | handleNameChange = (e) => { 26 | const name = e.target.value; 27 | this.setState({ name }); 28 | } 29 | 30 | handleDescriptionChange = (e) => { 31 | const description = e.target.value; 32 | this.setState({ description }); 33 | } 34 | 35 | handleChangesetCommentChange = (e) => { 36 | const changesetComment = e.target.value; 37 | this.setState({ changesetComment }); 38 | } 39 | 40 | handleFileInputChange = (e) => { 41 | const file = e.target.files[0]; 42 | this.setState({ file }); 43 | } 44 | 45 | handleSubmit = (e) => { 46 | e.preventDefault(); 47 | 48 | const { onSubmit, token } = this.props; 49 | const formData = this.getFormData(); 50 | 51 | onSubmit({ token, payload: formData }) 52 | .then(response => { 53 | if (response.status === AsyncStatus.SUCCESS) { 54 | this.props.openSuccessModal('Task updated succesfully'); 55 | } 56 | }); 57 | } 58 | 59 | getFormData = () => { 60 | const { task } = this.props; 61 | const { name, description, changesetComment, file } = this.state; 62 | 63 | const formData = new window.FormData(); 64 | 65 | formData.append('idtask', task.idtask); 66 | formData.append('name', name); 67 | formData.append('description', description); 68 | formData.append('changesetComment', changesetComment); 69 | formData.append('isCompleted', false); 70 | if (file) { 71 | formData.append('file', file); 72 | } 73 | 74 | return formData; 75 | } 76 | 77 | setInitialState = () => { 78 | const { task } = this.props; 79 | 80 | this.setState({ 81 | name: task.value.name, 82 | description: task.value.description, 83 | changesetComment: task.value.changesetComment, 84 | }); 85 | } 86 | 87 | componentDidMount() { 88 | this.setInitialState(); 89 | } 90 | 91 | shouldComponentUpdate(nextProps) { 92 | return ( 93 | nextProps.task.idtask !== this.props.idtask 94 | ); 95 | } 96 | 97 | componentDidUpdate(prevProps) { 98 | if (prevProps.task.idtask !== this.props.task.idtask) { 99 | this.setInitialState(); 100 | } 101 | } 102 | 103 | render() { 104 | const { name, description, changesetComment, file } = this.state; 105 | const { onCancel } = this.props; 106 | 107 | return ( 108 |
109 |
110 | 111 | 116 |
117 |
118 | 119 |