Passed
Push — master ( 0b01ae...6aca38 )
by Guangyu
08:43 queued 11s
created

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

Complexity

Total Complexity 11
Complexity/F 0

Size

Lines of Code 528
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

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