Bugzilla – Attachment 131169 Details for
Bug 30160
Rewrite cities admin view in React
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 30160: Don't use classes but hooks
Bug-30160-Dont-use-classes-but-hooks.patch (text/plain), 31.62 KB, created by
Jonathan Druart
on 2022-02-28 16:17:31 UTC
(
hide
)
Description:
Bug 30160: Don't use classes but hooks
Filename:
MIME Type:
Creator:
Jonathan Druart
Created:
2022-02-28 16:17:31 UTC
Size:
31.62 KB
patch
obsolete
>From e03c076caba6a4ba9e535a15c7d80dccc228422f Mon Sep 17 00:00:00 2001 >From: Jonathan Druart <jonathan.druart@bugs.koha-community.org> >Date: Mon, 28 Feb 2022 16:50:16 +0100 >Subject: [PATCH] Bug 30160: Don't use classes but hooks > >The modern way to use react is to not use classes but functions, as well >as useEffects and useState >https://en.reactjs.org/docs/hooks-intro.html >--- > gulpfile.js | 45 +++-- > koha-tmpl/intranet-tmpl/prog/js/src/Cities.js | 161 ------------------ > .../prog/js/src/cities/AddCity.js | 121 ------------- > .../prog/js/src/cities/DeleteCity.js | 67 -------- > .../prog/js/src/components/Cities.js | 144 ++++++++++++++++ > .../prog/js/src/components/cities/AddCity.js | 104 +++++++++++ > .../js/src/components/cities/DeleteCity.js | 57 +++++++ > .../cities/ListCity.js} | 134 +++++---------- > koha-tmpl/intranet-tmpl/prog/js/src/index.js | 4 +- > package.json | 4 +- > 10 files changed, 390 insertions(+), 451 deletions(-) > delete mode 100644 koha-tmpl/intranet-tmpl/prog/js/src/Cities.js > delete mode 100644 koha-tmpl/intranet-tmpl/prog/js/src/cities/AddCity.js > delete mode 100644 koha-tmpl/intranet-tmpl/prog/js/src/cities/DeleteCity.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/src/components/Cities.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/src/components/cities/AddCity.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/src/components/cities/DeleteCity.js > rename koha-tmpl/intranet-tmpl/prog/js/src/{cities/CityList.js => components/cities/ListCity.js} (54%) > >diff --git a/gulpfile.js b/gulpfile.js >index dc738521f47..4913936581d 100644 >--- a/gulpfile.js >+++ b/gulpfile.js >@@ -11,6 +11,8 @@ const util = require('util'); > const tap = require( "gulp-tap" ); > const browserify = require( "browserify" ); > const babelify = require( "babelify" ); >+const watchify = require('watchify'); >+const source = require('vinyl-source-stream'); > > const sass = require("gulp-sass"); > const cssnano = require("gulp-cssnano"); >@@ -86,18 +88,39 @@ function build() { > } > } > >-function build_js(){ >- >- let bundler = browserify().transform(babelify, {presets: ["@babel/preset-env", "@babel/preset-react"]}); >- >- return src( js_base + '/src/index.js' ) >- .pipe( tap( file => { >- let base_start = file.path.indexOf( js_base ); >- bundler.require( [ "react", "react-dom" ] ); >- file.contents = bundler.add( file.path ).bundle(); >- } ) ) >- .pipe( dest( js_base+ "/built" ) ); > >+function build_js(){ >+ var bundler = browserify({ >+ entries: [js_base + '/src/index.js'], >+ debug: false, >+ cache: {}, >+ packageCache: {}, >+ extensions: ['.js', '.json', '.jsx'], >+ paths: [js_base + 'src'], >+ fullPaths: false >+ }); >+ >+ bundler = bundler.transform(babelify, {presets: ["@babel/preset-env", "@babel/preset-react"]}); >+ >+ function bundle(b) { >+ var startMs = Date.now(); >+ var db = b.bundle() >+ .on('error', function(err) { >+ console.log(err.message); >+ this.emit('end'); >+ }) >+ .pipe(source('../built/index.js')) >+ db.pipe(dest(js_base + '/src')); >+ console.log('Updated bundle file in', (Date.now() - startMs) + 'ms'); >+ return db; >+ } >+ >+ bundler = watchify(bundler) >+ .on('update', function() { >+ bundle(bundler); >+ }); >+ >+ return bundle(bundler); > } > > const poTasks = { >diff --git a/koha-tmpl/intranet-tmpl/prog/js/src/Cities.js b/koha-tmpl/intranet-tmpl/prog/js/src/Cities.js >deleted file mode 100644 >index 70b5c815ee2..00000000000 >--- a/koha-tmpl/intranet-tmpl/prog/js/src/Cities.js >+++ /dev/null >@@ -1,161 +0,0 @@ >-import React, { Component } from 'react' >-import { Container, Button, Alert } from 'react-bootstrap' >-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' >-import { faPlus } from '@fortawesome/free-solid-svg-icons' >-import CityList from './cities/CityList' >-import AddCity from './cities/AddCity' >-import DeleteCity from './cities/DeleteCity' >- >-class App extends Component { >- constructor(props) { >- super(props) >- this.state = { >- op: 'list', >- error: null, >- message: null, >- city: {}, >- cities: [], >- } >- } >- >- onCreate() { >- this.setState({ op: 'add', city: {}, error: null, message: null }) >- } >- >- addCity = city => { >- let apiUrl >- >- apiUrl = '/api/v1/cities' >- >- const myHeaders = new Headers() >- myHeaders.append('Content-Type', 'application/json') >- >- let method = 'POST' >- if ( city.city_id ) { >- method = 'PUT' >- apiUrl += '/' + city.city_id >- } >- delete city.city_id >- >- const options = { >- method: method, >- body: JSON.stringify(city), >- myHeaders >- } >- >- fetch(apiUrl, options) >- .then(response => { >- if ( response.status == 200 ) { >- this.setState({ >- message: 'City updated', >- error: null, >- op: 'list', >- }) >- } else if ( response.status == 201 ) { >- this.setState({ >- message: 'City created', >- error: null, >- op: 'list', >- }) >- } else { >- this.setState({ error: response.message }) >- } >- }, (error) => { >- this.setState({ error }) >- }) >- } >- >- editCity = city_id => { >- >- const apiUrl = '/api/v1/cities/' + city_id >- >- const options = { >- method: 'GET', >- } >- >- fetch(apiUrl, options) >- .then(res => res.json()) >- .then( >- (result) => { >- this.setState({ >- city: result, >- op: 'add', >- message: null, >- error: null, >- }) >- }, (error) => { >- this.setState({ error }) >- } >- ) >- } >- >- confirmDeleteCity = city_id => { >- const apiUrl = '/api/v1/cities/' + city_id >- >- const options = { >- method: 'GET', >- } >- >- fetch(apiUrl, options) >- .then(res => res.json()) >- .then( >- (result) => { >- this.setState({ >- city: result, >- op: 'delete', >- message: null, >- error: null, >- }) >- }, (error) => { >- this.setState({ error }) >- } >- ) >- } >- >- deleteCity = city_id => { >- >- const { cities } = this.state >- const apiUrl = '/api/v1/cities/' + city_id >- >- const options = { >- method: 'DELETE', >- } >- >- fetch(apiUrl, options) >- .then(response => { >- if ( response.status == 204 ) { >- this.setState({ >- message: 'City deleted', >- error: null, >- op: 'list', >- }) >- } else { >- this.setState({ error: response.message }) >- } >- }, (error) => { >- this.setState({ error }) >- } >- ) >- } >- >- cancel = event => { >- event.preventDefault() >- this.setState({op: 'list'}) >- } >- >- render() { >- >- return ( >- <div className="App"> >- {this.state.op == 'list' && <div id="toolbar" className="btn-toolbar"><Button id="newcity" variant="default" onClick={() => this.onCreate()}><FontAwesomeIcon icon={faPlus} /> New city</Button></div>} >- {this.state.message && <div><Alert key="message_info" variant="info" transition={false}>{this.state.message}</Alert></div>} >- {this.state.error && <div><Alert key="message_error" variant="warning" transition={false}>Error: {this.state.error.message}</Alert></div>} >- {this.state.op == 'list' && <CityList editCity={this.editCity} confirmDeleteCity={this.confirmDeleteCity} cities={this.state.cities} />} >- {this.state.op == 'add' && <AddCity onFormSubmit={this.addCity} onFormCancel={this.cancel} city={this.state.city} />} >- {this.state.op == 'delete' && <DeleteCity onFormSubmit={this.deleteCity} onFormCancel={this.cancel} city={this.state.city} />} >- </div> >- ) >- } >-} >- >-export default App >diff --git a/koha-tmpl/intranet-tmpl/prog/js/src/cities/AddCity.js b/koha-tmpl/intranet-tmpl/prog/js/src/cities/AddCity.js >deleted file mode 100644 >index 57205c9c031..00000000000 >--- a/koha-tmpl/intranet-tmpl/prog/js/src/cities/AddCity.js >+++ /dev/null >@@ -1,121 +0,0 @@ >-import React from 'react'; >-import { Row, Form, Col, Button } from 'react-bootstrap'; >- >-class AddCity extends React.Component { >- constructor(props) { >- super(props); >- this.initialState = { >- city_id: '', >- name: '', >- state: '', >- postal_code: '', >- country: '' >- } >- >- if(props.city.city_id){ >- this.state = props.city >- } else { >- this.state = this.initialState; >- } >- >- this.handleChange = this.handleChange.bind(this); >- this.handleSubmit = this.handleSubmit.bind(this); >- this.handleCancel = this.handleCancel.bind(this); >- } >- >- handleChange(event) { >- const name = event.target.name; >- const value = event.target.value; >- >- this.setState({ >- [name]: value >- }) >- } >- >- handleSubmit(event) { >- event.preventDefault(); >- this.props.onFormSubmit(this.state); >- } >- >- handleCancel(event) { >- event.preventDefault(); >- this.props.onFormCancel(event); >- } >- >- render() { >- >- let pageTitle; >- if(this.state.city_id) { >- pageTitle = <h2>Edit city</h2> >- } else { >- pageTitle = <h2>New city</h2> >- } >- >- return( >- <div> >- {pageTitle} >- <Form onSubmit={this.handleSubmit} className="validated"> >- <fieldset className="rows"> >- <Form.Group controlId="name" as={Row}> >- <Form.Label column sm={4} className="required">City:</Form.Label> >- <Col sm={8}> >- <Form.Control >- type="text" >- name="name" >- value={this.state.name} >- onChange={this.handleChange} >- placeholder="City name" >- required="required"/> >- <span className="required">Required</span> >- </Col> >- </Form.Group> >- <Form.Group controlId="state" as={Row}> >- <Form.Label column sm={4}>State:</Form.Label> >- <Col sm={8}> >- <Form.Control >- type="text" >- name="state" >- value={this.state.state} >- onChange={this.handleChange} >- placeholder="City State"/> >- </Col> >- </Form.Group> >- <Form.Group controlId="postal_code" as={Row}> >- <Form.Label column sm={4} className="required">ZIP/Postal code:</Form.Label> >- <Col sm={8}> >- <Form.Control >- type="text" >- name="postal_code" >- value={this.state.postal_code} >- onChange={this.handleChange} >- placeholder="City postal code" >- required="required"/> >- <span className="required">Required</span> >- </Col> >- </Form.Group> >- <Form.Group controlId="country" as={Row}> >- <Form.Label column sm={4}>Country:</Form.Label> >- <Col sm={8}> >- <Form.Control >- type="text" >- name="country" >- value={this.state.country} >- onChange={this.handleChange} >- placeholder="City country"/> >- </Col> >- </Form.Group> >- </fieldset> >- <fieldset className="action"> >- <Form.Group> >- <Form.Control type="hidden" name="id" value={this.state.city_id} /> >- <Button variant="default" type="submit">Submit</Button> >- <a href="#" onClick={(e) => this.handleCancel(e)}>Cancel</a> >- </Form.Group> >- </fieldset> >- </Form> >- </div> >- ) >- } >-} >- >-export default AddCity; >diff --git a/koha-tmpl/intranet-tmpl/prog/js/src/cities/DeleteCity.js b/koha-tmpl/intranet-tmpl/prog/js/src/cities/DeleteCity.js >deleted file mode 100644 >index b2bb037eded..00000000000 >--- a/koha-tmpl/intranet-tmpl/prog/js/src/cities/DeleteCity.js >+++ /dev/null >@@ -1,67 +0,0 @@ >-import React from 'react'; >-import { Row, Form, Col, Button } from 'react-bootstrap'; >-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' >-import { faCheck, faRemove } from '@fortawesome/free-solid-svg-icons' >- >-class DeleteCity extends React.Component { >- constructor(props) { >- super(props); >- this.state = props.city; >- >- this.handleSubmit = this.handleSubmit.bind(this); >- this.handleCancel = this.handleCancel.bind(this); >- } >- >- handleSubmit(event) { >- event.preventDefault(); >- this.props.onFormSubmit(this.state.city_id); >- } >- handleCancel(event) { >- event.preventDefault(); >- this.props.onFormCancel(event); >- } >- >- render() { >- >- let pageTitle = <h2>Delete city</h2> >- >- return( >- <div> >- <Row> >- {pageTitle} >- <Form onSubmit={this.handleSubmit}> >- <fieldset className="rows"> >- <h3>Delete city "{this.state.name}"?</h3> >- <Form.Group controlId="city_id" as={Row}> >- <Form.Label column sm={4}>City id:</Form.Label> >- <Col sm={8}>{this.state.city_id}</Col> >- </Form.Group> >- <Form.Group controlId="name" as={Row}> >- <Form.Label column sm={4}>City:</Form.Label> >- <Col sm={8}>{this.state.name}</Col> >- </Form.Group> >- <Form.Group controlId="state" as={Row}> >- <Form.Label column sm={4}>State:</Form.Label> >- <Col sm={8}>{this.state.state}</Col> >- </Form.Group> >- <Form.Group controlId="postal_code" as={Row}> >- <Form.Label column sm={4}>ZIP/Postal code:</Form.Label> >- <Col sm={8}>{this.state.postal_code}</Col> >- </Form.Group> >- <Form.Group controlId="country" as={Row}> >- <Form.Label column sm={4}>Country:</Form.Label> >- <Col sm={8}>{this.state.country}</Col> >- </Form.Group> >- </fieldset> >- <fieldset className="action"> >- <Button variant="default" className="approve" type="submit"><FontAwesomeIcon icon={faCheck} /> Yes, Delete</Button> >- <Button variant="default" className="deny" onClick={(e) => this.handleCancel(e)}><FontAwesomeIcon icon={faRemove} /> No, do not remove</Button> >- </fieldset> >- </Form> >- </Row> >- </div> >- ) >- } >-} >- >-export default DeleteCity; >diff --git a/koha-tmpl/intranet-tmpl/prog/js/src/components/Cities.js b/koha-tmpl/intranet-tmpl/prog/js/src/components/Cities.js >new file mode 100644 >index 00000000000..996d0d8a609 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/src/components/Cities.js >@@ -0,0 +1,144 @@ >+import React, { useState } from 'react'; >+import { Container, Button, Alert } from 'react-bootstrap' >+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' >+import { faPlus } from '@fortawesome/free-solid-svg-icons' >+import ListCity from './cities/ListCity' >+import AddCity from './cities/AddCity' >+import DeleteCity from './cities/DeleteCity' >+ >+const Cities = () => { >+ const [op, setOp] = useState('list') >+ const [error, setError] = useState() >+ const [message, setMessage] = useState() >+ const [city, setCity] = useState({}) >+ >+ const onCreate = () => { >+ setOp('add'); >+ setCity({}); >+ setError(null); >+ setMessage(null); >+ } >+ >+ const addCity = city => { >+ let apiUrl >+ >+ apiUrl = '/api/v1/cities' >+ >+ const myHeaders = new Headers() >+ myHeaders.append('Content-Type', 'application/json') >+ >+ let method = 'POST' >+ if ( city.city_id ) { >+ method = 'PUT' >+ apiUrl += '/' + city.city_id >+ } >+ delete city.city_id >+ >+ const options = { >+ method: method, >+ body: JSON.stringify(city), >+ myHeaders >+ } >+ >+ fetch(apiUrl, options) >+ .then(response => { >+ if ( response.status == 200 ) { >+ setMessage('City updated') >+ setError(null) >+ setOp('list') >+ } else if ( response.status == 201 ) { >+ setMessage('City created') >+ setError(null) >+ setOp('list') >+ } else { >+ setError(response.message); >+ } >+ }, (e) => { >+ setError(e); >+ }) >+ } >+ >+ const editCity = city_id => { >+ >+ const apiUrl = '/api/v1/cities/' + city_id >+ >+ const options = { >+ method: 'GET', >+ } >+ >+ fetch(apiUrl, options) >+ .then(res => res.json()) >+ .then( >+ (result) => { >+ setCity(result) >+ setOp('add') >+ setMessage(null) >+ setError(null) >+ }, (e) => { >+ setError(e) >+ } >+ ) >+ } >+ >+ const confirmDeleteCity = city_id => { >+ const apiUrl = '/api/v1/cities/' + city_id >+ >+ const options = { >+ method: 'GET', >+ } >+ >+ fetch(apiUrl, options) >+ .then(res => res.json()) >+ .then( >+ (result) => { >+ setCity(result) >+ setOp('delete') >+ setMessage(null) >+ setError(null) >+ }, (e) => { >+ setError(e); >+ } >+ ) >+ } >+ >+ const deleteCity = city_id => { >+ >+ const apiUrl = '/api/v1/cities/' + city_id >+ >+ const options = { >+ method: 'DELETE', >+ } >+ >+ fetch(apiUrl, options) >+ .then(response => { >+ if ( response.status == 204 ) { >+ setMessage('City deleted') >+ setError(null) >+ setOp('list') >+ } else { >+ setError(response.message) >+ } >+ }, (e) => { >+ setError(e) >+ } >+ ) >+ } >+ >+ const cancel = event => { >+ event.preventDefault() >+ setOp('list') >+ } >+ >+ return ( >+ <div className="App"> >+ {op == 'list' && <div id="toolbar" className="btn-toolbar"><Button id="newcity" variant="default" onClick={() => onCreate()}><FontAwesomeIcon icon={faPlus} /> New city</Button></div>} >+ {message && <div><Alert key="message_info" variant="info" transition={false}>{message}</Alert></div>} >+ {error && <div><Alert key="message_error" variant="warning" transition={false}>Error: {error.message}</Alert></div>} >+ {op == 'list' && <ListCity editCity={editCity} confirmDeleteCity={confirmDeleteCity} />} >+ {op == 'add' && <AddCity onFormSubmit={addCity} onFormCancel={cancel} city={city} />} >+ {op == 'delete' && <DeleteCity onFormSubmit={deleteCity} onFormCancel={cancel} city={city} />} >+ </div> >+ ) >+} >+ >+export default Cities >diff --git a/koha-tmpl/intranet-tmpl/prog/js/src/components/cities/AddCity.js b/koha-tmpl/intranet-tmpl/prog/js/src/components/cities/AddCity.js >new file mode 100644 >index 00000000000..6dc64d0ae8b >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/src/components/cities/AddCity.js >@@ -0,0 +1,104 @@ >+import React, { useState } from 'react'; >+import { Row, Form, Col, Button } from 'react-bootstrap'; >+ >+const AddCity = ({ onFormSubmit, onFormCancel, city }) => { >+ >+ const initialState = { >+ city_id: '', >+ name: '', >+ state: '', >+ postal_code: '', >+ country: '' >+ } >+ const [myCity, setCity] = useState(city ? city : initialState) >+ >+ const handleChange = event => { >+ const name = event.target.name; >+ const value = event.target.value; >+ setCity({...myCity, [name]: value}) >+ } >+ >+ const handleSubmit = event => { >+ event.preventDefault(); >+ onFormSubmit(myCity); >+ } >+ >+ const handleCancel = event => { >+ event.preventDefault(); >+ onFormCancel(event); >+ } >+ >+ let pageTitle; >+ if(city.city_id) { >+ pageTitle = <h2>Edit city</h2> >+ } else { >+ pageTitle = <h2>New city</h2> >+ } >+ >+ return( >+ <div> >+ {pageTitle} >+ <Form onSubmit={handleSubmit} className="validated"> >+ <fieldset className="rows"> >+ <Form.Group controlId="name" as={Row}> >+ <Form.Label column sm={4} className="required">City:</Form.Label> >+ <Col sm={8}> >+ <Form.Control >+ type="text" >+ name="name" >+ value={myCity.name} >+ onChange={handleChange} >+ placeholder="City name" >+ required="required"/> >+ <span className="required">Required</span> >+ </Col> >+ </Form.Group> >+ <Form.Group controlId="state" as={Row}> >+ <Form.Label column sm={4}>State:</Form.Label> >+ <Col sm={8}> >+ <Form.Control >+ type="text" >+ name="state" >+ value={myCity.state} >+ onChange={handleChange} >+ placeholder="City State"/> >+ </Col> >+ </Form.Group> >+ <Form.Group controlId="postal_code" as={Row}> >+ <Form.Label column sm={4} className="required">ZIP/Postal code:</Form.Label> >+ <Col sm={8}> >+ <Form.Control >+ type="text" >+ name="postal_code" >+ value={myCity.postal_code} >+ onChange={handleChange} >+ placeholder="City postal code" >+ required="required"/> >+ <span className="required">Required</span> >+ </Col> >+ </Form.Group> >+ <Form.Group controlId="country" as={Row}> >+ <Form.Label column sm={4}>Country:</Form.Label> >+ <Col sm={8}> >+ <Form.Control >+ type="text" >+ name="country" >+ value={myCity.country} >+ onChange={handleChange} >+ placeholder="City country"/> >+ </Col> >+ </Form.Group> >+ </fieldset> >+ <fieldset className="action"> >+ <Form.Group> >+ <Form.Control type="hidden" name="id" value={myCity.city_id} /> >+ <Button variant="default" type="submit">Submit</Button> >+ <a href="#" onClick={(e) => handleCancel(e)}>Cancel</a> >+ </Form.Group> >+ </fieldset> >+ </Form> >+ </div> >+ ) >+} >+ >+export default AddCity; >diff --git a/koha-tmpl/intranet-tmpl/prog/js/src/components/cities/DeleteCity.js b/koha-tmpl/intranet-tmpl/prog/js/src/components/cities/DeleteCity.js >new file mode 100644 >index 00000000000..37bc4958bdf >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/src/components/cities/DeleteCity.js >@@ -0,0 +1,57 @@ >+import React from 'react'; >+import { Row, Form, Col, Button } from 'react-bootstrap'; >+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' >+import { faCheck, faRemove } from '@fortawesome/free-solid-svg-icons' >+ >+const DeleteCity = ({ onFormSubmit, onFormCancel, city }) => { >+ >+ const handleSubmit = event => { >+ event.preventDefault(); >+ onFormSubmit(city.city_id); >+ } >+ const handleCancel = event => { >+ event.preventDefault(); >+ onFormCancel(event); >+ } >+ >+ let pageTitle = <h2>Delete city</h2> >+ >+ return( >+ <div> >+ <Row> >+ {pageTitle} >+ <Form onSubmit={handleSubmit}> >+ <fieldset className="rows"> >+ <h3>Delete city "{city.name}"?</h3> >+ <Form.Group controlId="city_id" as={Row}> >+ <Form.Label column sm={4}>City id:</Form.Label> >+ <Col sm={8}>{city.city_id}</Col> >+ </Form.Group> >+ <Form.Group controlId="name" as={Row}> >+ <Form.Label column sm={4}>City:</Form.Label> >+ <Col sm={8}>{city.name}</Col> >+ </Form.Group> >+ <Form.Group controlId="state" as={Row}> >+ <Form.Label column sm={4}>State:</Form.Label> >+ <Col sm={8}>{city.state}</Col> >+ </Form.Group> >+ <Form.Group controlId="postal_code" as={Row}> >+ <Form.Label column sm={4}>ZIP/Postal code:</Form.Label> >+ <Col sm={8}>{city.postal_code}</Col> >+ </Form.Group> >+ <Form.Group controlId="country" as={Row}> >+ <Form.Label column sm={4}>Country:</Form.Label> >+ <Col sm={8}>{city.country}</Col> >+ </Form.Group> >+ </fieldset> >+ <fieldset className="action"> >+ <Button variant="default" className="approve" type="submit"><FontAwesomeIcon icon={faCheck} /> Yes, Delete</Button> >+ <Button variant="default" className="deny" onClick={(e) => handleCancel(e)}><FontAwesomeIcon icon={faRemove} /> No, do not remove</Button> >+ </fieldset> >+ </Form> >+ </Row> >+ </div> >+ ) >+} >+ >+export default DeleteCity; >diff --git a/koha-tmpl/intranet-tmpl/prog/js/src/cities/CityList.js b/koha-tmpl/intranet-tmpl/prog/js/src/components/cities/ListCity.js >similarity index 54% >rename from koha-tmpl/intranet-tmpl/prog/js/src/cities/CityList.js >rename to koha-tmpl/intranet-tmpl/prog/js/src/components/cities/ListCity.js >index c68bab36533..d289a934478 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/src/cities/CityList.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/src/components/cities/ListCity.js >@@ -1,51 +1,27 @@ >-import React, { Component } from 'react'; >+import React, { useState, useEffect, useRef } from 'react'; > import ReactDOM from 'react-dom'; > import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' > import { faPencil, faTrash } from '@fortawesome/free-solid-svg-icons' > import { Button, Alert } from 'react-bootstrap'; > >-class EditButton extends Component{ >- constructor(props){ >- super(props); >- this.handleClick = this.handleClick.bind(this); >- } >- handleClick(){ >- this.props.handleClick(this.props.city_id); >- } >- >- render(){ >- return ( >- <Button variant="default" size="sm" onClick={this.handleClick}><FontAwesomeIcon icon={faPencil} size="xs" /> Edit</Button> >- ) >- } >+const EditButton = ({city_id, handleClick}) => { >+ return ( >+ <Button variant="default" size="sm" onClick={() => handleClick(city_id)}><FontAwesomeIcon icon={faPencil} size="xs" /> Edit</Button> >+ ) > } > >-class DeleteButton extends Component{ >- constructor(props){ >- super(props); >- this.handleClick = this.handleClick.bind(this); >- } >- handleClick(){ >- this.props.handleClick(this.props.city_id); >- } >- >- render(){ >- return ( >- <Button variant="default" size="sm" onClick={this.handleClick}><FontAwesomeIcon icon={faTrash} size="xs" /> Delete</Button> >- ) >- } >+const DeleteButton = ({city_id, handleClick}) => { >+ return ( >+ <Button variant="default" size="sm" onClick={() => handleClick(city_id)}><FontAwesomeIcon icon={faTrash} size="xs" /> Delete</Button> >+ ) > } > >-class Table extends Component { >- constructor(props){ >- super(props); >- } >+const Table = ({editCity, confirmDeleteCity}) => { >+ const table_ref = React.createRef() > >- componentDidMount() { >+ useEffect(() => { > >- const editCity = this.props.editCity; >- const confirmDeleteCity = this.props.confirmDeleteCity; >- $(this.refs.main).kohaTable({ >+ $(table_ref.current).kohaTable({ > "ajax": { > "url": cities_table_url, > }, >@@ -106,71 +82,53 @@ class Table extends Component { > } > > }, columns_settings, 1); >- } > >- componentWillUnmount(){ >- $('.data-table-wrapper') >- .find('table') >- .DataTable() >- .destroy(true); >- } >- >- shouldComponentUpdate() { >- return true; >- } >- >- render() { >- return ( >- <div> >- <table ref="main" /> >- </div>); >- } >+ return function cleanup(){ >+ $('.data-table-wrapper') >+ .find('table') >+ .DataTable() >+ .destroy(true); >+ } >+ }, []) >+ >+ return ( >+ <div> >+ <table ref={table_ref} /> >+ </div>) > } > >-class CityList extends React.Component { >- constructor(props) { >- super(props); >- this.state = { >- error: null, >- cities: [], >- } >- >- if ( props.cities ){ >- this.state.cities = props.cities >- } >- } >+const ListCity = ({ editCity, confirmDeleteCity }) => { >+ const [cities, setCities] = useState() > >- componentDidMount() { >+ useEffect(() => { > const apiUrl = '/api/v1/cities'; > >+ let r = [] > fetch(apiUrl) > .then(res => res.json()) > .then( > (result) => { >- this.setState({ >- cities: result >- }); >+ setCities(result) > }, > (error) => { >- this.setState({ error }); >+ console.log(error) > } > ) >- } >- >- render() { >- const { error, cities } = this.state; >- >- if(error) { >- return ("Error: {error.message}") >- } else if(cities.length) { >- return(<div> >- <h1>Cities</h1> >- <Table editCity={this.props.editCity} confirmDeleteCity={this.props.confirmDeleteCity} /> >- </div>) >- } else { >- return(<div><h1>Cities</h1><div className="dialog message">There are no cities defined.</div></div>) >- } >+ }, []) >+ >+ if(cities == null ) { >+ return (<div> >+ <h1>Cities</h1> >+ <div>Loading...</div> >+ </div>) >+ } else if(cities.length) { >+ return(<div> >+ <h1>Cities</h1> >+ <Table editCity={editCity} confirmDeleteCity={confirmDeleteCity} /> >+ </div>) >+ } else { >+ return(<div><h1>Cities</h1><div className="dialog message">There are no cities defined.</div></div>) > } > } > >-export default CityList; >+export default ListCity; >diff --git a/koha-tmpl/intranet-tmpl/prog/js/src/index.js b/koha-tmpl/intranet-tmpl/prog/js/src/index.js >index 06f48cac9a8..4d04b54b5bf 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/src/index.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/src/index.js >@@ -1,5 +1,5 @@ > import React from 'react'; > import ReactDOM from 'react-dom'; >-import App from './Cities'; >+import Cities from './components/Cities.js'; > >-ReactDOM.render(<App />, document.getElementById('cities_container')); >+ReactDOM.render(<Cities />, document.getElementById('cities_container')); >diff --git a/package.json b/package.json >index 0dc659a0b70..914ee44b538 100644 >--- a/package.json >+++ b/package.json >@@ -57,6 +57,8 @@ > "author": "", > "license": "GPL-3.0", > "devDependencies": { >- "@types/react": "^17.0.39" >+ "@types/react": "^17.0.39", >+ "vinyl-source-stream": "^2.0.0", >+ "watchify": "^4.0.0" > } > } >-- >2.25.1
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 30160
:
131031
| 131169