View | Details | Raw Unified | Return to bug 30160
Collapse All | Expand All

(-)a/admin/cities.pl (-68 lines)
Lines 28-36 use Koha::Cities; Link Here
28
28
29
my $input       = CGI->new;
29
my $input       = CGI->new;
30
my $city_name_filter = $input->param('city_name_filter') // q||;
30
my $city_name_filter = $input->param('city_name_filter') // q||;
31
my $cityid      = $input->param('cityid');
32
my $op          = $input->param('op') || 'list';
33
my @messages;
34
31
35
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
32
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
36
    {   template_name   => "admin/cities.tt",
33
    {   template_name   => "admin/cities.tt",
Lines 40-112 my ( $template, $loggedinuser, $cookie ) = get_template_and_user( Link Here
40
    }
37
    }
41
);
38
);
42
39
43
my $dbh = C4::Context->dbh;
44
if ( $op eq 'add_form' ) {
45
    my $city;
46
    if ($cityid) {
47
        $city = Koha::Cities->find($cityid);
48
    }
49
50
    $template->param( city => $city, );
51
} elsif ( $op eq 'add_validate' ) {
52
    my $city_name    = $input->param('city_name');
53
    my $city_state   = $input->param('city_state');
54
    my $city_zipcode = $input->param('city_zipcode');
55
    my $city_country = $input->param('city_country');
56
57
    if ($cityid) {
58
        my $city = Koha::Cities->find($cityid);
59
        $city->city_name($city_name);
60
        $city->city_state($city_state);
61
        $city->city_zipcode($city_zipcode);
62
        $city->city_country($city_country);
63
        eval { $city->store; };
64
        if ($@) {
65
            push @messages, { type => 'error', code => 'error_on_update' };
66
        } else {
67
            push @messages, { type => 'message', code => 'success_on_update' };
68
        }
69
    } else {
70
        my $city = Koha::City->new(
71
            {   city_name    => $city_name,
72
                city_state   => $city_state,
73
                city_zipcode => $city_zipcode,
74
                city_country => $city_country,
75
            }
76
        );
77
        eval { $city->store; };
78
        if ($@) {
79
            push @messages, { type => 'error', code => 'error_on_insert' };
80
        } else {
81
            push @messages, { type => 'message', code => 'success_on_insert' };
82
        }
83
    }
84
    $city_name = q||;
85
    $op        = 'list';
86
} elsif ( $op eq 'delete_confirm' ) {
87
    my $city = Koha::Cities->find($cityid);
88
    $template->param( city => $city, );
89
} elsif ( $op eq 'delete_confirmed' ) {
90
    my $city = Koha::Cities->find($cityid);
91
    my $deleted = eval { $city->delete; };
92
93
    if ( $@ or not $deleted ) {
94
        push @messages, { type => 'error', code => 'error_on_delete' };
95
    } else {
96
        push @messages, { type => 'message', code => 'success_on_delete' };
97
    }
98
    $op = 'list';
99
}
100
101
if ( $op eq 'list' ) {
102
    $template->param( cities_count => Koha::Cities->search->count );
103
}
104
105
$template->param(
40
$template->param(
106
    cityid      => $cityid,
107
    city_name_filter => $city_name_filter,
41
    city_name_filter => $city_name_filter,
108
    messages    => \@messages,
109
    op          => $op,
110
);
42
);
111
43
112
output_html_with_http_headers $input, $cookie, $template->output;
44
output_html_with_http_headers $input, $cookie, $template->output;
(-)a/gulpfile.js (-2 / +18 lines)
Lines 8-13 const fs = require('fs'); Link Here
8
const os = require('os');
8
const os = require('os');
9
const path = require('path');
9
const path = require('path');
10
const util = require('util');
10
const util = require('util');
11
const tap = require( "gulp-tap" );
12
const browserify = require( "browserify" );
13
const babelify = require( "babelify" );
11
14
12
const sass = require("gulp-sass");
15
const sass = require("gulp-sass");
13
const cssnano = require("gulp-cssnano");
16
const cssnano = require("gulp-cssnano");
Lines 81-88 function build() { Link Here
81
        })) // Append "-rtl" to the filename.
84
        })) // Append "-rtl" to the filename.
82
        .pipe(dest(css_base));
85
        .pipe(dest(css_base));
83
    }
86
    }
87
}
88
89
function build_js(){
90
91
    let bundler = browserify().transform(babelify, {presets: ["@babel/preset-env", "@babel/preset-react"]});
92
93
    return src( js_base + '/src/index.js' )
94
        .pipe( tap( file => {
95
            let base_start = file.path.indexOf( js_base );
96
            bundler.require( [ "react", "react-dom" ] );
97
            file.contents = bundler.add( file.path ).bundle();
98
        } ) )
99
        .pipe( dest( js_base+ "/built" ) );
84
100
85
    return stream;
86
}
101
}
87
102
88
const poTasks = {
103
const poTasks = {
Lines 358-363 function getLanguages () { Link Here
358
}
373
}
359
374
360
exports.build = build;
375
exports.build = build;
376
exports.build_js = build_js;
361
exports.css = css;
377
exports.css = css;
362
378
363
exports['po:create'] = parallel(...poTypes.map(type => series(poTasks[type].extract, poTasks[type].create)));
379
exports['po:create'] = parallel(...poTypes.map(type => series(poTasks[type].extract, poTasks[type].create)));
Lines 366-369 exports['po:extract'] = parallel(...poTypes.map(type => poTasks[type].extract)); Link Here
366
382
367
exports.default = function () {
383
exports.default = function () {
368
    watch(css_base + "/src/**/*.scss", series('css'));
384
    watch(css_base + "/src/**/*.scss", series('css'));
369
}
385
};
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/cities.tt (-188 / +3 lines)
Lines 69-203 Link Here
69
    <div class="row">
69
    <div class="row">
70
        <div class="col-sm-10 col-sm-push-2">
70
        <div class="col-sm-10 col-sm-push-2">
71
            <main>
71
            <main>
72
72
                <div id="cities_container"></div>
73
[% FOR m IN messages %]
74
    <div class="dialog [% m.type | html %]">
75
        [% SWITCH m.code %]
76
        [% CASE 'error_on_update' %]
77
            An error occurred when updating this city. Perhaps it already exists.
78
        [% CASE 'error_on_insert' %]
79
            An error occurred when adding this city. The city id might already exist.
80
        [% CASE 'error_on_delete' %]
81
            An error occurred when deleting this city. Check the logs.
82
        [% CASE 'success_on_update' %]
83
            City updated successfully.
84
        [% CASE 'success_on_insert' %]
85
            City added successfully.
86
        [% CASE 'success_on_delete' %]
87
            City deleted successfully.
88
        [% CASE 'already_exists' %]
89
            This city already exists.
90
        [% CASE %]
91
            [% m.code | html %]
92
        [% END %]
93
    </div>
94
[% END %]
95
96
[% IF op == 'add_form' %]
97
    [% IF city %]
98
        <h1>Modify a city</h1>
99
    [% ELSE %]
100
        <h1>New city</h1>
101
    [% END %]
102
103
    <form action="/cgi-bin/koha/admin/cities.pl" name="Aform" method="post" class="validated">
104
        <input type="hidden" name="op" value="add_validate" />
105
        <input type="hidden" name="cityid" value="[% city.cityid | html %]" />
106
107
        <fieldset class="rows">
108
            <ol>
109
                [% IF city %]
110
                    <li><span class="label">City ID: </span>[% city.cityid | html %]</li>
111
                [% END %]
112
                <li>
113
                    <label for="city_name" class="required">City: </label>
114
                    <input type="text" name="city_name" id="city_name" size="80" maxlength="100" value="[% city.city_name | html %]" required="required" class="required" /> <span class="required">Required</span>
115
                </li>
116
                <li>
117
                    <label for="city_state">State: </label>
118
                    <input type="text" name="city_state" id="city_state" size="80" maxlength="100" value="[% city.city_state | html %]" />
119
                </li>
120
                <li>
121
                    <label for="city_zipcode" class="required">ZIP/Postal code: </label>
122
                    <input type="text" name="city_zipcode" id="city_zipcode" size="20" maxlength="20" value="[% city.city_zipcode | html %]" required="required" class="required" /> <span class="required">Required</span>
123
                </li>
124
                <li>
125
                    <label for="city_country">Country: </label>
126
                    <input type="text" name="city_country" id="city_country" size="80" maxlength="100" value="[% city.city_country | html %]" />
127
                </li>
128
            </ol>
129
        </fieldset>
130
131
        <fieldset class="action">
132
            <input type="submit" value="Submit" />
133
            <a class="cancel" href="/cgi-bin/koha/admin/cities.pl">Cancel</a>
134
        </fieldset>
135
    </form>
136
[% END %]
137
138
[% IF op == 'delete_confirm' %]
139
    <div class="dialog alert">
140
        <h3>Delete city "[% city.city_name | html %]?"</h3>
141
        <table>
142
            <tr><th>City id</th>
143
                <td>[% city.cityid | html %]</td>
144
            </tr>
145
            <tr><th>City</th>
146
                <td>[% city.city_name | html %]</td>
147
            </tr>
148
            <tr><th>State</th>
149
                <td>[% city.city_state | html %]</td>
150
            </tr>
151
            <tr><th>ZIP/Postal code</th>
152
                <td>[% city.city_zipcode | html %]</td>
153
            </tr>
154
            <tr><th>Country</th>
155
                <td>[% city.city_country | html %]</td>
156
            </tr>
157
        </table>
158
        <form action="/cgi-bin/koha/admin/cities.pl" method="post">
159
            <input type="hidden" name="op" value="delete_confirmed" />
160
            <input type="hidden" name="cityid" value="[% city.cityid | html %]" />
161
            <button type="submit" class="approve"><i class="fa fa-fw fa-check"></i> Yes, delete</button>
162
        </form>
163
        <form action="/cgi-bin/koha/admin/cities.pl" method="get">
164
            <button type="submit" class="deny"><i class="fa fa-fw fa-remove"></i> No, do not delete</button>
165
        </form>
166
    </div>
167
[% END %]
168
169
[% IF op == 'list' %]
170
171
    <div id="toolbar" class="btn-toolbar">
172
        <a class="btn btn-default" id="newcity" href="/cgi-bin/koha/admin/cities.pl?op=add_form"><i class="fa fa-plus"></i> New city</a>
173
    </div>
174
175
    <h2>Cities</h2>
176
    [% IF city_name_filter %]
177
        Searching: [% city_name_filter | html %]
178
    [% END %]
179
180
    [% IF cities_count > 0 %]
181
        <div class="table_cities_table_controls"></div>
182
        <table id="table_cities">
183
            <thead>
184
                <tr>
185
                    <th>City ID</th>
186
                    <th>City</th>
187
                    <th>State</th>
188
                    <th>ZIP/Postal code</th>
189
                    <th>Country</th>
190
                    <th data-class-name="actions noExport">Actions</th>
191
                </tr>
192
            </thead>
193
        </table>
194
    [% ELSE %]
195
        <div class="dialog message">
196
            There are no cities defined. <a href="/cgi-bin/koha/admin/cities.pl?op=add_form">Create a new city</a>.
197
        </div>
198
    [% END %]
199
[% END %]
200
201
            </main>
73
            </main>
202
        </div> <!-- /.col-sm-10.col-sm-push-2 -->
74
        </div> <!-- /.col-sm-10.col-sm-push-2 -->
203
75
Lines 213-222 Link Here
213
    [% INCLUDE 'datatables.inc' %]
85
    [% INCLUDE 'datatables.inc' %]
214
    [% INCLUDE 'columns_settings.inc' %]
86
    [% INCLUDE 'columns_settings.inc' %]
215
    <script>
87
    <script>
216
217
        var columns_settings = [% TablesSettings.GetColumns( 'admin', 'cities', 'table_cities', 'json' ) | $raw %];
88
        var columns_settings = [% TablesSettings.GetColumns( 'admin', 'cities', 'table_cities', 'json' ) | $raw %];
218
        $(document).ready(function() {
89
        var cities_table_url = '/api/v1/cities?';
219
            var cities_table_url = '/api/v1/cities?';
220
90
221
        [% IF city_name_filter %]
91
        [% IF city_name_filter %]
222
            var city_name_filter = {
92
            var city_name_filter = {
Lines 226-287 Link Here
226
            };
96
            };
227
            cities_table_url += 'q='+ encodeURIComponent(JSON.stringify(city_name_filter));
97
            cities_table_url += 'q='+ encodeURIComponent(JSON.stringify(city_name_filter));
228
        [% END %]
98
        [% END %]
229
230
            var cities_table = $("#table_cities").kohaTable({
231
                "ajax": {
232
                    "url": cities_table_url
233
                },
234
                "order": [[ 1, "asc" ]],
235
                "columnDefs": [ {
236
                    "targets": [0,1,2,3,4],
237
                    "render": function (data, type, row, meta) {
238
                        if ( type == 'display' ) {
239
                            return data.escapeHtml();
240
                        }
241
                        return data;
242
                    }
243
                } ],
244
                "columns": [
245
                    {
246
                        "data": "city_id",
247
                        "searchable": true,
248
                        "orderable": true
249
                    },
250
                    {
251
                        "data": "name",
252
                        "searchable": true,
253
                        "orderable": true
254
                    },
255
                    {
256
                        "data": "state",
257
                        "searchable": true,
258
                        "orderable": true
259
                    },
260
                    {
261
                        "data": "postal_code",
262
                        "searchable": true,
263
                        "orderable": true
264
                    },
265
                    {
266
                        "data": "country",
267
                        "searchable": true,
268
                        "orderable": true
269
                    },
270
                    {
271
                        "data": function( row, type, val, meta ) {
272
273
                            var result = '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/cities.pl?op=add_form&amp;cityid='+ encodeURIComponent(row.city_id) +'"><i class="fa fa-pencil" aria-hidden="true"></i> '+_("Edit")+'</a>'+"\n";
274
                            result += '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/cities.pl?op=delete_confirm&amp;cityid='+ encodeURIComponent(row.city_id) +'"><i class="fa fa-trash" aria-hidden="true"></i> '+_("Delete")+'</a>';
275
                            return result;
276
277
                        },
278
                        "searchable": false,
279
                        "orderable": false
280
                    }
281
                ]
282
            }, columns_settings, 1);
283
284
        });
285
    </script>
99
    </script>
100
    [% Asset.js("js/built/index.js") | $raw %]
286
[% END %]
101
[% END %]
287
[% INCLUDE 'intranet-bottom.inc' %]
102
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/js/datatables.js (-4 / +4 lines)
Lines 505-514 jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { Link Here
505
    $.fn.kohaTable = function(options, columns_settings, add_filters, default_filters) {
505
    $.fn.kohaTable = function(options, columns_settings, add_filters, default_filters) {
506
        var settings = null;
506
        var settings = null;
507
507
508
        if ( add_filters ) {
509
            $(this).find('thead tr').clone(true).appendTo( $(this).find('thead') );
510
        }
511
512
        if(options) {
508
        if(options) {
513
            if(!options.criteria || ['contains', 'starts_with', 'ends_with', 'exact'].indexOf(options.criteria.toLowerCase()) === -1) options.criteria = 'contains';
509
            if(!options.criteria || ['contains', 'starts_with', 'ends_with', 'exact'].indexOf(options.criteria.toLowerCase()) === -1) options.criteria = 'contains';
514
            options.criteria = options.criteria.toLowerCase();
510
            options.criteria = options.criteria.toLowerCase();
Lines 769-776 jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { Link Here
769
765
770
        if ( add_filters ) {
766
        if ( add_filters ) {
771
            var table_dt = table.DataTable();
767
            var table_dt = table.DataTable();
768
769
            $(this).find('thead tr').clone().appendTo( $(this).find('thead') );
770
772
            $(this).find('thead tr:eq(1) th').each( function (i) {
771
            $(this).find('thead tr:eq(1) th').each( function (i) {
773
                var is_searchable = table_dt.settings()[0].aoColumns[i].bSearchable;
772
                var is_searchable = table_dt.settings()[0].aoColumns[i].bSearchable;
773
                $(this).removeClass('sorting');
774
                if ( is_searchable ) {
774
                if ( is_searchable ) {
775
                    var title = $(this).text();
775
                    var title = $(this).text();
776
                    var existing_search = table_dt.column(i).search();
776
                    var existing_search = table_dt.column(i).search();
(-)a/koha-tmpl/intranet-tmpl/prog/js/src/Cities.js (+161 lines)
Line 0 Link Here
1
import React, { Component } from 'react'
2
import { Container, Button, Alert } from 'react-bootstrap'
3
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
4
import { faPlus } from '@fortawesome/free-solid-svg-icons'
5
import CityList from './cities/CityList'
6
import AddCity from './cities/AddCity'
7
import DeleteCity from './cities/DeleteCity'
8
9
class App extends Component {
10
    constructor(props) {
11
        super(props)
12
        this.state = {
13
            op: 'list',
14
            error: null,
15
            message: null,
16
            city: {},
17
            cities: [],
18
        }
19
    }
20
21
    onCreate() {
22
        this.setState({ op: 'add', city: {}, error: null, message: null })
23
    }
24
25
    addCity = city => {
26
        let apiUrl
27
28
        apiUrl = '/api/v1/cities'
29
30
        const myHeaders = new Headers()
31
        myHeaders.append('Content-Type', 'application/json')
32
33
        let method = 'POST'
34
        if ( city.city_id ) {
35
            method = 'PUT'
36
            apiUrl += '/' + city.city_id
37
        }
38
        delete city.city_id
39
40
        const options = {
41
            method: method,
42
            body: JSON.stringify(city),
43
            myHeaders
44
        }
45
46
        fetch(apiUrl, options)
47
          .then(response => {
48
            if ( response.status == 200 ) {
49
                this.setState({
50
                    message: 'City updated',
51
                    error: null,
52
                    op: 'list',
53
                })
54
            } else if ( response.status == 201 ) {
55
                this.setState({
56
                    message: 'City created',
57
                    error: null,
58
                    op: 'list',
59
                })
60
            } else {
61
                this.setState({ error: response.message })
62
            }
63
          }, (error) => {
64
            this.setState({ error })
65
          })
66
    }
67
68
    editCity = city_id => {
69
70
        const apiUrl = '/api/v1/cities/' + city_id
71
72
        const options = {
73
          method: 'GET',
74
        }
75
76
        fetch(apiUrl, options)
77
          .then(res => res.json())
78
          .then(
79
            (result) => {
80
              this.setState({
81
                city: result,
82
                op: 'add',
83
                message: null,
84
                error: null,
85
              })
86
            }, (error) => {
87
              this.setState({ error })
88
            }
89
          )
90
    }
91
92
    confirmDeleteCity = city_id => {
93
        const apiUrl = '/api/v1/cities/' + city_id
94
95
        const options = {
96
          method: 'GET',
97
        }
98
99
        fetch(apiUrl, options)
100
          .then(res => res.json())
101
          .then(
102
            (result) => {
103
              this.setState({
104
                city: result,
105
                op: 'delete',
106
                message: null,
107
                error: null,
108
              })
109
            }, (error) => {
110
              this.setState({ error })
111
            }
112
          )
113
    }
114
115
    deleteCity = city_id => {
116
117
        const { cities } = this.state
118
        const apiUrl = '/api/v1/cities/' + city_id
119
120
        const options = {
121
          method: 'DELETE',
122
        }
123
124
        fetch(apiUrl, options)
125
          .then(response => {
126
            if ( response.status == 204 ) {
127
                this.setState({
128
                    message: 'City deleted',
129
                    error: null,
130
                    op: 'list',
131
                })
132
            } else {
133
                this.setState({ error: response.message })
134
            }
135
          }, (error) => {
136
              this.setState({ error })
137
          }
138
         )
139
    }
140
141
    cancel = event => {
142
        event.preventDefault()
143
        this.setState({op: 'list'})
144
    }
145
146
    render() {
147
148
        return (
149
          <div className="App">
150
              {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>}
151
              {this.state.message && <div><Alert key="message_info" variant="info" transition={false}>{this.state.message}</Alert></div>}
152
              {this.state.error && <div><Alert key="message_error" variant="warning" transition={false}>Error: {this.state.error.message}</Alert></div>}
153
              {this.state.op == 'list' && <CityList editCity={this.editCity} confirmDeleteCity={this.confirmDeleteCity} cities={this.state.cities} />}
154
              {this.state.op == 'add' && <AddCity onFormSubmit={this.addCity} onFormCancel={this.cancel} city={this.state.city} />}
155
              {this.state.op == 'delete' && <DeleteCity onFormSubmit={this.deleteCity} onFormCancel={this.cancel} city={this.state.city} />}
156
          </div>
157
        )
158
    }
159
}
160
161
export default App
(-)a/koha-tmpl/intranet-tmpl/prog/js/src/cities/AddCity.js (+121 lines)
Line 0 Link Here
1
import React from 'react';
2
import { Row, Form, Col, Button } from 'react-bootstrap';
3
4
class AddCity extends React.Component {
5
    constructor(props) {
6
        super(props);
7
        this.initialState = {
8
            city_id: '',
9
            name: '',
10
            state: '',
11
            postal_code: '',
12
            country: ''
13
        }
14
15
        if(props.city.city_id){
16
          this.state = props.city
17
        } else {
18
          this.state = this.initialState;
19
        }
20
21
        this.handleChange = this.handleChange.bind(this);
22
        this.handleSubmit = this.handleSubmit.bind(this);
23
        this.handleCancel = this.handleCancel.bind(this);
24
    }
25
26
    handleChange(event) {
27
        const name = event.target.name;
28
        const value = event.target.value;
29
30
        this.setState({
31
            [name]: value
32
        })
33
    }
34
35
    handleSubmit(event) {
36
        event.preventDefault();
37
        this.props.onFormSubmit(this.state);
38
    }
39
40
    handleCancel(event) {
41
        event.preventDefault();
42
        this.props.onFormCancel(event);
43
    }
44
45
    render() {
46
47
        let pageTitle;
48
        if(this.state.city_id) {
49
            pageTitle = <h2>Edit city</h2>
50
        } else {
51
            pageTitle = <h2>New city</h2>
52
        }
53
54
        return(
55
          <div>
56
            {pageTitle}
57
            <Form onSubmit={this.handleSubmit} className="validated">
58
            <fieldset className="rows">
59
              <Form.Group controlId="name" as={Row}>
60
                <Form.Label column sm={4} className="required">City:</Form.Label>
61
                <Col sm={8}>
62
                <Form.Control
63
                  type="text"
64
                  name="name"
65
                  value={this.state.name}
66
                  onChange={this.handleChange}
67
                  placeholder="City name"
68
                  required="required"/>
69
                  <span className="required">Required</span>
70
                  </Col>
71
              </Form.Group>
72
              <Form.Group controlId="state" as={Row}>
73
                <Form.Label column sm={4}>State:</Form.Label>
74
                <Col sm={8}>
75
                <Form.Control
76
                  type="text"
77
                  name="state"
78
                  value={this.state.state}
79
                  onChange={this.handleChange}
80
                  placeholder="City State"/>
81
                  </Col>
82
              </Form.Group>
83
              <Form.Group controlId="postal_code" as={Row}>
84
                <Form.Label column sm={4} className="required">ZIP/Postal code:</Form.Label>
85
                <Col sm={8}>
86
                <Form.Control
87
                  type="text"
88
                  name="postal_code"
89
                  value={this.state.postal_code}
90
                  onChange={this.handleChange}
91
                  placeholder="City postal code"
92
                  required="required"/>
93
                  <span className="required">Required</span>
94
                  </Col>
95
              </Form.Group>
96
              <Form.Group controlId="country" as={Row}>
97
                <Form.Label column sm={4}>Country:</Form.Label>
98
                <Col sm={8}>
99
                <Form.Control
100
                  type="text"
101
                  name="country"
102
                  value={this.state.country}
103
                  onChange={this.handleChange}
104
                  placeholder="City country"/>
105
                  </Col>
106
              </Form.Group>
107
              </fieldset>
108
              <fieldset className="action">
109
              <Form.Group>
110
                <Form.Control type="hidden" name="id" value={this.state.city_id} />
111
                <Button variant="default" type="submit">Submit</Button>
112
                <a href="#" onClick={(e) => this.handleCancel(e)}>Cancel</a>
113
              </Form.Group>
114
              </fieldset>
115
            </Form>
116
           </div>
117
        )
118
    }
119
}
120
121
export default AddCity;
(-)a/koha-tmpl/intranet-tmpl/prog/js/src/cities/CityList.js (+176 lines)
Line 0 Link Here
1
import React, { Component } from 'react';
2
import ReactDOM from 'react-dom';
3
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
4
import { faPencil, faTrash } from '@fortawesome/free-solid-svg-icons'
5
import { Button, Alert } from 'react-bootstrap';
6
7
class EditButton extends Component{
8
    constructor(props){
9
        super(props);
10
        this.handleClick = this.handleClick.bind(this);
11
    }
12
    handleClick(){
13
        this.props.handleClick(this.props.city_id);
14
    }
15
16
    render(){
17
        return (
18
            <Button variant="default" size="sm" onClick={this.handleClick}><FontAwesomeIcon icon={faPencil} size="xs" /> Edit</Button>
19
        )
20
    }
21
}
22
23
class DeleteButton extends Component{
24
    constructor(props){
25
        super(props);
26
        this.handleClick = this.handleClick.bind(this);
27
    }
28
    handleClick(){
29
        this.props.handleClick(this.props.city_id);
30
    }
31
32
    render(){
33
        return (
34
            <Button variant="default" size="sm" onClick={this.handleClick}><FontAwesomeIcon icon={faTrash} size="xs" /> Delete</Button>
35
        )
36
    }
37
}
38
39
class Table extends Component {
40
    constructor(props){
41
        super(props);
42
    }
43
44
    componentDidMount() {
45
46
        const editCity = this.props.editCity;
47
        const confirmDeleteCity = this.props.confirmDeleteCity;
48
        $(this.refs.main).kohaTable({
49
                "ajax": {
50
                    "url": cities_table_url,
51
                },
52
                "order": [[ 1, "asc" ]],
53
                "columnDefs": [ {
54
                    "targets": [0,1,2,3,4],
55
                    "render": function (data, type, row, meta) {
56
                        if ( type == 'display' ) {
57
                            return escape_str(data);
58
                        }
59
                        return data;
60
                    }
61
                } ],
62
                "columns": [
63
                    {
64
                        "title": __("City ID"),
65
                        "data": "city_id",
66
                        "searchable": true,
67
                        "orderable": true
68
                    },
69
                    {
70
                        "title": __("City"),
71
                        "data": "name",
72
                        "searchable": true,
73
                        "orderable": true
74
                    },
75
                    {
76
                        "title": __("State"),
77
                        "data": "state",
78
                        "searchable": true,
79
                        "orderable": true
80
                    },
81
                    {
82
                        "title": __("ZIP/Postal code"),
83
                        "data": "postal_code",
84
                        "searchable": true,
85
                        "orderable": true
86
                    },
87
                    {
88
                        "title": __("Country"),
89
                        "data": "country",
90
                        "searchable": true,
91
                        "orderable": true
92
                    },
93
                   {
94
                        "title": __("Actions"),
95
                        "data": function( row, type, val, meta ) {
96
                            return '<div class="actions" data-city_id="'+row.city_id+'"></div>';
97
                        },
98
                        "searchable": false,
99
                        "orderable": false
100
                    }
101
                ],
102
                drawCallback: function(settings){
103
                    $.each($(this).find(".actions"), function(index, e) {
104
                        ReactDOM.render(<span><EditButton city_id={$(e).data('city_id')} handleClick={editCity} /><DeleteButton city_id={$(e).data('city_id')} handleClick={confirmDeleteCity} /></span>, e);
105
                    });
106
                }
107
108
            }, columns_settings, 1);
109
    }
110
111
    componentWillUnmount(){
112
       $('.data-table-wrapper')
113
       .find('table')
114
       .DataTable()
115
       .destroy(true);
116
    }
117
118
    shouldComponentUpdate() {
119
        return true;
120
    }
121
122
    render() {
123
        return (
124
            <div>
125
                <table ref="main" />
126
            </div>);
127
    }
128
}
129
130
class CityList extends React.Component {
131
    constructor(props) {
132
        super(props);
133
        this.state = {
134
            error: null,
135
            cities: [],
136
        }
137
138
        if ( props.cities ){
139
            this.state.cities = props.cities
140
        }
141
    }
142
143
    componentDidMount() {
144
        const apiUrl = '/api/v1/cities';
145
146
        fetch(apiUrl)
147
            .then(res => res.json())
148
            .then(
149
                (result) => {
150
                    this.setState({
151
                        cities: result
152
                    });
153
                },
154
                (error) => {
155
                    this.setState({ error });
156
                }
157
            )
158
    }
159
160
    render() {
161
        const { error, cities } = this.state;
162
163
        if(error) {
164
            return ("Error: {error.message}")
165
        } else if(cities.length) {
166
            return(<div>
167
                    <h1>Cities</h1>
168
                    <Table editCity={this.props.editCity} confirmDeleteCity={this.props.confirmDeleteCity} />
169
                </div>)
170
        } else {
171
            return(<div><h1>Cities</h1><div className="dialog message">There are no cities defined.</div></div>)
172
        }
173
    }
174
}
175
176
export default CityList;
(-)a/koha-tmpl/intranet-tmpl/prog/js/src/cities/DeleteCity.js (+67 lines)
Line 0 Link Here
1
import React from 'react';
2
import { Row, Form, Col, Button } from 'react-bootstrap';
3
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
4
import { faCheck, faRemove } from '@fortawesome/free-solid-svg-icons'
5
6
class DeleteCity extends React.Component {
7
    constructor(props) {
8
        super(props);
9
        this.state = props.city;
10
11
        this.handleSubmit = this.handleSubmit.bind(this);
12
        this.handleCancel = this.handleCancel.bind(this);
13
    }
14
15
    handleSubmit(event) {
16
        event.preventDefault();
17
        this.props.onFormSubmit(this.state.city_id);
18
    }
19
    handleCancel(event) {
20
        event.preventDefault();
21
        this.props.onFormCancel(event);
22
    }
23
24
    render() {
25
26
        let pageTitle = <h2>Delete city</h2>
27
28
        return(
29
          <div>
30
           <Row>
31
            {pageTitle}
32
            <Form onSubmit={this.handleSubmit}>
33
              <fieldset className="rows">
34
                <h3>Delete city "{this.state.name}"?</h3>
35
                <Form.Group controlId="city_id" as={Row}>
36
                  <Form.Label column sm={4}>City id:</Form.Label>
37
                  <Col sm={8}>{this.state.city_id}</Col>
38
                </Form.Group>
39
                <Form.Group controlId="name" as={Row}>
40
                  <Form.Label column sm={4}>City:</Form.Label>
41
                  <Col sm={8}>{this.state.name}</Col>
42
                </Form.Group>
43
                <Form.Group controlId="state" as={Row}>
44
                  <Form.Label column sm={4}>State:</Form.Label>
45
                  <Col sm={8}>{this.state.state}</Col>
46
                </Form.Group>
47
                <Form.Group controlId="postal_code" as={Row}>
48
                  <Form.Label column sm={4}>ZIP/Postal code:</Form.Label>
49
                  <Col sm={8}>{this.state.postal_code}</Col>
50
                </Form.Group>
51
                <Form.Group controlId="country" as={Row}>
52
                  <Form.Label column sm={4}>Country:</Form.Label>
53
                  <Col sm={8}>{this.state.country}</Col>
54
                </Form.Group>
55
                </fieldset>
56
                <fieldset className="action">
57
                <Button variant="default" className="approve" type="submit"><FontAwesomeIcon icon={faCheck} /> Yes, Delete</Button>
58
                <Button variant="default" className="deny" onClick={(e) => this.handleCancel(e)}><FontAwesomeIcon icon={faRemove} /> No, do not remove</Button>
59
              </fieldset>
60
            </Form>
61
            </Row>
62
          </div>
63
        )
64
    }
65
}
66
67
export default DeleteCity;
(-)a/koha-tmpl/intranet-tmpl/prog/js/src/index.js (+5 lines)
Line 0 Link Here
1
import React from 'react';
2
import ReactDOM from 'react-dom';
3
import App from './Cities';
4
5
ReactDOM.render(<App />, document.getElementById('cities_container'));
(-)a/package.json (-3 / +23 lines)
Lines 7-13 Link Here
7
    "test": "test"
7
    "test": "test"
8
  },
8
  },
9
  "dependencies": {
9
  "dependencies": {
10
    "@babel/core": "^7.0.0-beta.3",
11
    "@babel/preset-env": "^7.0.0-beta.3",
12
    "@babel/preset-react": "^7.0.0-beta.3",
13
    "@fortawesome/fontawesome-svg-core": "^1.3.0",
14
    "@fortawesome/free-regular-svg-icons": "^6.0.0",
15
    "@fortawesome/free-solid-svg-icons": "^6.0.0",
16
    "@fortawesome/react-fontawesome": "^0.1.17",
17
    "@types/react-dom": "^17.0.11",
18
    "babel-core": "^7.0.0-beta.3",
19
    "babelify": "^10.0.0",
10
    "bootstrap": "^4.5.2",
20
    "bootstrap": "^4.5.2",
21
    "browserify": "^17.0.0",
11
    "gulp": "^4.0.2",
22
    "gulp": "^4.0.2",
12
    "gulp-autoprefixer": "^4.0.0",
23
    "gulp-autoprefixer": "^4.0.0",
13
    "gulp-concat-po": "^1.0.0",
24
    "gulp-concat-po": "^1.0.0",
Lines 17-26 Link Here
17
    "gulp-rtlcss": "^1.4.1",
28
    "gulp-rtlcss": "^1.4.1",
18
    "gulp-sass": "^3.1.0",
29
    "gulp-sass": "^3.1.0",
19
    "gulp-sourcemaps": "^2.6.1",
30
    "gulp-sourcemaps": "^2.6.1",
31
    "gulp-tap": "^1.0.1",
20
    "js-yaml": "^3.13.1",
32
    "js-yaml": "^3.13.1",
21
    "lodash": "^4.17.12",
33
    "lodash": "^4.17.12",
22
    "merge-stream": "^2.0.0",
34
    "merge-stream": "^2.0.0",
23
    "minimist": "^1.2.5"
35
    "minimist": "^1.2.5",
36
    "react": "^17.0.2",
37
    "react-bootstrap": "^2.1.2",
38
    "react-dom": "^17.0.2",
39
    "react-router": "^6.2.1",
40
    "react-router-dom": "^6.2.1",
41
    "react-script": "^2.0.5"
24
  },
42
  },
25
  "scripts": {
43
  "scripts": {
26
    "build": "node_modules/.bin/gulp build",
44
    "build": "node_modules/.bin/gulp build",
Lines 37-41 Link Here
37
    "js-yaml": "^3.13.1"
55
    "js-yaml": "^3.13.1"
38
  },
56
  },
39
  "author": "",
57
  "author": "",
40
  "license": "GPL-3.0"
58
  "license": "GPL-3.0",
59
  "devDependencies": {
60
    "@types/react": "^17.0.39"
61
  }
41
}
62
}
42
- 

Return to bug 30160