From 34a1cc9be173a568dc12fec66ef3b6e52572f693 Mon Sep 17 00:00:00 2001 From: Dosseh KOUTO Date: Fri, 3 Jan 2020 16:29:21 +0100 Subject: [PATCH] generation du client React --- client/src/actions/valeursfonciere/create.js | 45 +++++ client/src/actions/valeursfonciere/delete.js | 29 +++ client/src/actions/valeursfonciere/list.js | 85 ++++++++ client/src/actions/valeursfonciere/show.js | 78 ++++++++ client/src/actions/valeursfonciere/update.js | 131 ++++++++++++ .../src/components/valeursfonciere/Create.js | 64 ++++++ client/src/components/valeursfonciere/Form.js | 122 ++++++++++++ client/src/components/valeursfonciere/List.js | 188 ++++++++++++++++++ client/src/components/valeursfonciere/Show.js | 156 +++++++++++++++ .../src/components/valeursfonciere/Update.js | 123 ++++++++++++ .../src/components/valeursfonciere/index.js | 6 + client/src/index.js | 9 +- client/src/reducers/valeursfonciere/create.js | 33 +++ client/src/reducers/valeursfonciere/delete.js | 33 +++ client/src/reducers/valeursfonciere/index.js | 8 + client/src/reducers/valeursfonciere/list.js | 74 +++++++ client/src/reducers/valeursfonciere/show.js | 59 ++++++ client/src/reducers/valeursfonciere/update.js | 106 ++++++++++ client/src/routes/valeursfonciere.js | 11 + client/src/utils/dataAccess.js | 82 ++++++++ 20 files changed, 1441 insertions(+), 1 deletion(-) create mode 100644 client/src/actions/valeursfonciere/create.js create mode 100644 client/src/actions/valeursfonciere/delete.js create mode 100644 client/src/actions/valeursfonciere/list.js create mode 100644 client/src/actions/valeursfonciere/show.js create mode 100644 client/src/actions/valeursfonciere/update.js create mode 100644 client/src/components/valeursfonciere/Create.js create mode 100644 client/src/components/valeursfonciere/Form.js create mode 100644 client/src/components/valeursfonciere/List.js create mode 100644 client/src/components/valeursfonciere/Show.js create mode 100644 client/src/components/valeursfonciere/Update.js create mode 100644 client/src/components/valeursfonciere/index.js create mode 100644 client/src/reducers/valeursfonciere/create.js create mode 100644 client/src/reducers/valeursfonciere/delete.js create mode 100644 client/src/reducers/valeursfonciere/index.js create mode 100644 client/src/reducers/valeursfonciere/list.js create mode 100644 client/src/reducers/valeursfonciere/show.js create mode 100644 client/src/reducers/valeursfonciere/update.js create mode 100644 client/src/routes/valeursfonciere.js create mode 100644 client/src/utils/dataAccess.js diff --git a/client/src/actions/valeursfonciere/create.js b/client/src/actions/valeursfonciere/create.js new file mode 100644 index 0000000..8101e59 --- /dev/null +++ b/client/src/actions/valeursfonciere/create.js @@ -0,0 +1,45 @@ +import { SubmissionError } from 'redux-form'; +import { fetch } from '../../utils/dataAccess'; + +export function error(error) { + return { type: 'VALEURSFONCIERE_CREATE_ERROR', error }; +} + +export function loading(loading) { + return { type: 'VALEURSFONCIERE_CREATE_LOADING', loading }; +} + +export function success(created) { + return { type: 'VALEURSFONCIERE_CREATE_SUCCESS', created }; +} + +export function create(values) { + return dispatch => { + dispatch(loading(true)); + + return fetch('valeurs_foncieres', { method: 'POST', body: JSON.stringify(values) }) + .then(response => { + dispatch(loading(false)); + + return response.json(); + }) + .then(retrieved => dispatch(success(retrieved))) + .catch(e => { + dispatch(loading(false)); + + if (e instanceof SubmissionError) { + dispatch(error(e.errors._error)); + throw e; + } + + dispatch(error(e.message)); + }); + }; +} + +export function reset() { + return dispatch => { + dispatch(loading(false)); + dispatch(error(null)); + }; +} diff --git a/client/src/actions/valeursfonciere/delete.js b/client/src/actions/valeursfonciere/delete.js new file mode 100644 index 0000000..970ca10 --- /dev/null +++ b/client/src/actions/valeursfonciere/delete.js @@ -0,0 +1,29 @@ +import { fetch } from '../../utils/dataAccess'; + +export function error(error) { + return { type: 'VALEURSFONCIERE_DELETE_ERROR', error }; +} + +export function loading(loading) { + return { type: 'VALEURSFONCIERE_DELETE_LOADING', loading }; +} + +export function success(deleted) { + return { type: 'VALEURSFONCIERE_DELETE_SUCCESS', deleted }; +} + +export function del(item) { + return dispatch => { + dispatch(loading(true)); + + return fetch(item['@id'], { method: 'DELETE' }) + .then(() => { + dispatch(loading(false)); + dispatch(success(item)); + }) + .catch(e => { + dispatch(loading(false)); + dispatch(error(e.message)); + }); + }; +} diff --git a/client/src/actions/valeursfonciere/list.js b/client/src/actions/valeursfonciere/list.js new file mode 100644 index 0000000..f7fed7a --- /dev/null +++ b/client/src/actions/valeursfonciere/list.js @@ -0,0 +1,85 @@ +import { + fetch, + normalize, + extractHubURL, + mercureSubscribe as subscribe +} from '../../utils/dataAccess'; +import { success as deleteSuccess } from './delete'; + +export function error(error) { + return { type: 'VALEURSFONCIERE_LIST_ERROR', error }; +} + +export function loading(loading) { + return { type: 'VALEURSFONCIERE_LIST_LOADING', loading }; +} + +export function success(retrieved) { + return { type: 'VALEURSFONCIERE_LIST_SUCCESS', retrieved }; +} + +export function list(page = 'valeurs_foncieres') { + return dispatch => { + dispatch(loading(true)); + dispatch(error('')); + + fetch(page) + .then(response => + response + .json() + .then(retrieved => ({ retrieved, hubURL: extractHubURL(response) })) + ) + .then(({ retrieved, hubURL }) => { + retrieved = normalize(retrieved); + + dispatch(loading(false)); + dispatch(success(retrieved)); + + if (hubURL && retrieved['hydra:member'].length) + dispatch( + mercureSubscribe( + hubURL, + retrieved['hydra:member'].map(i => i['@id']) + ) + ); + }) + .catch(e => { + dispatch(loading(false)); + dispatch(error(e.message)); + }); + }; +} + +export function reset(eventSource) { + return dispatch => { + if (eventSource) eventSource.close(); + + dispatch({ type: 'VALEURSFONCIERE_LIST_RESET' }); + dispatch(deleteSuccess(null)); + }; +} + +export function mercureSubscribe(hubURL, topics) { + return dispatch => { + const eventSource = subscribe(hubURL, topics); + dispatch(mercureOpen(eventSource)); + eventSource.addEventListener('message', event => + dispatch(mercureMessage(normalize(JSON.parse(event.data)))) + ); + }; +} + +export function mercureOpen(eventSource) { + return { type: 'VALEURSFONCIERE_LIST_MERCURE_OPEN', eventSource }; +} + +export function mercureMessage(retrieved) { + return dispatch => { + if (1 === Object.keys(retrieved).length) { + dispatch({ type: 'VALEURSFONCIERE_LIST_MERCURE_DELETED', retrieved }); + return; + } + + dispatch({ type: 'VALEURSFONCIERE_LIST_MERCURE_MESSAGE', retrieved }); + }; +} diff --git a/client/src/actions/valeursfonciere/show.js b/client/src/actions/valeursfonciere/show.js new file mode 100644 index 0000000..7ceeda9 --- /dev/null +++ b/client/src/actions/valeursfonciere/show.js @@ -0,0 +1,78 @@ +import { + fetch, + extractHubURL, + normalize, + mercureSubscribe as subscribe +} from '../../utils/dataAccess'; + +export function error(error) { + return { type: 'VALEURSFONCIERE_SHOW_ERROR', error }; +} + +export function loading(loading) { + return { type: 'VALEURSFONCIERE_SHOW_LOADING', loading }; +} + +export function success(retrieved) { + return { type: 'VALEURSFONCIERE_SHOW_SUCCESS', retrieved }; +} + +export function retrieve(id) { + return dispatch => { + dispatch(loading(true)); + + return fetch(id) + .then(response => + response + .json() + .then(retrieved => ({ retrieved, hubURL: extractHubURL(response) })) + ) + .then(({ retrieved, hubURL }) => { + retrieved = normalize(retrieved); + + dispatch(loading(false)); + dispatch(success(retrieved)); + + if (hubURL) dispatch(mercureSubscribe(hubURL, retrieved['@id'])); + }) + .catch(e => { + dispatch(loading(false)); + dispatch(error(e.message)); + }); + }; +} + +export function reset(eventSource) { + return dispatch => { + if (eventSource) eventSource.close(); + + dispatch({ type: 'VALEURSFONCIERE_SHOW_RESET' }); + dispatch(error(null)); + dispatch(loading(false)); + }; +} + +export function mercureSubscribe(hubURL, topic) { + return dispatch => { + const eventSource = subscribe(hubURL, [topic]); + dispatch(mercureOpen(eventSource)); + eventSource.addEventListener('message', event => + dispatch(mercureMessage(normalize(JSON.parse(event.data)))) + ); + }; +} + +export function mercureOpen(eventSource) { + return { type: 'VALEURSFONCIERE_SHOW_MERCURE_OPEN', eventSource }; +} + +export function mercureMessage(retrieved) { + return dispatch => { + if (1 === Object.keys(retrieved).length) { + dispatch({ type: 'VALEURSFONCIERE_SHOW_MERCURE_DELETED', retrieved }); + return; + } + + dispatch({ type: 'VALEURSFONCIERE_SHOW_MERCURE_MESSAGE', retrieved }); + }; +} diff --git a/client/src/actions/valeursfonciere/update.js b/client/src/actions/valeursfonciere/update.js new file mode 100644 index 0000000..b36611d --- /dev/null +++ b/client/src/actions/valeursfonciere/update.js @@ -0,0 +1,131 @@ +import { SubmissionError } from 'redux-form'; +import { + fetch, + extractHubURL, + normalize, + mercureSubscribe as subscribe +} from '../../utils/dataAccess'; +import { success as createSuccess } from './create'; +import { loading, error } from './delete'; + +export function retrieveError(retrieveError) { + return { type: 'VALEURSFONCIERE_UPDATE_RETRIEVE_ERROR', retrieveError }; +} + +export function retrieveLoading(retrieveLoading) { + return { type: 'VALEURSFONCIERE_UPDATE_RETRIEVE_LOADING', retrieveLoading }; +} + +export function retrieveSuccess(retrieved) { + return { type: 'VALEURSFONCIERE_UPDATE_RETRIEVE_SUCCESS', retrieved }; +} + +export function retrieve(id) { + return dispatch => { + dispatch(retrieveLoading(true)); + + return fetch(id) + .then(response => + response + .json() + .then(retrieved => ({ retrieved, hubURL: extractHubURL(response) })) + ) + .then(({ retrieved, hubURL }) => { + retrieved = normalize(retrieved); + + dispatch(retrieveLoading(false)); + dispatch(retrieveSuccess(retrieved)); + + if (hubURL) dispatch(mercureSubscribe(hubURL, retrieved['@id'])); + }) + .catch(e => { + dispatch(retrieveLoading(false)); + dispatch(retrieveError(e.message)); + }); + }; +} + +export function updateError(updateError) { + return { type: 'VALEURSFONCIERE_UPDATE_UPDATE_ERROR', updateError }; +} + +export function updateLoading(updateLoading) { + return { type: 'VALEURSFONCIERE_UPDATE_UPDATE_LOADING', updateLoading }; +} + +export function updateSuccess(updated) { + return { type: 'VALEURSFONCIERE_UPDATE_UPDATE_SUCCESS', updated }; +} + +export function update(item, values) { + return dispatch => { + dispatch(updateError(null)); + dispatch(createSuccess(null)); + dispatch(updateLoading(true)); + + return fetch(item['@id'], { + method: 'PUT', + headers: new Headers({ 'Content-Type': 'application/ld+json' }), + body: JSON.stringify(values) + }) + .then(response => + response + .json() + .then(retrieved => ({ retrieved, hubURL: extractHubURL(response) })) + ) + .then(({ retrieved, hubURL }) => { + retrieved = normalize(retrieved); + + dispatch(updateLoading(false)); + dispatch(updateSuccess(retrieved)); + + if (hubURL) dispatch(mercureSubscribe(hubURL, retrieved['@id'])); + }) + .catch(e => { + dispatch(updateLoading(false)); + + if (e instanceof SubmissionError) { + dispatch(updateError(e.errors._error)); + throw e; + } + + dispatch(updateError(e.message)); + }); + }; +} + +export function reset(eventSource) { + return dispatch => { + if (eventSource) eventSource.close(); + + dispatch({ type: 'VALEURSFONCIERE_UPDATE_RESET' }); + dispatch(error(null)); + dispatch(loading(false)); + dispatch(createSuccess(null)); + }; +} + +export function mercureSubscribe(hubURL, topic) { + return dispatch => { + const eventSource = subscribe(hubURL, [topic]); + dispatch(mercureOpen(eventSource)); + eventSource.addEventListener('message', event => + dispatch(mercureMessage(normalize(JSON.parse(event.data)))) + ); + }; +} + +export function mercureOpen(eventSource) { + return { type: 'VALEURSFONCIERE_UPDATE_MERCURE_OPEN', eventSource }; +} + +export function mercureMessage(retrieved) { + return dispatch => { + if (1 === Object.keys(retrieved).length) { + dispatch({ type: 'VALEURSFONCIERE_UPDATE_MERCURE_DELETED', retrieved }); + return; + } + + dispatch({ type: 'VALEURSFONCIERE_UPDATE_MERCURE_MESSAGE', retrieved }); + }; +} diff --git a/client/src/components/valeursfonciere/Create.js b/client/src/components/valeursfonciere/Create.js new file mode 100644 index 0000000..33de841 --- /dev/null +++ b/client/src/components/valeursfonciere/Create.js @@ -0,0 +1,64 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { Link, Redirect } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import Form from './Form'; +import { create, reset } from '../../actions/valeursfonciere/create'; + +class Create extends Component { + static propTypes = { + error: PropTypes.string, + loading: PropTypes.bool.isRequired, + created: PropTypes.object, + create: PropTypes.func.isRequired, + reset: PropTypes.func.isRequired + }; + + componentWillUnmount() { + this.props.reset(); + } + + render() { + if (this.props.created) + return ( + + ); + + return ( +
+

New ValeursFonciere

+ + {this.props.loading && ( +
+ Loading... +
+ )} + {this.props.error && ( +
+
+ )} + +
+ + Back to list + +
+ ); + } +} + +const mapStateToProps = state => { + const { created, error, loading } = state.valeursfonciere.create; + return { created, error, loading }; +}; + +const mapDispatchToProps = dispatch => ({ + create: values => dispatch(create(values)), + reset: () => dispatch(reset()) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Create); diff --git a/client/src/components/valeursfonciere/Form.js b/client/src/components/valeursfonciere/Form.js new file mode 100644 index 0000000..3e5f9fb --- /dev/null +++ b/client/src/components/valeursfonciere/Form.js @@ -0,0 +1,122 @@ +import React, { Component } from 'react'; +import { Field, reduxForm } from 'redux-form'; +import PropTypes from 'prop-types'; + +class Form extends Component { + static propTypes = { + handleSubmit: PropTypes.func.isRequired, + error: PropTypes.string + }; + + renderField = data => { + data.input.className = 'form-control'; + + const isInvalid = data.meta.touched && !!data.meta.error; + if (isInvalid) { + data.input.className += ' is-invalid'; + data.input['aria-invalid'] = true; + } + + if (this.props.error && data.meta.touched && !data.meta.error) { + data.input.className += ' is-valid'; + } + + return ( +
+ + + {isInvalid &&
{data.meta.error}
} +
+ ); + }; + + render() { + return ( + + + + parseFloat(v)} + /> + + parseFloat(v)} + /> + + parseFloat(v)} + /> + parseFloat(v)} + /> + parseFloat(v)} + /> + + + + ); + } +} + +export default reduxForm({ + form: 'valeursfonciere', + enableReinitialize: true, + keepDirtyOnReinitialize: true +})(Form); diff --git a/client/src/components/valeursfonciere/List.js b/client/src/components/valeursfonciere/List.js new file mode 100644 index 0000000..f027d31 --- /dev/null +++ b/client/src/components/valeursfonciere/List.js @@ -0,0 +1,188 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { Link } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import { list, reset } from '../../actions/valeursfonciere/list'; + +class List extends Component { + static propTypes = { + retrieved: PropTypes.object, + loading: PropTypes.bool.isRequired, + error: PropTypes.string, + eventSource: PropTypes.instanceOf(EventSource), + deletedItem: PropTypes.object, + list: PropTypes.func.isRequired, + reset: PropTypes.func.isRequired + }; + + componentDidMount() { + this.props.list( + this.props.match.params.page && + decodeURIComponent(this.props.match.params.page) + ); + } + + componentWillReceiveProps(nextProps) { + if (this.props.match.params.page !== nextProps.match.params.page) + nextProps.list( + nextProps.match.params.page && + decodeURIComponent(nextProps.match.params.page) + ); + } + + componentWillUnmount() { + this.props.reset(this.props.eventSource); + } + + render() { + return ( +
+

ValeursFonciere List

+ + {this.props.loading && ( +
Loading...
+ )} + {this.props.deletedItem && ( +
+ {this.props.deletedItem['@id']} deleted. +
+ )} + {this.props.error && ( +
{this.props.error}
+ )} + +

+ + Create + +

+ + + + + + + + + + + + + + + + + + {this.props.retrieved && + this.props.retrieved['hydra:member'].map(item => ( + + + + + + + + + + + + + + + ))} + +
iddateMutationnatureMutationvaleurFoncierecommunecodeCommunetypeLocalcodeTypeLocalsurfaceReelleBaticodeDepartement +
+ + {item['@id']} + + {item['dateMutation']}{item['natureMutation']}{item['valeurFonciere']}{item['commune']}{item['codeCommune']}{item['typeLocal']}{item['codeTypeLocal']}{item['surfaceReelleBati']}{item['codeDepartement']} + + + +
+ + {this.pagination()} +
+ ); + } + + pagination() { + const view = this.props.retrieved && this.props.retrieved['hydra:view']; + if (!view) return; + + const { + 'hydra:first': first, + 'hydra:previous': previous, + 'hydra:next': next, + 'hydra:last': last + } = view; + + return ( + + ); + } + + renderLinks = (type, items) => { + if (Array.isArray(items)) { + return items.map((item, i) => ( +
{this.renderLinks(type, item)}
+ )); + } + + return ( + {items} + ); + }; +} + +const mapStateToProps = state => { + const { + retrieved, + loading, + error, + eventSource, + deletedItem + } = state.valeursfonciere.list; + return { retrieved, loading, error, eventSource, deletedItem }; +}; + +const mapDispatchToProps = dispatch => ({ + list: page => dispatch(list(page)), + reset: eventSource => dispatch(reset(eventSource)) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(List); diff --git a/client/src/components/valeursfonciere/Show.js b/client/src/components/valeursfonciere/Show.js new file mode 100644 index 0000000..2f66e99 --- /dev/null +++ b/client/src/components/valeursfonciere/Show.js @@ -0,0 +1,156 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { Link, Redirect } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import { retrieve, reset } from '../../actions/valeursfonciere/show'; +import { del } from '../../actions/valeursfonciere/delete'; + +class Show extends Component { + static propTypes = { + retrieved: PropTypes.object, + loading: PropTypes.bool.isRequired, + error: PropTypes.string, + eventSource: PropTypes.instanceOf(EventSource), + retrieve: PropTypes.func.isRequired, + reset: PropTypes.func.isRequired, + deleteError: PropTypes.string, + deleteLoading: PropTypes.bool.isRequired, + deleted: PropTypes.object, + del: PropTypes.func.isRequired + }; + + componentDidMount() { + this.props.retrieve(decodeURIComponent(this.props.match.params.id)); + } + + componentWillUnmount() { + this.props.reset(this.props.eventSource); + } + + del = () => { + if (window.confirm('Are you sure you want to delete this item?')) + this.props.del(this.props.retrieved); + }; + + render() { + if (this.props.deleted) return ; + + const item = this.props.retrieved; + + return ( +
+

Show {item && item['@id']}

+ + {this.props.loading && ( +
+ Loading... +
+ )} + {this.props.error && ( +
+
+ )} + {this.props.deleteError && ( +
+
+ )} + + {item && ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldValue
dateMutation{item['dateMutation']}
natureMutation{item['natureMutation']}
valeurFonciere{item['valeurFonciere']}
commune{item['commune']}
codeCommune{item['codeCommune']}
typeLocal{item['typeLocal']}
codeTypeLocal{item['codeTypeLocal']}
surfaceReelleBati{item['surfaceReelleBati']}
codeDepartement{item['codeDepartement']}
+ )} + + Back to list + + {item && ( + + + + )} + +
+ ); + } + + renderLinks = (type, items) => { + if (Array.isArray(items)) { + return items.map((item, i) => ( +
{this.renderLinks(type, item)}
+ )); + } + + return ( + + {items} + + ); + }; +} + +const mapStateToProps = state => ({ + retrieved: state.valeursfonciere.show.retrieved, + error: state.valeursfonciere.show.error, + loading: state.valeursfonciere.show.loading, + eventSource: state.valeursfonciere.show.eventSource, + deleteError: state.valeursfonciere.del.error, + deleteLoading: state.valeursfonciere.del.loading, + deleted: state.valeursfonciere.del.deleted +}); + +const mapDispatchToProps = dispatch => ({ + retrieve: id => dispatch(retrieve(id)), + del: item => dispatch(del(item)), + reset: eventSource => dispatch(reset(eventSource)) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Show); diff --git a/client/src/components/valeursfonciere/Update.js b/client/src/components/valeursfonciere/Update.js new file mode 100644 index 0000000..c85133e --- /dev/null +++ b/client/src/components/valeursfonciere/Update.js @@ -0,0 +1,123 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { Link, Redirect } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import Form from './Form'; +import { retrieve, update, reset } from '../../actions/valeursfonciere/update'; +import { del } from '../../actions/valeursfonciere/delete'; + +class Update extends Component { + static propTypes = { + retrieved: PropTypes.object, + retrieveLoading: PropTypes.bool.isRequired, + retrieveError: PropTypes.string, + updateLoading: PropTypes.bool.isRequired, + updateError: PropTypes.string, + deleteLoading: PropTypes.bool.isRequired, + deleteError: PropTypes.string, + updated: PropTypes.object, + deleted: PropTypes.object, + eventSource: PropTypes.instanceOf(EventSource), + retrieve: PropTypes.func.isRequired, + update: PropTypes.func.isRequired, + del: PropTypes.func.isRequired, + reset: PropTypes.func.isRequired + }; + + componentDidMount() { + this.props.retrieve(decodeURIComponent(this.props.match.params.id)); + } + + componentWillUnmount() { + this.props.reset(this.props.eventSource); + } + + del = () => { + if (window.confirm('Are you sure you want to delete this item?')) + this.props.del(this.props.retrieved); + }; + + render() { + if (this.props.deleted) return ; + + const item = this.props.updated ? this.props.updated : this.props.retrieved; + + return ( +
+

Edit {item && item['@id']}

+ + {this.props.created && ( +
+ {this.props.created['@id']} created. +
+ )} + {this.props.updated && ( +
+ {this.props.updated['@id']} updated. +
+ )} + {(this.props.retrieveLoading || + this.props.updateLoading || + this.props.deleteLoading) && ( +
+ Loading... +
+ )} + {this.props.retrieveError && ( +
+
+ )} + {this.props.updateError && ( +
+
+ )} + {this.props.deleteError && ( +
+
+ )} + + {item && ( +
this.props.update(item, values)} + initialValues={item} + /> + )} + + Back to list + + +
+ ); + } +} + +const mapStateToProps = state => ({ + retrieved: state.valeursfonciere.update.retrieved, + retrieveError: state.valeursfonciere.update.retrieveError, + retrieveLoading: state.valeursfonciere.update.retrieveLoading, + updateError: state.valeursfonciere.update.updateError, + updateLoading: state.valeursfonciere.update.updateLoading, + deleteError: state.valeursfonciere.del.error, + deleteLoading: state.valeursfonciere.del.loading, + eventSource: state.valeursfonciere.update.eventSource, + created: state.valeursfonciere.create.created, + deleted: state.valeursfonciere.del.deleted, + updated: state.valeursfonciere.update.updated +}); + +const mapDispatchToProps = dispatch => ({ + retrieve: id => dispatch(retrieve(id)), + update: (item, values) => dispatch(update(item, values)), + del: item => dispatch(del(item)), + reset: eventSource => dispatch(reset(eventSource)) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Update); diff --git a/client/src/components/valeursfonciere/index.js b/client/src/components/valeursfonciere/index.js new file mode 100644 index 0000000..e936e55 --- /dev/null +++ b/client/src/components/valeursfonciere/index.js @@ -0,0 +1,6 @@ +import Create from './Create'; +import List from './List'; +import Update from './Update'; +import Show from './Show'; + +export { Create, List, Update, Show }; diff --git a/client/src/index.js b/client/src/index.js index 085cf89..940f0f3 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -15,6 +15,12 @@ import 'bootstrap/dist/css/bootstrap.css'; import 'font-awesome/css/font-awesome.css'; import * as serviceWorker from './serviceWorker'; // Import your reducers and routes here +// import reducers +import valeursfonciere from './reducers/valeursfonciere/'; + +//import routes +import valeursfonciereRoutes from './routes/valeursfonciere'; + import Welcome from './Welcome'; const history = createBrowserHistory(); @@ -22,6 +28,7 @@ const store = createStore( combineReducers({ router: connectRouter(history), form, + valeursfonciere, /* Add your reducers here */ }), applyMiddleware(routerMiddleware(history), thunk) @@ -32,7 +39,7 @@ ReactDOM.render( - {/* Add your routes here */} + {valeursfonciereRoutes}

Not Found

} />
diff --git a/client/src/reducers/valeursfonciere/create.js b/client/src/reducers/valeursfonciere/create.js new file mode 100644 index 0000000..69a1e5f --- /dev/null +++ b/client/src/reducers/valeursfonciere/create.js @@ -0,0 +1,33 @@ +import { combineReducers } from 'redux'; + +export function error(state = null, action) { + switch (action.type) { + case 'VALEURSFONCIERE_CREATE_ERROR': + return action.error; + + default: + return state; + } +} + +export function loading(state = false, action) { + switch (action.type) { + case 'VALEURSFONCIERE_CREATE_LOADING': + return action.loading; + + default: + return state; + } +} + +export function created(state = null, action) { + switch (action.type) { + case 'VALEURSFONCIERE_CREATE_SUCCESS': + return action.created; + + default: + return state; + } +} + +export default combineReducers({ error, loading, created }); diff --git a/client/src/reducers/valeursfonciere/delete.js b/client/src/reducers/valeursfonciere/delete.js new file mode 100644 index 0000000..d1658d1 --- /dev/null +++ b/client/src/reducers/valeursfonciere/delete.js @@ -0,0 +1,33 @@ +import { combineReducers } from 'redux'; + +export function error(state = null, action) { + switch (action.type) { + case 'VALEURSFONCIERE_DELETE_ERROR': + return action.error; + + default: + return state; + } +} + +export function loading(state = false, action) { + switch (action.type) { + case 'VALEURSFONCIERE_DELETE_LOADING': + return action.loading; + + default: + return state; + } +} + +export function deleted(state = null, action) { + switch (action.type) { + case 'VALEURSFONCIERE_DELETE_SUCCESS': + return action.deleted; + + default: + return state; + } +} + +export default combineReducers({ error, loading, deleted }); diff --git a/client/src/reducers/valeursfonciere/index.js b/client/src/reducers/valeursfonciere/index.js new file mode 100644 index 0000000..d57c1ad --- /dev/null +++ b/client/src/reducers/valeursfonciere/index.js @@ -0,0 +1,8 @@ +import { combineReducers } from 'redux'; +import list from './list'; +import create from './create'; +import update from './update'; +import del from './delete'; +import show from './show'; + +export default combineReducers({ list, create, update, del, show }); diff --git a/client/src/reducers/valeursfonciere/list.js b/client/src/reducers/valeursfonciere/list.js new file mode 100644 index 0000000..a7f9840 --- /dev/null +++ b/client/src/reducers/valeursfonciere/list.js @@ -0,0 +1,74 @@ +import { combineReducers } from 'redux'; + +export function error(state = null, action) { + switch (action.type) { + case 'VALEURSFONCIERE_LIST_ERROR': + return action.error; + + case 'VALEURSFONCIERE_LIST_MERCURE_DELETED': + return `${action.retrieved['@id']} has been deleted by another user.`; + + case 'VALEURSFONCIERE_LIST_RESET': + return null; + + default: + return state; + } +} + +export function loading(state = false, action) { + switch (action.type) { + case 'VALEURSFONCIERE_LIST_LOADING': + return action.loading; + + case 'VALEURSFONCIERE_LIST_RESET': + return false; + + default: + return state; + } +} + +export function retrieved(state = null, action) { + switch (action.type) { + case 'VALEURSFONCIERE_LIST_SUCCESS': + return action.retrieved; + + case 'VALEURSFONCIERE_LIST_RESET': + return null; + + case 'VALEURSFONCIERE_LIST_MERCURE_MESSAGE': + return { + ...state, + 'hydra:member': state['hydra:member'].map(item => + item['@id'] === action.retrieved['@id'] ? action.retrieved : item + ) + }; + + case 'VALEURSFONCIERE_LIST_MERCURE_DELETED': + return { + ...state, + 'hydra:member': state['hydra:member'].filter( + item => item['@id'] !== action.retrieved['@id'] + ) + }; + + default: + return state; + } +} + +export function eventSource(state = null, action) { + switch (action.type) { + case 'VALEURSFONCIERE_LIST_MERCURE_OPEN': + return action.eventSource; + + case 'VALEURSFONCIERE_LIST_RESET': + return null; + + default: + return state; + } +} + +export default combineReducers({ error, loading, retrieved, eventSource }); diff --git a/client/src/reducers/valeursfonciere/show.js b/client/src/reducers/valeursfonciere/show.js new file mode 100644 index 0000000..0375d9f --- /dev/null +++ b/client/src/reducers/valeursfonciere/show.js @@ -0,0 +1,59 @@ +import { combineReducers } from 'redux'; + +export function error(state = null, action) { + switch (action.type) { + case 'VALEURSFONCIERE_SHOW_ERROR': + return action.error; + + case 'VALEURSFONCIERE_SHOW_MERCURE_DELETED': + return `${action.retrieved['@id']} has been deleted by another user.`; + + case 'VALEURSFONCIERE_SHOW_RESET': + return null; + + default: + return state; + } +} + +export function loading(state = false, action) { + switch (action.type) { + case 'VALEURSFONCIERE_SHOW_LOADING': + return action.loading; + + case 'VALEURSFONCIERE_SHOW_RESET': + return false; + + default: + return state; + } +} + +export function retrieved(state = null, action) { + switch (action.type) { + case 'VALEURSFONCIERE_SHOW_SUCCESS': + case 'VALEURSFONCIERE_SHOW_MERCURE_MESSAGE': + return action.retrieved; + + case 'VALEURSFONCIERE_SHOW_RESET': + return null; + + default: + return state; + } +} + +export function eventSource(state = null, action) { + switch (action.type) { + case 'VALEURSFONCIERE_SHOW_MERCURE_OPEN': + return action.eventSource; + + case 'VALEURSFONCIERE_SHOW_RESET': + return null; + + default: + return state; + } +} + +export default combineReducers({ error, loading, retrieved, eventSource }); diff --git a/client/src/reducers/valeursfonciere/update.js b/client/src/reducers/valeursfonciere/update.js new file mode 100644 index 0000000..8533490 --- /dev/null +++ b/client/src/reducers/valeursfonciere/update.js @@ -0,0 +1,106 @@ +import { combineReducers } from 'redux'; + +export function retrieveError(state = null, action) { + switch (action.type) { + case 'VALEURSFONCIERE_UPDATE_RETRIEVE_ERROR': + return action.retrieveError; + + case 'VALEURSFONCIERE_UPDATE_MERCURE_DELETED': + return `${action.retrieved['@id']} has been deleted by another user.`; + + case 'VALEURSFONCIERE_UPDATE_RESET': + return null; + + default: + return state; + } +} + +export function retrieveLoading(state = false, action) { + switch (action.type) { + case 'VALEURSFONCIERE_UPDATE_RETRIEVE_LOADING': + return action.retrieveLoading; + + case 'VALEURSFONCIERE_UPDATE_RESET': + return false; + + default: + return state; + } +} + +export function retrieved(state = null, action) { + switch (action.type) { + case 'VALEURSFONCIERE_UPDATE_RETRIEVE_SUCCESS': + case 'VALEURSFONCIERE_UPDATE_MERCURE_MESSAGE': + return action.retrieved; + + case 'VALEURSFONCIERE_UPDATE_RESET': + return null; + + default: + return state; + } +} + +export function updateError(state = null, action) { + switch (action.type) { + case 'VALEURSFONCIERE_UPDATE_UPDATE_ERROR': + return action.updateError; + + case 'VALEURSFONCIERE_UPDATE_RESET': + return null; + + default: + return state; + } +} + +export function updateLoading(state = false, action) { + switch (action.type) { + case 'VALEURSFONCIERE_UPDATE_UPDATE_LOADING': + return action.updateLoading; + + case 'VALEURSFONCIERE_UPDATE_RESET': + return false; + + default: + return state; + } +} + +export function updated(state = null, action) { + switch (action.type) { + case 'VALEURSFONCIERE_UPDATE_UPDATE_SUCCESS': + return action.updated; + + case 'VALEURSFONCIERE_UPDATE_RESET': + return null; + + default: + return state; + } +} + +export function eventSource(state = null, action) { + switch (action.type) { + case 'VALEURSFONCIERE_UPDATE_MERCURE_OPEN': + return action.eventSource; + + case 'VALEURSFONCIERE_UPDATE_RESET': + return null; + + default: + return state; + } +} + +export default combineReducers({ + retrieveError, + retrieveLoading, + retrieved, + updateError, + updateLoading, + updated, + eventSource +}); diff --git a/client/src/routes/valeursfonciere.js b/client/src/routes/valeursfonciere.js new file mode 100644 index 0000000..b142e8d --- /dev/null +++ b/client/src/routes/valeursfonciere.js @@ -0,0 +1,11 @@ +import React from 'react'; +import { Route } from 'react-router-dom'; +import { List, Create, Update, Show } from '../components/valeursfonciere/'; + +export default [ + , + , + , + , + +]; diff --git a/client/src/utils/dataAccess.js b/client/src/utils/dataAccess.js new file mode 100644 index 0000000..2a3e2ba --- /dev/null +++ b/client/src/utils/dataAccess.js @@ -0,0 +1,82 @@ +import { ENTRYPOINT } from '../config/entrypoint'; +import { SubmissionError } from 'redux-form'; +import get from 'lodash/get'; +import has from 'lodash/has'; +import mapValues from 'lodash/mapValues'; + +const MIME_TYPE = 'application/ld+json'; + +export function fetch(id, options = {}) { + if ('undefined' === typeof options.headers) options.headers = new Headers(); + if (null === options.headers.get('Accept')) + options.headers.set('Accept', MIME_TYPE); + + if ( + 'undefined' !== options.body && + !(options.body instanceof FormData) && + null === options.headers.get('Content-Type') + ) + options.headers.set('Content-Type', MIME_TYPE); + + return global.fetch(new URL(id, ENTRYPOINT), options).then(response => { + if (response.ok) return response; + + return response.json().then( + json => { + const error = + json['hydra:description'] || + json['hydra:title'] || + 'An error occurred.'; + if (!json.violations) throw Error(error); + + let errors = { _error: error }; + json.violations.forEach(violation => + errors[violation.propertyPath] + ? (errors[violation.propertyPath] += + '\n' + errors[violation.propertyPath]) + : (errors[violation.propertyPath] = violation.message) + ); + + throw new SubmissionError(errors); + }, + () => { + throw new Error(response.statusText || 'An error occurred.'); + } + ); + }); +} + +export function mercureSubscribe(url, topics) { + topics.forEach(topic => + url.searchParams.append('topic', new URL(topic, ENTRYPOINT)) + ); + + return new EventSource(url.toString()); +} + +export function normalize(data) { + if (has(data, 'hydra:member')) { + // Normalize items in collections + data['hydra:member'] = data['hydra:member'].map(item => normalize(item)); + + return data; + } + + // Flatten nested documents + return mapValues(data, value => + Array.isArray(value) + ? value.map(v => get(v, '@id', v)) + : get(value, '@id', value) + ); +} + +export function extractHubURL(response) { + const linkHeader = response.headers.get('Link'); + if (!linkHeader) return null; + + const matches = linkHeader.match( + /<([^>]+)>;\s+rel=(?:mercure|"[^"]*mercure[^"]*")/ + ); + + return matches && matches[1] ? new URL(matches[1], ENTRYPOINT) : null; +} -- GitLab