GladysProject/Gladys

View on GitHub
front/src/routes/integration/all/enedis-gateway/UsagePoints.jsx

Summary

Maintainability
D
1 day
Test Coverage
import { Component } from 'preact';
import { connect } from 'unistore/preact';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import localizedFormat from 'dayjs/plugin/localizedFormat';

import { Text } from 'preact-i18n';
import cx from 'classnames';
import update from 'immutability-helper';

dayjs.extend(relativeTime);
dayjs.extend(localizedFormat);

import { getDeviceParam } from '../../../../utils/device';
import { DEVICE_FEATURE_UNITS } from '../../../../../../server/utils/constants';
import { DEVICE_PARAMS } from './consts';
import EnedisPage from './EnedisPage';

const UsagePointDevice = ({
  device,
  language = 'fr',
  deviceIndex,
  updateDeviceParam,
  saveDevice,
  destroyDevice,
  syncs = []
}) => {
  const usagePointId = device.external_id.split(':')[1];

  const contractType = getDeviceParam(device, DEVICE_PARAMS.CONTRACT_TYPE);
  const pricePerKwh = getDeviceParam(device, DEVICE_PARAMS.PRICE_PER_KWH);
  const priceCurrency = getDeviceParam(device, DEVICE_PARAMS.PRICE_CURRENCY);
  const lastRefresh = getDeviceParam(device, DEVICE_PARAMS.LAST_REFRESH);
  const numberOfStates = getDeviceParam(device, DEVICE_PARAMS.NUMBER_OF_STATES);
  const mySyncs = syncs.filter(sync => sync.usage_point_id === usagePointId);

  let syncInProgress;
  if (mySyncs.length > 0) {
    const lastSync = mySyncs[0];
    if (lastSync.jobs_done < lastSync.jobs_total) {
      syncInProgress = lastSync;
      syncInProgress.progress = Math.round((lastSync.jobs_done / lastSync.jobs_total) * 100);
    }
  }

  const lastRefreshDate = lastRefresh
    ? dayjs(lastRefresh)
        .locale(language)
        .format('L LTS')
    : undefined;

  const updateContractType = e => {
    updateDeviceParam(deviceIndex, DEVICE_PARAMS.CONTRACT_TYPE, e.target.value);
  };

  const updatePricePerKwh = async e => {
    await updateDeviceParam(deviceIndex, DEVICE_PARAMS.PRICE_PER_KWH, e.target.value);
    await updateDeviceParam(deviceIndex, DEVICE_PARAMS.PRICE_CURRENCY, DEVICE_FEATURE_UNITS.EURO);
  };

  const save = () => {
    saveDevice(deviceIndex);
  };

  const destroy = () => {
    destroyDevice(deviceIndex);
  };

  return (
    <div class="col-md-6">
      <div class="card">
        <div class="card-header">
          <h3 class="card-title">
            <Text id="integration.enedis.usagePoints.usagePointName" />
          </h3>
        </div>
        <div class="card-body">
          <div class="form-group">
            <label>
              <Text id="integration.enedis.usagePoints.usagePointId" />
            </label>
            <input type="text" class="form-control" value={usagePointId} disabled />
          </div>
          {false && (
            <div class="form-group">
              <label>
                <Text id="integration.enedis.usagePoints.contractType" />
              </label>
              <select class="form-control" onChange={updateContractType} value={contractType}>
                <option value="base">
                  <Text id="integration.enedis.usagePoints.contracts.base" />
                </option>
                <option value="hc-hp">
                  <Text id="integration.enedis.usagePoints.contracts.hc-hp" />
                </option>
              </select>
            </div>
          )}
          {contractType === 'base' && (
            <div class="form-group">
              <label>
                <Text id="integration.enedis.usagePoints.pricePerKwh" />
              </label>
              <div class="input-group">
                <input type="text" class="form-control" value={pricePerKwh} onChange={updatePricePerKwh} />
                <div class="input-group-append">
                  <span class="input-group-text">
                    <Text id={`deviceFeatureUnitShort.${priceCurrency}`} />
                  </span>
                </div>
              </div>
            </div>
          )}
          {syncInProgress && (
            <div class="form-group">
              <label>
                <Text id="integration.enedis.usagePoints.gladysPlusRefreshProgressLabel" />
              </label>
              <div class="progress">
                <div
                  class="progress-bar bg-primary"
                  style={{
                    width: `${syncInProgress.progress}%`
                  }}
                  role="progressbar"
                  aria-valuenow={syncInProgress.progress}
                  aria-valuemin="0"
                  aria-valuemax="100"
                  aria-label={syncInProgress.progress}
                >
                  <span class="visually-hidden">{syncInProgress.progress}%</span>
                </div>
              </div>
              <div class="mt-2">
                <small>
                  <Text id="integration.enedis.usagePoints.gladysPlusRefreshProgressDescription" />
                </small>
              </div>
            </div>
          )}
          {(lastRefreshDate || numberOfStates) && (
            <div class="card">
              <div class="card-body">
                <h4>
                  <Text id="integration.enedis.usagePoints.statistics" />
                </h4>
                <p>
                  {lastRefreshDate && (
                    <span>
                      <b>
                        <Text id="integration.enedis.usagePoints.lastSyncLabel" />
                      </b>{' '}
                      {lastRefresh}
                      <br />
                    </span>
                  )}
                  {numberOfStates && (
                    <span>
                      <b>
                        <Text id="integration.enedis.usagePoints.numberOfStatesLabel" />
                      </b>{' '}
                      {numberOfStates}
                      <br />
                    </span>
                  )}
                </p>
              </div>
            </div>
          )}
          <button class="btn btn-success" onClick={save}>
            <Text id="integration.enedis.usagePoints.saveButton" />
          </button>
          <button class="btn btn-danger ml-2" onClick={destroy}>
            <Text id="integration.enedis.usagePoints.deleteButton" />
          </button>
        </div>
      </div>
    </div>
  );
};

const EnedisUsagePoints = ({
  errored,
  loading,
  usagePointsDevices,
  updateDeviceParam,
  saveDevice,
  destroyDevice,
  syncs,
  sync
}) => (
  <div class="card">
    <div class="card-header">
      <h1 class="card-title">
        <Text id="integration.enedis.usagePoints.title" />
      </h1>
      <div class="page-options d-flex">
        {usagePointsDevices && usagePointsDevices.length > 0 && (
          <button class="btn btn-primary" onClick={sync}>
            <i class="fe fe-refresh-cw" />{' '}
            <span class="d-none d-sm-inline-block ml-2">
              <Text id="integration.enedis.usagePoints.refreshLocal" />
            </span>
          </button>
        )}
      </div>
    </div>
    <div class="card-body">
      <div
        class={cx('dimmer', {
          active: loading
        })}
      >
        <div class="loader" />
        <div class="dimmer-content">
          {errored && (
            <p class="alert alert-danger">
              <Text id="integration.enedis.welcome.error" />
            </p>
          )}

          {usagePointsDevices && usagePointsDevices.length === 0 && (
            <p class="alert alert-info">
              <Text id="integration.enedis.usagePoints.emptyState" />
            </p>
          )}

          {usagePointsDevices && usagePointsDevices.length > 0 && (
            <p class="alert alert-primary">
              <Text id="integration.enedis.usagePoints.explanation" />
            </p>
          )}

          <div class="row">
            {usagePointsDevices &&
              usagePointsDevices.map((usagePointDevice, index) => (
                <UsagePointDevice
                  device={usagePointDevice}
                  deviceIndex={index}
                  updateDeviceParam={updateDeviceParam}
                  saveDevice={saveDevice}
                  destroyDevice={destroyDevice}
                  syncs={syncs}
                />
              ))}
          </div>
        </div>
      </div>
    </div>
  </div>
);

class EnedisWelcomePageComponent extends Component {
  getCurrentEnedisUsagePoints = async () => {
    try {
      const usagePointsDevices = await this.props.httpClient.get('/api/v1/service/enedis/device');
      this.setState({ usagePointsDevices });
    } catch (e) {
      console.error(e);
    }
  };
  updateDeviceParam = async (deviceIndex, deviceParam, value) => {
    const device = this.state.usagePointsDevices[deviceIndex];
    const deviceParamIndex = device.params.findIndex(p => p.name === deviceParam);
    let newUsagePointsDevices;
    if (deviceParamIndex !== -1) {
      newUsagePointsDevices = update(this.state.usagePointsDevices, {
        [deviceIndex]: {
          params: {
            [deviceParamIndex]: {
              value: {
                $set: value
              }
            }
          }
        }
      });
    } else {
      newUsagePointsDevices = update(this.state.usagePointsDevices, {
        [deviceIndex]: {
          params: {
            $push: [
              {
                name: deviceParam,
                value
              }
            ]
          }
        }
      });
    }
    await this.setState({ usagePointsDevices: newUsagePointsDevices });
  };
  getCurrentSync = async () => {
    try {
      const syncs = await this.props.session.gatewayClient.enedisGetSync();
      this.setState({ syncs });
    } catch (e) {
      console.error(e);
    }
  };
  sync = async () => {
    await this.setState({ loading: true });
    try {
      await this.props.httpClient.post('/api/v1/service/enedis/sync');
    } catch (e) {
      console.error(e);
    }
    await this.setState({ loading: false });
  };
  saveDevice = async deviceIndex => {
    await this.setState({ loading: true });
    try {
      const device = this.state.usagePointsDevices[deviceIndex];
      await this.props.httpClient.post('/api/v1/device', device);
    } catch (e) {
      console.error(e);
    }
    await this.setState({ loading: false });
  };
  destroyDevice = async deviceIndex => {
    await this.setState({ loading: true });
    try {
      const device = this.state.usagePointsDevices[deviceIndex];
      await this.props.httpClient.delete(`/api/v1/device/${device.selector}`);
      await this.getCurrentEnedisUsagePoints();
      await this.getCurrentSync();
    } catch (e) {
      console.error(e);
    }
    await this.setState({ loading: false });
  };
  init = async () => {
    await this.setState({ loading: true });
    await this.getCurrentEnedisUsagePoints();
    await this.getCurrentSync();
    await this.setState({ loading: false });
  };
  componentDidMount() {
    this.init();
    this.refreshInterval = setInterval(this.getCurrentSync, 30 * 1000);
  }
  componentWillUnmount() {
    clearInterval(this.refreshInterval);
  }
  render({ user }, { loading, errored, usagePointsDevices, syncs }) {
    return (
      <EnedisPage user={this.props.user}>
        <EnedisUsagePoints
          loading={loading}
          errored={errored}
          usagePointsDevices={usagePointsDevices}
          sync={this.sync}
          syncs={syncs}
          language={user.language}
          updateDeviceParam={this.updateDeviceParam}
          saveDevice={this.saveDevice}
          destroyDevice={this.destroyDevice}
        />
      </EnedisPage>
    );
  }
}

export default connect('user,session,httpClient', {})(EnedisWelcomePageComponent);