diff --git a/package-lock.json b/package-lock.json index e30b626..d7a35a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1710,6 +1710,11 @@ "lazy-cache": "^1.0.3" } }, + "chain-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.1.tgz", + "integrity": "sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg==" + }, "chalk": { "version": "1.1.3", "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", @@ -8755,6 +8760,38 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-notifications": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/react-notifications/-/react-notifications-1.4.3.tgz", + "integrity": "sha1-cGDTOYlvElpbGD680EUmmApDMiI=", + "requires": { + "classnames": "^2.1.1", + "prop-types": "^15.5.10", + "react-transition-group": "^1.2.0" + }, + "dependencies": { + "react-transition-group": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz", + "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==", + "requires": { + "chain-function": "^1.0.0", + "dom-helpers": "^3.2.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.5.6", + "warning": "^3.0.0" + } + }, + "warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, "react-popper": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-0.10.4.tgz", diff --git a/package.json b/package.json index 2125272..862618f 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "react-cookie": "^3.0.4", "react-dom": "^16.5.0", "react-icons": "^3.0.5", + "react-notifications": "^1.4.3", "react-router-dom": "^4.3.1", "react-scripts": "1.1.5", "reactstrap": "^6.4.0" diff --git a/src/Categories.js b/src/Categories.js index fedee20..5fe75db 100644 --- a/src/Categories.js +++ b/src/Categories.js @@ -1,4 +1,6 @@ -import React, { Component } from 'react'; +import React, { + Component +} from 'react'; import { Container, Table, @@ -7,12 +9,23 @@ import { Button, Input, } from 'reactstrap'; -import { FaTrashAlt, FaPlus } from 'react-icons/fa'; -import { Link } from 'react-router-dom'; +import { + FaTrashAlt, + FaPlus +} from 'react-icons/fa'; +import { + Link +} from 'react-router-dom'; +import { + NotificationContainer, + NotificationManager +} from 'react-notifications'; import Navigation from './Navigation'; import Header from './Header'; import API from './Api'; +import 'react-notifications/lib/notifications.css'; + class Categories extends Component { constructor(props) { super(props); @@ -44,13 +57,17 @@ class Categories extends Component { API.get('types') .then((res) => { if (res.status === 200) { - this.setState({ Categories: res.data.rows }); + this.setState({ + Categories: res.data.rows + }); } else { - this.setState({ Categories: [] }); + this.setState({ + Categories: [] + }); } }) .catch((e) => { - alert('Erreur lors de la récupération des catégories'); + NotificationManager.error('Erreur lors de la récupération des catégories'); }); } @@ -58,13 +75,16 @@ class Categories extends Component { event.preventDefault(); // Let's stop this event. event.stopPropagation(); // Really this time. API.post('types', { - name: this.state.name, - }) + name: this.state.name, + }) .then((res) => { - this.setState(prevState => ({ Categories: [...prevState.Categories, res.data], name: '' })); + this.setState(prevState => ({ + Categories: [...prevState.Categories, res.data], + name: '' + })); }) .catch(() => { - alert('Impossile de créer cette catégorie'); + NotificationManager.error('Impossile de créer cette catégorie'); }); } @@ -76,13 +96,14 @@ class Categories extends Component { }); }) .catch((e) => { - alert('Erreur lors de la suppression de cette catégorie'); + NotificationManager.error('Erreur lors de la suppression de cette catégorie'); }); } render() { return (
+
diff --git a/src/Category.js b/src/Category.js index 9a8c650..f7ee926 100644 --- a/src/Category.js +++ b/src/Category.js @@ -1,4 +1,6 @@ -import React, { Component } from 'react'; +import React, { + Component +} from 'react'; import { Container, Table, @@ -7,12 +9,24 @@ import { Button, Input, } from 'reactstrap'; -import { FaTrashAlt, FaPlus, FaEdit } from 'react-icons/fa'; -import { Link } from 'react-router-dom'; +import { + FaTrashAlt, + FaPlus, + FaEdit +} from 'react-icons/fa'; +import { + Link +} from 'react-router-dom'; +import { + NotificationContainer, + NotificationManager +} from 'react-notifications'; import Navigation from './Navigation'; import Header from './Header'; import API from './Api'; +import 'react-notifications/lib/notifications.css'; + class Category extends Component { constructor(props) { super(props); @@ -48,13 +62,15 @@ class Category extends Component { event.preventDefault(); // Let's stop this event. event.stopPropagation(); // Really this time. API.patch(`types/${this.state.Category.id}`, { - name: this.state.categoryName, - }) + name: this.state.categoryName, + }) .then((res) => { - this.setState({ Category: res.data }); + this.setState({ + Category: res.data + }); }) .catch(() => { - alert('Impossile de mettre à jour cette catégorie'); + NotificationManager.error('Impossile de mettre à jour cette catégorie'); }); } @@ -66,13 +82,18 @@ class Category extends Component { res.data.Vegetables = []; } - this.setState({ Category: res.data, categoryName: res.data.name }); + this.setState({ + Category: res.data, + categoryName: res.data.name + }); } else { - this.setState({ Category: {} }); + this.setState({ + Category: {} + }); } }) .catch((e) => { - alert('Erreur lors de la récupération des catégories'); + NotificationManager.error('Erreur lors de la récupération des catégories'); }); } @@ -80,19 +101,21 @@ class Category extends Component { event.preventDefault(); // Let's stop this event. event.stopPropagation(); // Really this time. API.post(`types/${this.state.Category.id}/vegetables/`, { - name: this.state.name, - lat: 0, - lng: 0, - description: '', - }) + name: this.state.name, + lat: 0, + lng: 0, + description: '', + }) .then((res) => { const Category = this.state.Category; Category.Vegetables.push(res.data); - this.setState({ Category }); + this.setState({ + Category + }); }) .catch(() => { - alert('Impossile de créer cette catégorie'); + NotificationManager.error('Impossile de créer cette catégorie'); }); } @@ -103,16 +126,19 @@ class Category extends Component { Category.Vegetables.splice(key, 1); - this.setState({ Category }); + this.setState({ + Category + }); }) .catch((e) => { - alert('Erreur lors de la suppression de ce végétal'); + NotificationManager.error('Erreur lors de la suppression de ce végétal'); }); } render() { return (
+
diff --git a/src/Properties.js b/src/Properties.js index 5f48552..16d828d 100644 --- a/src/Properties.js +++ b/src/Properties.js @@ -13,10 +13,16 @@ import { FaTrashAlt, FaPlus } from 'react-icons/fa'; +import { + NotificationContainer, + NotificationManager +} from 'react-notifications'; import Navigation from './Navigation'; import Header from './Header'; import API from './Api'; +import 'react-notifications/lib/notifications.css'; + class Properties extends Component { constructor(props) { super(props); @@ -58,7 +64,7 @@ class Properties extends Component { } }) .catch((e) => { - alert('Erreur lors de la récupération des types de propriétés'); + NotificationManager.error('Erreur lors de la récupération des types de propriétés'); }); } @@ -75,7 +81,7 @@ class Properties extends Component { })); }) .catch(() => { - alert('Impossile de créer ce type de propriété'); + NotificationManager.error('Impossile de créer ce type de propriété'); }); } @@ -87,13 +93,14 @@ class Properties extends Component { }); }) .catch((e) => { - alert('Erreur lors de la suppression de ce type de propriété'); + NotificationManager.error('Erreur lors de la suppression de ce type de propriété'); }); } render() { return (
+
diff --git a/src/Vegetable.js b/src/Vegetable.js index d216ebc..b75f9d3 100644 --- a/src/Vegetable.js +++ b/src/Vegetable.js @@ -15,6 +15,10 @@ import classnames from 'classnames'; import { FaEdit, } from 'react-icons/fa'; +import { + NotificationContainer, + NotificationManager +} from 'react-notifications'; import Navigation from './Navigation'; import Header from './Header'; import API from './Api'; @@ -23,6 +27,7 @@ import VegetableCarousel from './Vegetable/Carousel' import VegetableMap from './Vegetable/Map' import './Vegetable.css'; +import 'react-notifications/lib/notifications.css'; class Vegetable extends Component { constructor(props) { @@ -100,27 +105,66 @@ class Vegetable extends Component { } updateVegetable(event) { + NotificationManager.info('Sauvegarde en cours'); event.preventDefault(); // Let's stop this event. event.stopPropagation(); // Really this time. + const requests = 1 + this.state.Vegetable.Properties.length; + let requestsDone = 0; + let isDone = () => { + if (requests === requestsDone) { + NotificationManager.success('Sauvegarde terminée'); + this.getVegetable(this.props.match.params.categoryId, this.props.match.params.vegetableId); + } + } + + // Patch Vegetable const fd = new FormData(); if (this.state.imagePreviewUrl) { fd.append('mainPicture', this.state.Vegetable.mainPictureImported); } Object.keys(this.state.Vegetable).map((objectKey) => { - if (objectKey !== 'mainPicture' && objectKey !== 'mainPictureImported') { + // if (objectKey === 'Properties') { + // fd.append(objectKey, JSON.stringify(this.state.Vegetable[objectKey])); + // } else + if (objectKey !== 'mainPicture' && objectKey !== 'mainPictureImported' && objectKey !== 'Properties') { fd.append(objectKey, this.state.Vegetable[objectKey]); } return true; }); API.patch(`types/${this.state.Vegetable.Type.id}/vegetables/${this.state.Vegetable.id}`, fd) .then((res) => { - // this.setState({ Category: res.data }); + requestsDone++; + isDone(); }) .catch(() => { - alert('Impossile de mettre à jour ce végétal'); + NotificationManager.error('Impossile de mettre à jour ce végétal'); }); + + // Patch or create Properties + this.state.Vegetable.Properties.forEach(propertyItem => { + if (propertyItem.id) { + API.patch(`types/${this.state.Vegetable.Type.id}/vegetables/${this.state.Vegetable.id}/properties/${propertyItem.id}`, propertyItem) + .then((res) => { + requestsDone++; + isDone(); + }) + .catch(() => { + NotificationManager.error(`Impossile de mettre à jour la propriété ${propertyItem.Property.name}`); + }); + } else { + API.post(`types/${this.state.Vegetable.Type.id}/vegetables/${this.state.Vegetable.id}/properties`, propertyItem) + .then((res) => { + requestsDone++; + isDone(); + }) + .catch(() => { + NotificationManager.error(`Impossile de sauvegarder la propriété ayant pour valeur ${propertyItem.value}`); + }); + } + }) + } postPicture(picture) { @@ -133,7 +177,7 @@ class Vegetable extends Component { // this.setState({ Category: res.data }); }) .catch(() => { - alert('Impossile d\'ajouter cette image'); + NotificationManager.error(`Impossible d'ajouter cette image`); }); } @@ -148,7 +192,7 @@ class Vegetable extends Component { } }) .catch(e => { - alert('Erreur lors de la récupération des types de propriétés'); + NotificationManager.error('Erreur lors de la récupération des types de propriétés'); }); API.get(`types/${categoryId}/vegetables/${vegetableId}`) @@ -175,13 +219,14 @@ class Vegetable extends Component { } }) .catch((e) => { - alert('Erreur lors de la récupération de ce végétal'); + NotificationManager.error('Erreur lors de la récupération de ce végétal'); }); } render() { return (
+
@@ -219,6 +264,7 @@ class Vegetable extends Component { imagePreviewUrl={this.state.imagePreviewUrl} updateValue={this.updateValueFromChild} changeMainPicture={this.changeMainPicture} + availableProperties={this.state.availableProperties} /> diff --git a/src/Vegetable/Main.js b/src/Vegetable/Main.js index 5e3326e..dec8021 100644 --- a/src/Vegetable/Main.js +++ b/src/Vegetable/Main.js @@ -11,6 +11,7 @@ import { import { FaFileImage, } from 'react-icons/fa'; +import VegetablesProperties from './Properties' class VegetableMain extends Component { constructor(props) { @@ -25,13 +26,13 @@ class VegetableMain extends Component { Pictures: [], }, imagePreviewUrl: '', + availableProperties: [] }; this.handleChange = this.handleChange.bind(this); } componentWillReceiveProps(nextProps) { - console.log(nextProps) if (nextProps.Vegetable) { this.setState({ Vegetable: nextProps.Vegetable @@ -42,6 +43,11 @@ class VegetableMain extends Component { imagePreviewUrl: nextProps.imagePreviewUrl }); } + if (nextProps.availableProperties) { + this.setState({ + availableProperties: nextProps.availableProperties + }); + } } handleChange(event) { @@ -128,6 +134,12 @@ class VegetableMain extends Component { /> + +
diff --git a/src/Vegetable/Properties.js b/src/Vegetable/Properties.js new file mode 100644 index 0000000..5b63b7d --- /dev/null +++ b/src/Vegetable/Properties.js @@ -0,0 +1,167 @@ +import React, { + Component, +} from 'react'; +import { + Button, + FormGroup, + Label, + Input, + InputGroup, + InputGroupAddon, + Col +} from 'reactstrap'; +import { + FaPlus, +} from 'react-icons/fa'; + +class VegetablesProperties extends Component { + constructor(props) { + super(props); + + this.state = { + selectedProperties: [], + availableProperties: [], + selectedPropety: null + }; + + this.handleChange = this.handleChange.bind(this); + this.handleArrayChange = this.handleArrayChange.bind(this); + this.updateParent = this.updateParent.bind(this); + this.addProperty = this.addProperty.bind(this); + this.notSet = this.notSet.bind(this); + } + + componentWillReceiveProps(nextProps) { + if (nextProps.selectedProperties) { + this.setState({ + selectedProperties: nextProps.selectedProperties + }); + } + if (nextProps.availableProperties) { + this.setState({ + availableProperties: nextProps.availableProperties, + // selectedPropety: (nextProps.availableProperties.length > 0 ? nextProps.availableProperties[0].id : null) + }); + } + } + + handleChange(event) { + const target = event.target; + const value = target.type === 'checkbox' ? target.checked : target.value; + const name = target.name; + + this.setState({ + [name]: value, + }); + } + + handleArrayChange(e) { + const { + id, + value + } = e.target; + let { + selectedProperties + } = this.state; + + const targetIndex = selectedProperties.findIndex(datum => { + return Number(datum.propertyId) === Number(id); + }); + + if (targetIndex !== -1) { + selectedProperties[targetIndex].value = value; + this.setState({ + selectedProperties + }); + } + } + + addProperty() { + for (let i = 0; i < this.state.availableProperties.length; i += 1) { + if (this.state.availableProperties[i].id === Number(this.state.selectedPropety)) { + this.setState(prevState => ({ + selectedProperties: [ + ...prevState.selectedProperties, + { + propertyId: this.state.selectedPropety, + value: '', + label: this.state.availableProperties[i].name + } + ] + })); + break; + } + } + } + + notSet(propertyId) { + let isSet = false; + + for (let i = 0; i < this.state.selectedProperties.length; i += 1) { + if (this.state.selectedProperties[i].propertyId === propertyId) { + isSet = true; + break; + } + } + + return !isSet; + } + + updateParent() { + + this.props.setProperties({ + target: { + type: 'text', + name: 'Properties', + value: this.state.selectedProperties + } + }); + } + + render() { + return ( +
+ {this.state.selectedProperties.map( (item , key) => ( + + + + + + + ))} +
+ + + + + + {this.state.availableProperties.map( (item, key) => { + if ( this.notSet( item.id ) ) { + return () + } + return (null) + })} + + + + + + + + +
+ ) + } + +} + +export default VegetablesProperties;