Passed
Push — master ( 833fbd...b08008 )
by Guangyu
04:25 queued 10s
created

src/components/MyEMS/Tenant/TenantBill.js   A

Complexity

Total Complexity 11
Complexity/F 0

Size

Lines of Code 542
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 11
eloc 475
mnd 11
bc 11
fnc 0
dl 0
loc 542
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
import React, { Fragment, useEffect, useState } from 'react';
2
import PropTypes from 'prop-types';
3
import {
4
  Breadcrumb,
5
  BreadcrumbItem,
6
  Button,
7
  ButtonGroup,
8
  Row,
9
  Col,
10
  Card,
11
  CardBody,
12
  CardFooter,
13
  Form,
14
  FormGroup,
15
  Input,
16
  Label,
17
  CustomInput,
18
  Table,
19
} from 'reactstrap';
20
import Loader from '../../common/Loader';
21
import ButtonIcon from '../../common/ButtonIcon';
22
import createMarkup from '../../../helpers/createMarkup';
23
import Datetime from 'react-datetime';
24
import moment from 'moment';
25
import Cascader from 'rc-cascader';
26
import { isIterableArray } from '../../../helpers/utils';
27
import logoInvoice from '../../../assets/img/logos/myems.png';
28
import { getCookieValue, createCookie } from '../../../helpers/utils';
29
import withRedirect from '../../../hoc/withRedirect';
30
import { withTranslation } from 'react-i18next';
31
import { toast } from 'react-toastify';
32
import { APIBaseURL } from '../../../config';
33
34
35
const formatCurrency = (number, currency) =>
36
  `${currency}${number.toFixed(2).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,')}`;
37
38
const ProductTr = ({ name, description, startdate, enddate, subtotalinput, unit, subtotalcost }) => {
39
  return (
40
    <tr>
41
      <td className="align-middle">
42
        <h6 className="mb-0 text-nowrap">{name}</h6>
43
        <p className="mb-0">{description}</p>
44
      </td>
45
      <td className="align-middle text-center">{startdate}</td>
46
      <td className="align-middle text-center">{enddate}</td>
47
      <td className="align-middle text-center">{subtotalinput.toFixed(3).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,')}</td>
48
      <td className="align-middle text-right">{unit}</td>
49
      <td className="align-middle text-right">{(subtotalcost).toFixed(2).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,')}</td>
50
    </tr>
51
  );
52
};
53
54
ProductTr.propTypes = {
55
  name: PropTypes.string.isRequired,
56
  description: PropTypes.string,
57
  startdate: PropTypes.string.isRequired,
58
  enddate: PropTypes.string.isRequired,
59
  subtotalinput: PropTypes.number.isRequired,
60
  unit: PropTypes.string.isRequired,
61
  subtotalcost: PropTypes.number.isRequired,
62
};
63
64
const InvoiceHeader = ({ institution, logo, address, t }) => (
65
  <Row className="align-items-center text-center mb-3">
66
    <Col sm={6} className="text-sm-left">
67
      <img src={logo} alt="invoice" width={150} />
68
    </Col>
69
    <Col className="text-sm-right mt-3 mt-sm-0">
70
      <h2 className="mb-3">{t('Payment Notice')}</h2>
71
      <h5>{institution}</h5>
72
      {address && <p className="fs--1 mb-0" dangerouslySetInnerHTML={createMarkup(address)} />}
73
    </Col>
74
    <Col xs={12}>
75
      <hr />
76
    </Col>
77
  </Row>
78
);
79
80
InvoiceHeader.propTypes = {
81
  institution: PropTypes.string.isRequired,
82
  logo: PropTypes.string.isRequired,
83
  address: PropTypes.string
84
};
85
86
const Invoice = ({ setRedirect, setRedirectUrl, t }) => {
87
  let current_moment = moment();
88
  useEffect(() => {
89
    let is_logged_in = getCookieValue('is_logged_in');
90
    let user_name = getCookieValue('user_name');
91
    let user_display_name = getCookieValue('user_display_name');
92
    let user_uuid = getCookieValue('user_uuid');
93
    let token = getCookieValue('token');
94
    if (is_logged_in === null || !is_logged_in) {
95
      setRedirectUrl(`/authentication/basic/login`);
96
      setRedirect(true);
97
    } else {
98
      //update expires time of cookies
99
      createCookie('is_logged_in', true, 1000 * 60 * 60 * 8);
100
      createCookie('user_name', user_name, 1000 * 60 * 60 * 8);
101
      createCookie('user_display_name', user_display_name, 1000 * 60 * 60 * 8);
102
      createCookie('user_uuid', user_uuid, 1000 * 60 * 60 * 8);
103
      createCookie('token', token, 1000 * 60 * 60 * 8);
104
    }
105
  });
106
  //State
107
  // Query Parameters
108
 
109
  const [selectedSpaceName, setSelectedSpaceName] = useState(undefined);
110
  const [selectedSpaceID, setSelectedSpaceID] = useState(undefined);
111
  const [tenantList, setTenantList] = useState([]);
112
  const [selectedTenant, setSelectedTenant] = useState(undefined);
113
  const [reportingPeriodBeginsDatetime, setReportingPeriodBeginsDatetime] = useState(current_moment.clone().subtract(1, 'months').startOf('month'));
114
  const [reportingPeriodEndsDatetime, setReportingPeriodEndsDatetime] = useState(current_moment.clone().subtract(1, 'months').endOf('month'));
115
  const [cascaderOptions, setCascaderOptions] = useState(undefined);
116
117
  // Submit button status
118
  const [submitButtonDisabled, setSubmitButtonDisabled] = useState(false);
119
  
120
  //Results
121
  const [invoice, setInvoice] = useState(undefined);
122
  const [subtotal, setSubtotal] = useState(0);
123
  const [taxRate, setTaxRate] = useState(0.00);
124
  const [tax, setTax] = useState(0);
125
  const [total, setTotal] = useState(0);
126
127
  useEffect(() => {
128
    let isResponseOK = false;
129
    fetch(APIBaseURL + '/spaces/tree', {
130
      method: 'GET',
131
      headers: {
132
        "Content-type": "application/json",
133
        "User-UUID": getCookieValue('user_uuid'),
134
        "Token": getCookieValue('token')
135
      },
136
      body: null,
137
138
    }).then(response => {
139
      console.log(response);
140
      if (response.ok) {
141
        isResponseOK = true;
142
      }
143
      return response.json();
144
    }).then(json => {
145
      console.log(json);
146
      if (isResponseOK) {
147
        // rename keys 
148
        json = JSON.parse(JSON.stringify([json]).split('"id":').join('"value":').split('"name":').join('"label":'));
149
        setCascaderOptions(json);
150
        setSelectedSpaceName([json[0]].map(o => o.label));
151
        setSelectedSpaceID([json[0]].map(o => o.value));
152
        // get Tenants by root Space ID
153
        let isResponseOK = false;
154
        fetch(APIBaseURL + '/spaces/' + [json[0]].map(o => o.value) + '/tenants', {
155
          method: 'GET',
156
          headers: {
157
            "Content-type": "application/json",
158
            "User-UUID": getCookieValue('user_uuid'),
159
            "Token": getCookieValue('token')
160
          },
161
          body: null,
162
163
        }).then(response => {
164
          if (response.ok) {
165
            isResponseOK = true;
166
          }
167
          return response.json();
168
        }).then(json => {
169
          if (isResponseOK) {
170
            json = JSON.parse(JSON.stringify([json]).split('"id":').join('"value":').split('"name":').join('"label":'));
171
            console.log(json);
172
            setTenantList(json[0]);
173
            if (json[0].length > 0) {
174
              setSelectedTenant(json[0][0].value);
175
              // enable submit button
176
              setSubmitButtonDisabled(false);
177
            } else {
178
              setSelectedTenant(undefined);
179
              // disable submit button
180
              setSubmitButtonDisabled(true);
181
            }
182
          } else {
183
            toast.error(json.description)
184
          }
185
        }).catch(err => {
186
          console.log(err);
187
        });
188
        // end of get Tenants by root Space ID
189
      } else {
190
        toast.error(json.description);
191
      }
192
    }).catch(err => {
193
      console.log(err);
194
    });
195
196
  }, []);
197
198
  const labelClasses = 'ls text-uppercase text-600 font-weight-semi-bold mb-0';
199
200
  let onSpaceCascaderChange = (value, selectedOptions) => {
201
    setSelectedSpaceName(selectedOptions.map(o => o.label).join('/'));
202
    setSelectedSpaceID(value[value.length - 1]);
203
204
    let isResponseOK = false;
205
    fetch(APIBaseURL + '/spaces/' + value[value.length - 1] + '/tenants', {
206
      method: 'GET',
207
      headers: {
208
        "Content-type": "application/json",
209
        "User-UUID": getCookieValue('user_uuid'),
210
        "Token": getCookieValue('token')
211
      },
212
      body: null,
213
214
    }).then(response => {
215
      if (response.ok) {
216
        isResponseOK = true;
217
      }
218
      return response.json();
219
    }).then(json => {
220
      if (isResponseOK) {
221
        json = JSON.parse(JSON.stringify([json]).split('"id":').join('"value":').split('"name":').join('"label":'));
222
        console.log(json)
223
        setTenantList(json[0]);
224
        if (json[0].length > 0) {
225
          setSelectedTenant(json[0][0].value);
226
          // enable submit button
227
          setSubmitButtonDisabled(false);
228
        } else {
229
          setSelectedTenant(undefined);
230
          // disable submit button
231
          setSubmitButtonDisabled(true);
232
        }
233
      } else {
234
        toast.error(json.description)
235
      }
236
    }).catch(err => {
237
      console.log(err);
238
    });
239
  }
240
241
242
  let onReportingPeriodBeginsDatetimeChange = (newDateTime) => {
243
    setReportingPeriodBeginsDatetime(newDateTime);
244
  }
245
246
  let onReportingPeriodEndsDatetimeChange = (newDateTime) => {
247
    setReportingPeriodEndsDatetime(newDateTime);
248
  }
249
250
  var getValidReportingPeriodBeginsDatetimes = function (currentDate) {
251
    return currentDate.isBefore(moment(reportingPeriodEndsDatetime, 'MM/DD/YYYY, hh:mm:ss a'));
252
  }
253
254
  var getValidReportingPeriodEndsDatetimes = function (currentDate) {
255
    return currentDate.isAfter(moment(reportingPeriodBeginsDatetime, 'MM/DD/YYYY, hh:mm:ss a'));
256
  }
257
258
  // Handler
259
  const handleSubmit = e => {
260
    e.preventDefault();
261
    console.log('handleSubmit');
262
    console.log(selectedSpaceID);
263
    console.log(selectedTenant);
264
    console.log(reportingPeriodBeginsDatetime.format('YYYY-MM-DDTHH:mm:ss'));
265
    console.log(reportingPeriodEndsDatetime.format('YYYY-MM-DDTHH:mm:ss'));
266
    
267
    // disable submit button
268
    setSubmitButtonDisabled(true);
269
270
    let isResponseOK = false;
271
    fetch(APIBaseURL + '/reports/tenantbill?' +
272
      'tenantid=' + selectedTenant +
273
      '&reportingperiodstartdatetime=' + reportingPeriodBeginsDatetime.format('YYYY-MM-DDTHH:mm:ss') +
274
      '&reportingperiodenddatetime=' + reportingPeriodEndsDatetime.format('YYYY-MM-DDTHH:mm:ss'), {
275
      method: 'GET',
276
      headers: {
277
        "Content-type": "application/json",
278
        "User-UUID": getCookieValue('user_uuid'),
279
        "Token": getCookieValue('token')
280
      },
281
      body: null,
282
283
    }).then(response => {
284
      if (response.ok) {
285
        isResponseOK = true;
286
      }
287
  
288
      // enable submit button
289
      setSubmitButtonDisabled(false);
290
291
      return response.json();
292
    }).then(json => {
293
      if (isResponseOK) {
294
        console.log(json);
295
        
296
        let productArray = []
297
        json['reporting_period']['names'].forEach((currentValue, index) => {
298
          let productItem = {}
299
          productItem['name'] = json['reporting_period']['names'][index];
300
          productItem['unit'] = json['reporting_period']['units'][index];
301
          productItem['startdate'] = reportingPeriodBeginsDatetime.format('YYYY-MM-DD');
302
          productItem['enddate'] = reportingPeriodEndsDatetime.format('YYYY-MM-DD');
303
          productItem['subtotalinput'] = json['reporting_period']['subtotals_input'][index];
304
          productItem['subtotalcost'] = json['reporting_period']['subtotals_cost'][index];
305
          productArray.push(productItem);
306
        });
307
308
        setInvoice({
309
          institution: json['tenant']['name'],
310
          logo: logoInvoice,
311
          address: json['tenant']['rooms'] + '<br />' + json['tenant']['floors'] + '<br />' + json['tenant']['buildings'],
312
          tax: 0.01,
313
          currency: json['reporting_period']['currency_unit'],
314
          user: {
315
            name: json['tenant']['name'],
316
            address: json['tenant']['rooms'] + '<br />' + json['tenant']['floors'] + '<br />' + json['tenant']['buildings'],
317
            email: json['tenant']['email'],
318
            cell: json['tenant']['phone']
319
          },
320
          summary: {
321
            invoice_no: current_moment.format('YYYYMMDDHHmmss'),
322
            lease_number: json['tenant']['lease_number'],
323
            invoice_date: current_moment.format('YYYY-MM-DD'),
324
            payment_due: current_moment.clone().add(7, 'days').format('YYYY-MM-DD'),
325
            amount_due: json['reporting_period']['total_cost']
326
          },
327
          products: productArray
328
        });
329
330
        setSubtotal(json['reporting_period']['total_cost']);
331
        
332
        setTax(json['reporting_period']['total_cost'] * taxRate);
333
        
334
        setTotal(json['reporting_period']['total_cost'] * (1.00 + taxRate));
335
        
336
      } else {
337
        toast.error(json.description)
338
      }
339
    }).catch(err => {
340
      console.log(err);
341
    });
342
  };
343
344
  return (
345
    <Fragment>
346
      <div>
347
        <Breadcrumb>
348
          <BreadcrumbItem>{t('Tenant Data')}</BreadcrumbItem><BreadcrumbItem active>{t('Tenant Bill')}</BreadcrumbItem>
349
        </Breadcrumb>
350
      </div>
351
      <Card className="bg-light mb-3">
352
        <CardBody className="p-3">
353
          <Form onSubmit={handleSubmit}>
354
            <Row form>
355
              <Col xs="auto">
356
                <FormGroup className="form-group">
357
                  <Label className={labelClasses} for="space">
358
                    {t('Space')}
359
                  </Label>
360
                  <br />
361
                  <Cascader options={cascaderOptions}
362
                    onChange={onSpaceCascaderChange}
363
                    changeOnSelect
364
                    expandTrigger="hover">
365
                    <Input value={selectedSpaceName || ''} readOnly />
366
                  </Cascader>
367
                </FormGroup>
368
              </Col>
369
              <Col xs="auto">
370
                <FormGroup>
371
                  <Label className={labelClasses} for="tenantSelect">
372
                    {t('Tenant')}
373
                  </Label>
374
                  <CustomInput type="select" id="tenantSelect" name="tenantSelect" onChange={({ target }) => setSelectedTenant(target.value)}
375
                  >
376
                    {tenantList.map((tenant, index) => (
377
                      <option value={tenant.value} key={tenant.value}>
378
                        {tenant.label}
379
                      </option>
380
                    ))}
381
                  </CustomInput>
382
                </FormGroup>
383
              </Col>
384
              <Col xs="auto">
385
                <FormGroup className="form-group">
386
                  <Label className={labelClasses} for="reportingPeriodBeginsDatetime">
387
                    {t('Reporting Period Begins')}
388
                  </Label>
389
                  <Datetime id='reportingPeriodBeginsDatetime'
390
                    value={reportingPeriodBeginsDatetime}
391
                    onChange={onReportingPeriodBeginsDatetimeChange}
392
                    isValidDate={getValidReportingPeriodBeginsDatetimes}
393
                    closeOnSelect={true} />
394
                </FormGroup>
395
              </Col>
396
              <Col xs="auto">
397
                <FormGroup className="form-group">
398
                  <Label className={labelClasses} for="reportingPeriodEndsDatetime">
399
                    {t('Reporting Period Ends')}
400
                  </Label>
401
                  <Datetime id='reportingPeriodEndsDatetime'
402
                    value={reportingPeriodEndsDatetime}
403
                    onChange={onReportingPeriodEndsDatetimeChange}
404
                    isValidDate={getValidReportingPeriodEndsDatetimes}
405
                    closeOnSelect={true} />
406
                </FormGroup>
407
              </Col>
408
              <Col xs="auto">
409
                <FormGroup>
410
                  <br></br>
411
                  <ButtonGroup id="submit">
412
                    <Button color="success" disabled={submitButtonDisabled} >{t('Submit')}</Button>
413
                  </ButtonGroup>
414
                </FormGroup>
415
              </Col>
416
            </Row>
417
          </Form>
418
        </CardBody>
419
      </Card>
420
      <Card className="mb-3">
421
        {invoice !== undefined &&
422
        <CardBody>
423
          <Row className="justify-content-between align-items-center">
424
            <Col md>
425
              <h5 className="mb-2 mb-md-0">{t('Lease Contract Number')}: {invoice.summary.lease_number}</h5>
426
            </Col>
427
            <Col xs="auto">
428
              <ButtonIcon color="falcon-default" size="sm" icon="arrow-down" className="mr-2 mb-2 mb-sm-0">
429
                {t('Download')} (.pdf)
430
              </ButtonIcon>
431
              <ButtonIcon color="falcon-default" size="sm" icon="print" className="mr-2 mb-2 mb-sm-0">
432
                {t('Print')}
433
              </ButtonIcon>
434
435
            </Col>
436
          </Row>
437
        </CardBody>
438
        }
439
      </Card>
440
441
      <Card>
442
        {invoice !== undefined &&
443
        <CardBody>
444
          <InvoiceHeader institution={invoice.institution} logo={invoice.logo} address={invoice.address} t={t} />
445
          <Row className="justify-content-between align-items-center">
446
            <Col>
447
              <h6 className="text-500">{t('Bill To')}</h6>
448
              <h5>{invoice.user.name}</h5>
449
              <p className="fs--1" dangerouslySetInnerHTML={createMarkup(invoice.user.address)} />
450
              <p className="fs--1">
451
                <a href={`mailto:${invoice.user.email}`}>{invoice.user.email}</a>
452
                <br />
453
                <a href={`tel:${invoice.user.cell.split('-').join('')}`}>{invoice.user.cell}</a>
454
              </p>
455
            </Col>
456
            <Col sm="auto" className="ml-auto">
457
              <div className="table-responsive">
458
                <Table size="sm" borderless className="fs--1">
459
                  <tbody>
460
                    <tr>
461
                      <th className="text-sm-right">{t('Bill Number')}:</th>
462
                      <td>{invoice.summary.invoice_no}</td>
463
                    </tr>
464
                    <tr>
465
                      <th className="text-sm-right">{t('Lease Contract Number')}:</th>
466
                      <td>{invoice.summary.lease_number}</td>
467
                    </tr>
468
                    <tr>
469
                      <th className="text-sm-right">{t('Bill Date')}:</th>
470
                      <td>{invoice.summary.invoice_date}</td>
471
                    </tr>
472
                    <tr>
473
                      <th className="text-sm-right">{t('Payment Due Date')}:</th>
474
                      <td>{invoice.summary.payment_due}</td>
475
                    </tr>
476
                    <tr className="alert-success font-weight-bold">
477
                      <th className="text-sm-right">{t('Amount Payable')}:</th>
478
                      <td>{formatCurrency(invoice.summary.amount_due, invoice.currency)}</td>
479
                    </tr>
480
                  </tbody>
481
                </Table>
482
              </div>
483
            </Col>
484
          </Row>
485
          <div className="table-responsive mt-4 fs--1">
486
            <Table striped className="border-bottom">
487
              <thead>
488
                <tr className="bg-primary text-white">
489
                  <th className="border-0">{t('Energy Category')}</th>
490
                  <th className="border-0 text-center">{t('Billing Period Start')}</th>
491
                  <th className="border-0 text-center">{t('Billing Period End')}</th>
492
                  <th className="border-0 text-center">{t('Quantity')}</th>
493
                  <th className="border-0 text-right">{t('Unit')}</th>
494
                  <th className="border-0 text-right">{t('Amount')}</th>
495
                </tr>
496
              </thead>
497
              <tbody>
498
                {isIterableArray(invoice.products) &&
499
                  invoice.products.map((product, index) => <ProductTr {...product} key={index} />)}
500
              </tbody>
501
            </Table>
502
          </div>
503
          <Row noGutters className="justify-content-end">
504
            <Col xs="auto">
505
              <Table size="sm" borderless className="fs--1 text-right">
506
                <tbody>
507
                  <tr>
508
                    <th className="text-900">{t('Subtotal')}:</th>
509
                    <td className="font-weight-semi-bold">{formatCurrency(subtotal, invoice.currency)}</td>
510
                  </tr>
511
                  <tr>
512
                    <th className="text-900">{t('VAT Output Tax')}:</th>
513
                    <td className="font-weight-semi-bold">{formatCurrency(tax, invoice.currency)}</td>
514
                  </tr>
515
                  <tr className="border-top">
516
                    <th className="text-900">{t('Total Amount Payable')}:</th>
517
                    <td className="font-weight-semi-bold">{formatCurrency(total, invoice.currency)}</td>
518
                  </tr>
519
                </tbody>
520
              </Table>
521
            </Col>
522
          </Row>
523
        </CardBody>
524
        }
525
        
526
        {//todo: get the bank account infomation from API
527
        /* <CardFooter className="bg-light">
528
          <p className="fs--1 mb-0">
529
            <strong>{t('Please make sure to pay on or before the payment due date above')}, {t('Send money to the following account')}:</strong><br />
530
            {t('Acount Name')}: MyEMS商场有限公司<br />
531
            {t('Bank Name')}: 中国银行股份有限公司北京王府井支行<br />
532
            {t('Bank Address')}: 中国北京市东城区王府井大街<br />
533
            {t('RMB Account')}: 1188228822882288<br />
534
          </p>
535
        </CardFooter> */}
536
      </Card>
537
    </Fragment>
538
  );
539
};
540
541
export default withTranslation()(withRedirect(Invoice));
542