<commit_before>"use strict";
module.exports = function (SIP) {

var RegisterContext;

RegisterContext = function (ua, options = {}) {
  this.options = {};

  this.logger = ua.getLogger('sip.registercontext');

  this.loadConfig(options);

  if (this.options.regId && !this.options.instanceId) {
    this.options.instanceId = SIP.Utils.newUUID();
  } else if (!this.options.regId && this.options.instanceId) {
    this.options.regId = 1;
  }

  this.options.params.to_uri = this.options.params.to_uri || ua.configuration.uri;
  this.options.params.to_displayName = this.options.params.to_displayName || ua.configuration.displayName;
  this.options.params.call_id = this.options.params.call_id || SIP.Utils.createRandomToken(22);
  this.options.params.cseq = this.options.params.cseq || Math.floor(Math.random() * 10000);

  /* If no 'registrarServer' is set use the 'uri' value without user portion. */
  if (!this.options.registrar) {
    let registrarServer = {};
    if (typeof ua.configuration.uri === 'object') {
      registrarServer = ua.configuration.uri.clone();
      registrarServer.user = null;
    } else {
      registrarServer = ua.configuration.uri;
    }
    this.options.registrar = registrarServer;
  }

  // Registration expires
  this.expires = this.options.expires;

  // Cseq
  this.cseq = this.options.params.cseq;

  // Contact header
  this.contact = ua.contact.toString();

  // Extends ClientContext
  SIP.Utils.augment(this, SIP.ClientContext, [ua, 'REGISTER', this.options.registrar, this.options]);

  this.registrationTimer = null;
  this.registrationExpiredTimer = null;

  // Set status
  this.registered = false;

  ua.on('transportCreated', function (transport) {
    transport.on('disconnected', this.onTransportDisconnected.bind(this));
  }.bind(this));
};

RegisterContext.prototype = Object.create({}, {
  /**
   * Configuration load.
   * @private
   * returns {Boolean}
   */
  loadConfig: {writable: true, value: function loadConfig(configuration) {
    let parameter, value, checkedValue;
    const settings = {
      expires: 600,
      extraContactHeaderParams: [],
      instanceId: null,
      params: {},
      regId: null,
      registrar: null,
    };

    const configCheck = this.getConfigurationCheck();

    // Check Mandatory parameters
    for(parameter in configCheck.mandatory) {
      if(!configuration.hasOwnProperty(parameter)) {
        throw new SIP.Exceptions.ConfigurationError(parameter);
      } else {
        value = configuration[parameter];
        checkedValue = configCheck.mandatory[parameter](value);
        if (checkedValue !== undefined) {
          settings[parameter] = checkedValue;
        } else {
          throw new SIP.Exceptions.ConfigurationError(parameter, value);
        }
      }
    }

    // Check Optional parameters
    for(parameter in configCheck.optional) {
      if(configuration.hasOwnProperty(parameter)) {
        value = configuration[parameter];

        // If the parameter value is an empty array, but shouldn't be, apply its default value.
        if (value instanceof Array && value.length === 0) { continue; }

        // If the parameter value is null, empty string, or undefined then apply its default value.
        if(value === null || value === '' || value === undefined) { continue; }
        // If it's a number with NaN value then also apply its default value.
        // NOTE: JS does not allow "value === NaN", the following does the work:
        else if(typeof(value) === 'number' && isNaN(value)) { continue; }

        checkedValue = configCheck.optional[parameter](value);
        if (checkedValue !== undefined) {
          settings[parameter] = checkedValue;
        } else {
          throw new SIP.Exceptions.ConfigurationError(parameter, value);
        }
      }
    }

    Object.assign(this.options, settings);

    this.logger.log('configuration parameters for RegisterContext after validation:');
    for(parameter in settings) {
      this.logger.log('· ' + parameter + ': ' + JSON.stringify(settings[parameter]));
    }

    return;

  }},

  getConfigurationCheck: {writable: true, value: function getConfigurationCheck () {
    return {
      mandatory: {
      },

      optional: {
        expires: function(expires) {
          if (SIP.Utils.isDecimal(expires)) {
            const value = Number(expires);
            if (value >= 0) {
              return value;
            }
          }
        },
        extraContactHeaderParams: function(extraContactHeaderParams) {
          if (extraContactHeaderParams instanceof Array) {
            return extraContactHeaderParams.filter((contactHeaderParam) => (typeof contactHeaderParam === 'string'));
          }
        },
        instanceId: function(instanceId) {
          if (typeof instanceId !== 'string') {
            return;
          }

          if ((/^uuid:/i.test(instanceId))) {
            instanceId = instanceId.substr(5);
          }

          if (SIP.Grammar.parse(instanceId, 'uuid') === -1) {
            return;
          } else {
            return instanceId;
          }
        },
        params: function(params) {
          if (typeof params === 'object') {
            return params;
          }
        },
        regId: function(regId) {
          if (SIP.Utils.isDecimal(regId)) {
            const value = Number(regId);
            if (value >= 0) {
              return value;
            }
          }
        },
        registrar: function(registrar) {
          if(typeof registrar !== 'string') {
            return;
          }

          if (!/^sip:/i.test(registrar)) {
            registrar = SIP.C.SIP + ':' + registrar;
          }
          const parsed = SIP.URI.parse(registrar);

          if(!parsed) {
            return;
          } else if(parsed.user) {
            return;
          } else {
            return parsed;
          }
        }
      }
    };
  }},

  /**
   * Helper Function to generate Contact Header
   * @private
   * returns {String}
   */
  generateContactHeader: {writable: true, value: function generateContactHeader(expires) {
    let contact = this.contact;
    if (this.options.regId && this.options.instanceId) {
      contact += ';reg-id=' + this.options.regId;
      contact += ';+sip.instance="<urn:uuid:' + this.options.instanceId + '>"';
    }

    if (this.options.extraContactHeaderParams) {
      this.options.extraContactHeaderParams.forEach((header) => {
        contact += ';' + header;
      });
    }

    contact += ';expires=' + (expires || '0');

    return contact;
  }},

  register: {writable: true, value: function register (options = {}) {
    // Handle Options
    this.options = SIP.Utils.defaultOptions(this.options, options);
    const extraHeaders = (this.options.extraHeaders || []).slice();

    extraHeaders.push('Contact: ' + this.generateContactHeader(this.expires));
    extraHeaders.push('Allow: ' + SIP.UA.C.ALLOWED_METHODS.toString());

    // Save original extraHeaders to be used in .close
    this.closeHeaders = this.options.closeWithHeaders ?
      (this.options.extraHeaders || []).slice() : [];

    this.receiveResponse = function(response) {
      var contact, expires,
        contacts = response.getHeaders('contact').length,
        cause;

      // Discard responses to older REGISTER/un-REGISTER requests.
      if(response.cseq !== this.cseq) {
        return;
      }

      // Clear registration timer
      if (this.registrationTimer !== null) {
        SIP.Timers.clearTimeout(this.registrationTimer);
        this.registrationTimer = null;
      }

      switch(true) {
        case /^1[0-9]{2}$/.test(response.status_code):
          this.emit('progress', response);
          break;
        case /^2[0-9]{2}$/.test(response.status_code):
          this.emit('accepted', response);

          if(response.hasHeader('expires')) {
            expires = response.getHeader('expires');
          }

          if (this.registrationExpiredTimer !== null) {
            SIP.Timers.clearTimeout(this.registrationExpiredTimer);
            this.registrationExpiredTimer = null;
          }

          // Search the Contact pointing to us and update the expires value accordingly.
          if (!contacts) {
            this.logger.warn('no Contact header in response to REGISTER, response ignored');
            break;
          }

          while(contacts--) {
            contact = response.parseHeader('contact', contacts);
            if(contact.uri.user === this.ua.contact.uri.user) {
              expires = contact.getParam('expires');
              break;
            } else {
              contact = null;
            }
          }

          if (!contact) {
            this.logger.warn('no Contact header pointing to us, response ignored');
            break;
          }

          if(!expires) {
            expires = this.expires;
          }

          // Re-Register before the expiration interval has elapsed.
          // For that, decrease the expires value. ie: 3 seconds
          this.registrationTimer = SIP.Timers.setTimeout(() => {
            this.registrationTimer = null;
            this.register(this.options);
          }, (expires * 1000) - 3000);
          this.registrationExpiredTimer = SIP.Timers.setTimeout(() => {
            this.logger.warn('registration expired');
            if (this.registered) {
              this.unregistered(null, SIP.C.causes.EXPIRES);
            }
          }, expires * 1000);

          //Save gruu values
          if (contact.hasParam('temp-gruu')) {
            this.ua.contact.temp_gruu = SIP.URI.parse(contact.getParam('temp-gruu').replace(/"/g,''));
          }
          if (contact.hasParam('pub-gruu')) {
            this.ua.contact.pub_gruu = SIP.URI.parse(contact.getParam('pub-gruu').replace(/"/g,''));
          }

          this.registered = true;
          this.emit('registered', response || null);
          break;
        // Interval too brief RFC3261 10.2.8
        case /^423$/.test(response.status_code):
          if(response.hasHeader('min-expires')) {
            // Increase our registration interval to the suggested minimum
            this.expires = response.getHeader('min-expires');
            // Attempt the registration again immediately
            this.register(this.options);
          } else { //This response MUST contain a Min-Expires header field
            this.logger.warn('423 response received for REGISTER without Min-Expires');
            this.registrationFailure(response, SIP.C.causes.SIP_FAILURE_CODE);
          }
          break;
        default:
          cause = SIP.Utils.sipErrorCause(response.status_code);
          this.registrationFailure(response, cause);
      }
    };

    this.onRequestTimeout = function() {
      this.registrationFailure(null, SIP.C.causes.REQUEST_TIMEOUT);
    };

    this.onTransportError = function() {
      this.registrationFailure(null, SIP.C.causes.CONNECTION_ERROR);
    };

    this.cseq++;
    this.request.cseq = this.cseq;
    this.request.setHeader('cseq', this.cseq + ' REGISTER');
    this.request.extraHeaders = extraHeaders;
    this.send();
  }},

  registrationFailure: {writable: true, value: function registrationFailure  (response, cause) {
    this.emit('failed', response || null, cause || null);
  }},

  onTransportDisconnected: {writable: true, value: function onTransportDisconnected () {
    this.registered_before = this.registered;
    if (this.registrationTimer !== null) {
      SIP.Timers.clearTimeout(this.registrationTimer);
      this.registrationTimer = null;
    }

    if (this.registrationExpiredTimer !== null) {
      SIP.Timers.clearTimeout(this.registrationExpiredTimer);
      this.registrationExpiredTimer = null;
    }

    if(this.registered) {
      this.unregistered(null, SIP.C.causes.CONNECTION_ERROR);
    }
  }},

  close: {writable: true, value: function close () {
    var options = {
      all: false,
      extraHeaders: this.closeHeaders
    };

    this.registered_before = this.registered;
    if (this.registered) {
      this.unregister(options);
    }
  }},

  unregister: {writable: true, value: function unregister (options) {
    var extraHeaders;

    options = options || {};

    if(!this.registered && !options.all) {
      this.logger.warn('Already unregistered, but sending an unregister anyways.');
    }

    extraHeaders = (options.extraHeaders || []).slice();

    this.registered = false;

    // Clear the registration timer.
    if (this.registrationTimer !== null) {
      SIP.Timers.clearTimeout(this.registrationTimer);
      this.registrationTimer = null;
    }

    if(options.all) {
      extraHeaders.push('Contact: *');
      extraHeaders.push('Expires: 0');
    } else {
      extraHeaders.push('Contact: '+ this.generateContactHeader(0));
    }

    this.receiveResponse = function(response) {
      var cause;

      switch(true) {
        case /^1[0-9]{2}$/.test(response.status_code):
          this.emit('progress', response);
          break;
        case /^2[0-9]{2}$/.test(response.status_code):
          this.emit('accepted', response);
          if (this.registrationExpiredTimer !== null) {
            SIP.Timers.clearTimeout(this.registrationExpiredTimer);
            this.registrationExpiredTimer = null;
          }
          this.unregistered(response);
          break;
        default:
          cause = SIP.Utils.sipErrorCause(response.status_code);
          this.unregistered(response,cause);
      }
    };

    this.onRequestTimeout = function() {
      // Not actually unregistered...
      //this.unregistered(null, SIP.C.causes.REQUEST_TIMEOUT);
    };

    this.cseq++;
    this.request.cseq = this.cseq;
    this.request.setHeader('cseq', this.cseq + ' REGISTER');
    this.request.extraHeaders = extraHeaders;

    this.send();
  }},

  unregistered: {writable: true, value: function unregistered (response, cause) {
    this.registered = false;
    this.emit('unregistered', response || null, cause || null);
  }}

});

SIP.RegisterContext = RegisterContext;
};
<commit_msg>Cleanup register context helper<commit_after>"use strict";
module.exports = function (SIP) {

var RegisterContext;

RegisterContext = function (ua, options = {}) {
  this.options = {};

  this.logger = ua.getLogger('sip.registercontext');

  this.loadConfig(options);

  if (this.options.regId && !this.options.instanceId) {
    this.options.instanceId = SIP.Utils.newUUID();
  } else if (!this.options.regId && this.options.instanceId) {
    this.options.regId = 1;
  }

  this.options.params.to_uri = this.options.params.to_uri || ua.configuration.uri;
  this.options.params.to_displayName = this.options.params.to_displayName || ua.configuration.displayName;
  this.options.params.call_id = this.options.params.call_id || SIP.Utils.createRandomToken(22);
  this.options.params.cseq = this.options.params.cseq || Math.floor(Math.random() * 10000);

  /* If no 'registrarServer' is set use the 'uri' value without user portion. */
  if (!this.options.registrar) {
    let registrarServer = {};
    if (typeof ua.configuration.uri === 'object') {
      registrarServer = ua.configuration.uri.clone();
      registrarServer.user = null;
    } else {
      registrarServer = ua.configuration.uri;
    }
    this.options.registrar = registrarServer;
  }

  // Registration expires
  this.expires = this.options.expires;

  // Cseq
  this.cseq = this.options.params.cseq;

  // Contact header
  this.contact = ua.contact.toString();

  // Extends ClientContext
  SIP.Utils.augment(this, SIP.ClientContext, [ua, 'REGISTER', this.options.registrar, this.options]);

  this.registrationTimer = null;
  this.registrationExpiredTimer = null;

  // Set status
  this.registered = false;

  ua.on('transportCreated', function (transport) {
    transport.on('disconnected', this.onTransportDisconnected.bind(this));
  }.bind(this));
};

RegisterContext.prototype = Object.create({}, {
  /**
   * Configuration load.
   * @private
   * returns {Boolean}
   */
  loadConfig: {writable: true, value: function loadConfig(configuration) {
    let parameter, value, checkedValue;
    const settings = {
      expires: 600,
      extraContactHeaderParams: [],
      instanceId: null,
      params: {},
      regId: null,
      registrar: null,
    };

    const configCheck = this.getConfigurationCheck();

    // Check Mandatory parameters
    for(parameter in configCheck.mandatory) {
      if(!configuration.hasOwnProperty(parameter)) {
        throw new SIP.Exceptions.ConfigurationError(parameter);
      } else {
        value = configuration[parameter];
        checkedValue = configCheck.mandatory[parameter](value);
        if (checkedValue !== undefined) {
          settings[parameter] = checkedValue;
        } else {
          throw new SIP.Exceptions.ConfigurationError(parameter, value);
        }
      }
    }

    // Check Optional parameters
    for(parameter in configCheck.optional) {
      if(configuration.hasOwnProperty(parameter)) {
        value = configuration[parameter];

        // If the parameter value is an empty array, but shouldn't be, apply its default value.
        if (value instanceof Array && value.length === 0) { continue; }

        // If the parameter value is null, empty string, or undefined then apply its default value.
        if(value === null || value === '' || value === undefined) { continue; }
        // If it's a number with NaN value then also apply its default value.
        // NOTE: JS does not allow "value === NaN", the following does the work:
        else if(typeof(value) === 'number' && isNaN(value)) { continue; }

        checkedValue = configCheck.optional[parameter](value);
        if (checkedValue !== undefined) {
          settings[parameter] = checkedValue;
        } else {
          throw new SIP.Exceptions.ConfigurationError(parameter, value);
        }
      }
    }

    Object.assign(this.options, settings);

    this.logger.log('configuration parameters for RegisterContext after validation:');
    for(parameter in settings) {
      this.logger.log('· ' + parameter + ': ' + JSON.stringify(settings[parameter]));
    }

    return;

  }},

  getConfigurationCheck: {writable: true, value: function getConfigurationCheck () {
    return {
      mandatory: {
      },

      optional: {
        expires: function(expires) {
          if (SIP.Utils.isDecimal(expires)) {
            const value = Number(expires);
            if (value >= 0) {
              return value;
            }
          }
        },
        extraContactHeaderParams: function(extraContactHeaderParams) {
          if (extraContactHeaderParams instanceof Array) {
            return extraContactHeaderParams.filter((contactHeaderParam) => (typeof contactHeaderParam === 'string'));
          }
        },
        instanceId: function(instanceId) {
          if (typeof instanceId !== 'string') {
            return;
          }

          if ((/^uuid:/i.test(instanceId))) {
            instanceId = instanceId.substr(5);
          }

          if (SIP.Grammar.parse(instanceId, 'uuid') === -1) {
            return;
          } else {
            return instanceId;
          }
        },
        params: function(params) {
          if (typeof params === 'object') {
            return params;
          }
        },
        regId: function(regId) {
          if (SIP.Utils.isDecimal(regId)) {
            const value = Number(regId);
            if (value >= 0) {
              return value;
            }
          }
        },
        registrar: function(registrar) {
          if(typeof registrar !== 'string') {
            return;
          }

          if (!/^sip:/i.test(registrar)) {
            registrar = SIP.C.SIP + ':' + registrar;
          }
          const parsed = SIP.URI.parse(registrar);

          if(!parsed) {
            return;
          } else if(parsed.user) {
            return;
          } else {
            return parsed;
          }
        }
      }
    };
  }},

  /**
   * Helper Function to generate Contact Header
   * @private
   * returns {String}
   */
  generateContactHeader: {writable: true, value: function generateContactHeader(expires = 0) {
    let contact = this.contact;
    if (this.options.regId && this.options.instanceId) {
      contact += ';reg-id=' + this.options.regId;
      contact += ';+sip.instance="<urn:uuid:' + this.options.instanceId + '>"';
    }

    if (this.options.extraContactHeaderParams) {
      this.options.extraContactHeaderParams.forEach((header) => {
        contact += ';' + header;
      });
    }

    contact += ';expires=' + expires;

    return contact;
  }},

  register: {writable: true, value: function register (options = {}) {
    // Handle Options
    this.options = SIP.Utils.defaultOptions(this.options, options);
    const extraHeaders = (this.options.extraHeaders || []).slice();

    extraHeaders.push('Contact: ' + this.generateContactHeader(this.expires));
    extraHeaders.push('Allow: ' + SIP.UA.C.ALLOWED_METHODS.toString());

    // Save original extraHeaders to be used in .close
    this.closeHeaders = this.options.closeWithHeaders ?
      (this.options.extraHeaders || []).slice() : [];

    this.receiveResponse = function(response) {
      var contact, expires,
        contacts = response.getHeaders('contact').length,
        cause;

      // Discard responses to older REGISTER/un-REGISTER requests.
      if(response.cseq !== this.cseq) {
        return;
      }

      // Clear registration timer
      if (this.registrationTimer !== null) {
        SIP.Timers.clearTimeout(this.registrationTimer);
        this.registrationTimer = null;
      }

      switch(true) {
        case /^1[0-9]{2}$/.test(response.status_code):
          this.emit('progress', response);
          break;
        case /^2[0-9]{2}$/.test(response.status_code):
          this.emit('accepted', response);

          if(response.hasHeader('expires')) {
            expires = response.getHeader('expires');
          }

          if (this.registrationExpiredTimer !== null) {
            SIP.Timers.clearTimeout(this.registrationExpiredTimer);
            this.registrationExpiredTimer = null;
          }

          // Search the Contact pointing to us and update the expires value accordingly.
          if (!contacts) {
            this.logger.warn('no Contact header in response to REGISTER, response ignored');
            break;
          }

          while(contacts--) {
            contact = response.parseHeader('contact', contacts);
            if(contact.uri.user === this.ua.contact.uri.user) {
              expires = contact.getParam('expires');
              break;
            } else {
              contact = null;
            }
          }

          if (!contact) {
            this.logger.warn('no Contact header pointing to us, response ignored');
            break;
          }

          if(!expires) {
            expires = this.expires;
          }

          // Re-Register before the expiration interval has elapsed.
          // For that, decrease the expires value. ie: 3 seconds
          this.registrationTimer = SIP.Timers.setTimeout(() => {
            this.registrationTimer = null;
            this.register(this.options);
          }, (expires * 1000) - 3000);
          this.registrationExpiredTimer = SIP.Timers.setTimeout(() => {
            this.logger.warn('registration expired');
            if (this.registered) {
              this.unregistered(null, SIP.C.causes.EXPIRES);
            }
          }, expires * 1000);

          //Save gruu values
          if (contact.hasParam('temp-gruu')) {
            this.ua.contact.temp_gruu = SIP.URI.parse(contact.getParam('temp-gruu').replace(/"/g,''));
          }
          if (contact.hasParam('pub-gruu')) {
            this.ua.contact.pub_gruu = SIP.URI.parse(contact.getParam('pub-gruu').replace(/"/g,''));
          }

          this.registered = true;
          this.emit('registered', response || null);
          break;
        // Interval too brief RFC3261 10.2.8
        case /^423$/.test(response.status_code):
          if(response.hasHeader('min-expires')) {
            // Increase our registration interval to the suggested minimum
            this.expires = response.getHeader('min-expires');
            // Attempt the registration again immediately
            this.register(this.options);
          } else { //This response MUST contain a Min-Expires header field
            this.logger.warn('423 response received for REGISTER without Min-Expires');
            this.registrationFailure(response, SIP.C.causes.SIP_FAILURE_CODE);
          }
          break;
        default:
          cause = SIP.Utils.sipErrorCause(response.status_code);
          this.registrationFailure(response, cause);
      }
    };

    this.onRequestTimeout = function() {
      this.registrationFailure(null, SIP.C.causes.REQUEST_TIMEOUT);
    };

    this.onTransportError = function() {
      this.registrationFailure(null, SIP.C.causes.CONNECTION_ERROR);
    };

    this.cseq++;
    this.request.cseq = this.cseq;
    this.request.setHeader('cseq', this.cseq + ' REGISTER');
    this.request.extraHeaders = extraHeaders;
    this.send();
  }},

  registrationFailure: {writable: true, value: function registrationFailure  (response, cause) {
    this.emit('failed', response || null, cause || null);
  }},

  onTransportDisconnected: {writable: true, value: function onTransportDisconnected () {
    this.registered_before = this.registered;
    if (this.registrationTimer !== null) {
      SIP.Timers.clearTimeout(this.registrationTimer);
      this.registrationTimer = null;
    }

    if (this.registrationExpiredTimer !== null) {
      SIP.Timers.clearTimeout(this.registrationExpiredTimer);
      this.registrationExpiredTimer = null;
    }

    if(this.registered) {
      this.unregistered(null, SIP.C.causes.CONNECTION_ERROR);
    }
  }},

  close: {writable: true, value: function close () {
    var options = {
      all: false,
      extraHeaders: this.closeHeaders
    };

    this.registered_before = this.registered;
    if (this.registered) {
      this.unregister(options);
    }
  }},

  unregister: {writable: true, value: function unregister (options) {
    var extraHeaders;

    options = options || {};

    if(!this.registered && !options.all) {
      this.logger.warn('Already unregistered, but sending an unregister anyways.');
    }

    extraHeaders = (options.extraHeaders || []).slice();

    this.registered = false;

    // Clear the registration timer.
    if (this.registrationTimer !== null) {
      SIP.Timers.clearTimeout(this.registrationTimer);
      this.registrationTimer = null;
    }

    if(options.all) {
      extraHeaders.push('Contact: *');
      extraHeaders.push('Expires: 0');
    } else {
      extraHeaders.push('Contact: '+ this.generateContactHeader(0));
    }

    this.receiveResponse = function(response) {
      var cause;

      switch(true) {
        case /^1[0-9]{2}$/.test(response.status_code):
          this.emit('progress', response);
          break;
        case /^2[0-9]{2}$/.test(response.status_code):
          this.emit('accepted', response);
          if (this.registrationExpiredTimer !== null) {
            SIP.Timers.clearTimeout(this.registrationExpiredTimer);
            this.registrationExpiredTimer = null;
          }
          this.unregistered(response);
          break;
        default:
          cause = SIP.Utils.sipErrorCause(response.status_code);
          this.unregistered(response,cause);
      }
    };

    this.onRequestTimeout = function() {
      // Not actually unregistered...
      //this.unregistered(null, SIP.C.causes.REQUEST_TIMEOUT);
    };

    this.cseq++;
    this.request.cseq = this.cseq;
    this.request.setHeader('cseq', this.cseq + ' REGISTER');
    this.request.extraHeaders = extraHeaders;

    this.send();
  }},

  unregistered: {writable: true, value: function unregistered (response, cause) {
    this.registered = false;
    this.emit('unregistered', response || null, cause || null);
  }}

});

SIP.RegisterContext = RegisterContext;
};
<|endoftext|>


<commit_before>'use strict'

let emitter = require("global-queue");
let TicketApi = require('resource-management-framework').TicketApi;

let moment = require('moment-timezone');

require('moment-range');

let Splitters = require('./Entities/Splitter.js');
let Filters = require('./Entities/Filter.js');
let Aggregators = require('./Entities/Aggregator.js');
let DataSource = require('./Entities/DataSource.js');
let Transform = require('./Entities/Transform.js');

let RDFcb = require("cbird-rdf").RD;

let db = new RDFcb();

class Reports {
	constructor() {
		this.emitter = emitter;
	}
	init(config) {
		let bucket_name = config.default_bucket || "rdf";
		DataSource.setDefaultBucket(db.bucket(bucket_name));

		return Promise.resolve(true);
	}
	launch() {
		return Promise.resolve(true);
	}

	//API
	actionBootstrap() {
		console.log('Reports');

		return Promise.resolve(true);
	}
	actionReady() {
		return Promise.resolve(true);
	}
	actionGetTable({
		table
	}) {
		var time = process.hrtime();
		let rows = table.params;
		let entity_name = table.entity;

		let source = DataSource.discover(entity_name, table.interval_field);

		source.setInterval(table.interval)
			.setDepartments(table.department);
		let group = Splitters.compose(entity_name, table.group);

		let fns = _.mapValues(rows, row => ({
			filter: Filters.compose(entity_name, row.filter),
			aggregator: Aggregators.get(entity_name, row.aggregator),
			transform: Transform.compose(entity_name, row.transform)
		}));

		let accumulator = {};
		let meta = {};

		let result = new Promise(function (resolve, reject) {
			source.parse((data) => {
				_.forEach(data, (a) => {

					let data_row = a.value;
					if (!data_row) return true;

					let group_index = group(data_row);

					_.forEach(rows, (row, index) => {
						let key = row.key;
						let meta_key = row.meta;
						let transform = _.get(fns, [index, 'transform']);
						let filter = _.get(fns, [index, 'filter']);

						if (transform) transform(data_row);

						if (!filter(data_row)) return true;

						let exported = key ? data_row[key] : 1;
						_.updateWith(accumulator, [group_index, index], (n) => n ? (n.push(exported) && n) : [exported], Object);
						if (meta_key) {
							let fields = _.pick(data_row, _.castArray(meta_key));
							_.updateWith(meta, [group_index, index], (n) => n ? (n.push(fields) && n) : [fields], Object);
						}
					})
				})

			}).finally(() => {
				let result = _.mapValues(accumulator, (group, group_index) => _.mapValues(group, (d, param_index) => {
					let value = fns[param_index].aggregator(d);
					return table.params[param_index].meta ? {
						value: value,
						meta: _.get(meta, [group_index, param_index])
					} : value;
				}));

				resolve(result);
			});
		});

		return result;
	}
	actionGetTableTemplate() {
		//@NOTE: get template from DB
		//return Template_Object
	}

}
module.exports = Reports;
<commit_msg>meta == all<commit_after>'use strict'

let emitter = require("global-queue");
let TicketApi = require('resource-management-framework').TicketApi;

let moment = require('moment-timezone');

require('moment-range');

let Splitters = require('./Entities/Splitter.js');
let Filters = require('./Entities/Filter.js');
let Aggregators = require('./Entities/Aggregator.js');
let DataSource = require('./Entities/DataSource.js');
let Transform = require('./Entities/Transform.js');

let RDFcb = require("cbird-rdf").RD;

let db = new RDFcb();

class Reports {
	constructor() {
		this.emitter = emitter;
	}
	init(config) {
		let bucket_name = config.default_bucket || "rdf";
		DataSource.setDefaultBucket(db.bucket(bucket_name));

		return Promise.resolve(true);
	}
	launch() {
		return Promise.resolve(true);
	}

	//API
	actionBootstrap() {
		console.log('Reports');

		return Promise.resolve(true);
	}
	actionReady() {
		return Promise.resolve(true);
	}
	actionGetTable({
		table
	}) {
		var time = process.hrtime();
		let rows = table.params;
		let entity_name = table.entity;

		let source = DataSource.discover(entity_name, table.interval_field);

		source.setInterval(table.interval)
			.setDepartments(table.department);
		let group = Splitters.compose(entity_name, table.group);

		let fns = _.mapValues(rows, row => ({
			filter: Filters.compose(entity_name, row.filter),
			aggregator: Aggregators.get(entity_name, row.aggregator),
			transform: Transform.compose(entity_name, row.transform)
		}));

		let accumulator = {};
		let meta = {};

		let result = new Promise(function (resolve, reject) {
			source.parse((data) => {
				_.forEach(data, (a) => {

					let data_row = a.value;
					if (!data_row) return true;

					let group_index = group(data_row);

					_.forEach(rows, (row, index) => {
						let key = row.key;
						let meta_key = row.meta;
						let transform = _.get(fns, [index, 'transform']);
						let filter = _.get(fns, [index, 'filter']);

						if (transform) transform(data_row);

						if (!filter(data_row)) return true;

						let exported = key ? data_row[key] : 1;
						_.updateWith(accumulator, [group_index, index], (n) => n ? (n.push(exported) && n) : [exported], Object);
						if (meta_key) {
							let fields = meta_key == 'all' ? data_row : _.pick(data_row, _.castArray(meta_key));
							_.updateWith(meta, [group_index, index], (n) => n ? (n.push(fields) && n) : [fields], Object);
						}
					})
				})

			}).finally(() => {
				let result = _.mapValues(accumulator, (group, group_index) => _.mapValues(group, (d, param_index) => {
					let value = fns[param_index].aggregator(d);
					return table.params[param_index].meta ? {
						value: value,
						meta: _.get(meta, [group_index, param_index])
					} : value;
				}));

				resolve(result);
			});
		});

		return result;
	}
	actionGetTableTemplate() {
		//@NOTE: get template from DB
		//return Template_Object
	}

}
module.exports = Reports;
<|endoftext|>


<commit_before>define(["cmwapi/cmwapi"], function(CommonMapApi) {
    var Feature = function(adapater, overlayManager) {
        var me = this;

        /**
         * Handler for plot feature request
         * @method feature.handlePlot
         * @param sender {String} the widget which made the plot feature request
         * @param data {Object|Object[]}
         * @param data.overlayId {String} The Id of the overlay to which the feature should be plotted.
         * @param data.featureId {String} The id to be given to the feature; unique to the overlayId.
         * @param data.name {String} The non-unique readable name to be given to the feature.
         * @param data.format {String} The format type of the feature data
         * @param data.feature The data for the feature to be plotted
         * @param [data.zoom] {Boolean} Whether or not the feature should be zoomed to when plotted.
         * @memberof! module:EsriAdapter#
         */
        me.handlePlot = function(sender, data) {
            if(data.length > 1) {
                for(var i = 0; i < data.length; i++) {

                }
            } else {

            }
        };
        CommonMapApi.feature.plot.addHandler(me.handlePlot);

        /**
         * Handler for plot url request
         * @method feature.handlePlot
         * @param sender {String} the widget which made the feature plot url request
         * @param data {Object|Object[]}
         * @param data.overlayId {String} The Id of the overlay to which the feature should be plotted.
         * @param data.featureId {String} The id to be given to the feature; unique to the overlayId.
         * @param data.name {String} The non-unique readable name to be given to the feature.
         * @param data.format {String} The format type of the feature data
         * @param data.url {String} The url for where the feature data could be retrieved
         * @param [data.zoom] {Boolean} Whether or not the feature should be zoomed to when plotted.
         * @memberof! module:EsriAdapter#
         */
        me.handlePlotUrl = function(sender, overlayId, featureId, name, format, url, params, zoom) {
            if(data.length > 1) {
                for(var i = 0; i < data.length; i++) {
                    OverlayManager.plotFeatureUrl(sender, overlayId, featureId, name, format, url, params, zoom);
                }
            } else {
                OverlayManager.plotFeatureUrl(sender, overlayId, featureId, name, format, url, params, zoom);
            }
        };
        CommonMapApi.feature.plot.url.addHandler(me.handlePlotUrl);

        /**
         * Handler for feature unplot request
         * @method feature.handleUnplot
         * @param sender {String} the widget which made the feature unplot request
         * @param overlayId {String} optional; the id for the overlay from which the feature should be
         *      unplotted. If not provided, the id of the sender will be assumed
         * @param featureId {String} The id of the feature to unplot
         * @memberof! module:EsriAdapter#
         */
        me.handleUnplot = function(sender, overlayId, featureId) {
            if(!overlayId) {
                overlayId = sender;
            }
            overlayManager.deleteFeature(overlayId, featureId);
        };
        CommonMapApi.feature.unplot.addHandler(me.handleUnplot);

        /**
         * Handler for request to hide feature
         * @method feature.handleHide
         * @param sender {String} the widget which made the feature hide request
         * @param overlayId {String} optional; the id for the overlay from which the feature should be
         *      hidden. If not provided, the id of the sender will be assumed
         * @param featureId {String} The id of the feature to hide
         * @memberof! module:EsriAdapter#
         */
        me.handleHide = function(sender, overlayId, featureId) {
            if(!overlayId) {
                overlayId = sender;
            }
            overlayManager.hideFeature(overlayId, featureId);
        };
        CommonMapApi.feature.hide.addHandler(me.handleHide);

        me.handleShow = function() {

        };
        CommonMapApi.feature.show.addHandler(me.handleShow);

        me.handleSelected = function() {

        };
        CommonMapApi.feature.selected.addHandler(me.handleSelected);

        me.handleUpdate = function() {

        };
        CommonMapApi.feature.update.addHandler(me.handlePlotUrl);
    }

    return Feature;
});<commit_msg>Fixing unimplemented functions and docs<commit_after>define(["cmwapi/cmwapi"], function(CommonMapApi) {
    var Feature = function(adapater, overlayManager) {
        var me = this;

        /**
         * Handler for plot feature request
         * @method feature.handlePlot
         * @param sender {String} the widget which made the plot feature request
         * @param data {Object|Object[]}
         * @param data.overlayId {String} The Id of the overlay to which the feature should be plotted.
         * @param data.featureId {String} The id to be given to the feature; unique to the overlayId.
         * @param data.name {String} The non-unique readable name to be given to the feature.
         * @param data.format {String} The format type of the feature data
         * @param data.feature The data for the feature to be plotted
         * @param [data.zoom] {Boolean} Whether or not the feature should be zoomed to when plotted.
         * @memberof! module:EsriAdapter#
         */
        me.handlePlot = function(sender, data) {
            if(data.length > 1) {
                var data_item;
                for(var i = 0; i < data.length; i++) {
                    data_item = data[i];
                    overlayManager.plotFeature(sender, data_item.overlayId, data_item.featureId, data_item.name,
                        data_item.format, data_item.feature, data_item.zoom);
                }
            } else {
                overlayManager.plotFeature(sender, data.overlayId, data.featureId, data.name,
                        data.format, data.feature, data.zoom);
            }
        };
        CommonMapApi.feature.plot.addHandler(me.handlePlot);

        /**
         * Handler for plot url request
         * @method feature.handlePlot
         * @param sender {String} the widget which made the feature plot url request
         * @param data {Object|Object[]}
         * @param data.overlayId {String} The Id of the overlay to which the feature should be plotted.
         * @param data.featureId {String} The id to be given to the feature; unique to the overlayId.
         * @param data.name {String} The non-unique readable name to be given to the feature.
         * @param data.format {String} The format type of the feature data
         * @param data.url {String} The url for where the feature data could be retrieved
         * @param [data.zoom] {Boolean} Whether or not the feature should be zoomed to when plotted.
         * @memberof! module:EsriAdapter#
         */
        me.handlePlotUrl = function(sender, data) {
            if(data.length > 1) {
                for(var i = 0; i < data.length; i++) {
                    OverlayManager.plotFeatureUrl(sender, overlayId, featureId, name, format, url, params, zoom);
                }
            } else {
                OverlayManager.plotFeatureUrl(sender, overlayId, featureId, name, format, url, params, zoom);
            }
        };
        CommonMapApi.feature.plot.url.addHandler(me.handlePlotUrl);

        /**
         * Handler for feature unplot request
         * @method feature.handleUnplot
         * @param sender {String} the widget which made the feature unplot request
         * @param data {Object|Object[]}
         * @param data.overlayId {String} optional; the id for the overlay from which the feature should be
         *      unplotted. If not provided, the id of the sender will be assumed
         * @param data.featureId {String} The id of the feature to unplot
         * @memberof! module:EsriAdapter#
         */
        me.handleUnplot = function(sender, data) {
            if(data.length > 1) {
                var data_item;
                for(var i = 0; i < data.length; i++) {
                    data_item = data[i];
                    overlayManager.deleteFeature(data_item.overlayId, data_item.featureId);
                }
            } else {
                overlayManager.deleteFeature(data.overlayId, data.featureId);
            }
        };
        CommonMapApi.feature.unplot.addHandler(me.handleUnplot);

        /**
         * Handler for request to hide feature
         * @method feature.handleHide
         * @param sender {String} the widget which made the feature hide request
         * @param data {Object|Object[]}
         * @param data.overlayId {String} optional; the id for the overlay from which the feature should be
         *      hidden. If not provided, the id of the sender will be assumed
         * @param data.featureId {String} The id of the feature to hide
         * @memberof! module:EsriAdapter#
         */
        me.handleHide = function(sender, data) {
            if(data.length > 1) {
                var data_item;
                for(var i = 0; i < data.length; i++) {
                    data_item = data[i];
                    overlayManager.hideFeature(data_item.overlayId, data_item.featureId);
                }
            } else {
                overlayManager.hideFeature(data.overlayId, data.featureId);
            }

        };
        CommonMapApi.feature.hide.addHandler(me.handleHide);

        /**
         * Handler for request to show feature
         * @param sender {String} The id of the widget making the request to show the feature
         * @param data {Object|Object[]}
         * @param data.overlayId {String} The id of the overlay to which the feature to show belongs
         * @param data.featureId {Stirng} The id of the feature which should be shown
         */
        me.handleShow = function(sender, data) {
            if(data.length > 1) {
                var data_item;
                for(var i = 0; i < data.length; i++) {
                    data_item = data[i];
                    overlayManager.showFeature(data_item.overlayId, data_item.featureId);
                }
            } else {
                overlayManager.showFeature(data.overlayId, data.featureId);
            }
        };
        CommonMapApi.feature.show.addHandler(me.handleShow);

        /**
         * Handler for a given feature being selected
         * //FIXME
         */
        me.handleSelected = function() {
            //zoom to the feature on the map
        };
        CommonMapApi.feature.selected.addHandler(me.handleSelected);

        /**
         * Handler for request to update a feature
         * @param Sender {String} The id of the widgets making the request to update the feature
         * @param data {Object|Object[]}
         * @param data.overlayId {String} The id of the overlay for which the feature to be updated belongs.
         * @param data.featureId {String} The id of the feature to be updated; unique to the given overlayId
         * @param [data.name] {String} the optional name to be set for the feature; If not provided, the name will not be changed.
         * @param [data.newOverlayId] {String} The optional id of the new overlay for which the feature should belong. If not
         *      provided the parent overlay will not be changed.
         */
        me.handleUpdate = function() {
            if(data.length > 1) {
                var data_item;
                for(var i = 0; i < data.length; i++) {
                    data_item = data[i];
                    overlayManager.hideFeature(data_item.overlayId, data_item.featureId, data_item.name, data_item.newOverlayId);
                }
            } else {
                overlayManager.hideFeature(data.overlayId, data.featureId, data.name, data.newOverlayId);
            }
        };
        CommonMapApi.feature.update.addHandler(me.handleUpdate);
    }

    return Feature;
});<|endoftext|>


<commit_before>import $ from 'jquery';
import Entity from './Entity';
import Component from '../components/Component';

function loadScript(url) {
	return $.getScript(url);
}

const apiLoaded = loadScript('https://www.youtube.com/iframe_api');

class Youtube extends Entity {
	constructor(player, options) {
		super(player, options);
		this._paused = true;

		this.ytPlayer = {
			getDuration : () => {
				return 0;
			},
			getCurrentTime : () => 0
		}

		this.element.on('click', this.onClick.bind(this));
		this.element.on('dblclick', this.onDblclick.bind(this));
	}

	onClick(event) {
		this.trigger('click');
	}

	onDblclick() {
		this.trigger('dblclick');
	}

	get currentTime() {
		return this.ytPlayer.getCurrentTime();
	}

	set currentTime(value) {
		this.ytPlayer.seekTo(value);
	}

	get duration() {
		return this.ytPlayer ? this.ytPlayer.getDuration() : NaN;
	}

	get paused() {
		return this._paused;
	}
	set paused(value) {
		this._paused = value;
	}

	get rate() {
		return this.ytPlayer.getPlaybackRate();
	}

	set rate(value) {
		this.ytPlayer.setPlaybackRate(value);
		this.trigger('ratechange');
	}

	get volume() {
		return this.ytPlayer ? this.ytPlayer.getVolume() / 100.0 : 1;
	}

	set volume(value) {
		this.ytPlayer.setVolume(value * 100);

		this.trigger('volumechange');

	}

	supportsFullScreen() {
		return true;
	}

	play() {
		this.ytPlayer.playVideo();
	}

	pause() {
		this.ytPlayer.pauseVideo();
	}

	init() {
		return apiLoaded
			.then(() => this.initYTPlayer())
	}

	createElement() {
		this.element = $('<div />')
			.addClass('leplayer__youtube-wrapper');
		this.youtubeElement = $('<div />')
			.addClass('leplayer__youtube');
		this.blocker = $('<div />')
			.addClass('leplayer__youtube-blocker');

		return this.element
			.append(this.blocker)
			.append(this.youtubeElement);
	}

	initYTPlayer() {
		const dfd = $.Deferred();
		const globalOptions = this.player.options;
		let ytOptions = {
			controls : 0,
			modestbranding : 1,
			rel : 0,
			showinfo : 0,
			loop : globalOptions.loop ? 1 : 0,
			autoplay : globalOptions.autoplay ? 1 : 0,
			disablekb : 0,
			fs : 0,
			time : 0
		};

		YT.ready(() => {
			this.options.ctx.replaceWith(this.element);
			this.ytPlayer = new YT.Player(this.youtubeElement[0], {
				videoId : globalOptions.youtubeVideoId,
				width : globalOptions.width || '100%',
				playerVars : ytOptions,
				events : {
					onReady : () => dfd.resolve(),
					onStateChange : this.onPlayerStateChange.bind(this)

				}
			})
		})
		return dfd.promise();
	}

	onPlayerStateChange(e) {
		const state = e.data;
		if(state === this._state) {
			return;
		}

		this._state = state;
		switch(state) {
			case -1:
				this.trigger('loadstart');
				this.trigger('loadedmetadata');
				this.trigger('durationchange');
				this.trigger('ratechange');
				break;

			case YT.PlayerState.ENDED:
				this.trigger('ended');
				break;

			case YT.PlayerState.PLAYING:
				this.trigger('timeupdate');
				this.trigger('durationchange');
				this.trigger('playing');
				this.trigger('play');
				this.paused = false;
				break;

			case YT.PlayerState.PAUSED:
				this.trigger('canplay');
				this.trigger('pause');
				this.paused = true;
				break;

			case YT.PlayerState.BUFFERING:
				this.trigger('timeupdate');
				this.trigger('waiting');
				break;
		}

	}
}

Component.registerComponent('Youtube', Youtube);
export default Youtube;
<commit_msg>[#LPLR-241]: Correct rate change<commit_after>import $ from 'jquery';
import Entity from './Entity';
import Component from '../components/Component';

function loadScript(url) {
	return $.getScript(url);
}

const apiLoaded = loadScript('https://www.youtube.com/iframe_api');

class Youtube extends Entity {
	constructor(player, options) {
		super(player, options);
		this._paused = true;

		this.ytPlayer = {
			getDuration : () => {
				return 0;
			},
			getCurrentTime : () => 0
		}

		this.element.on('click', this.onClick.bind(this));
		this.element.on('dblclick', this.onDblclick.bind(this));
	}

	onClick(event) {
		this.trigger('click');
	}

	onDblclick() {
		this.trigger('dblclick');
	}

	get currentTime() {
		return this.ytPlayer.getCurrentTime();
	}

	set currentTime(value) {
		this.ytPlayer.seekTo(value);
	}

	get duration() {
		return this.ytPlayer ? this.ytPlayer.getDuration() : NaN;
	}

	get paused() {
		return this._paused;
	}
	set paused(value) {
		this._paused = value;
	}

	get rate() {
		return this.ytPlayer.getPlaybackRate();
	}

	set rate(value) {
		this.ytPlayer.setPlaybackRate(value);
		this.trigger('ratechange');
	}

	get volume() {
		return this.ytPlayer ? this.ytPlayer.getVolume() / 100.0 : 1;
	}

	set volume(value) {
		this.ytPlayer.setVolume(value * 100);

		this.trigger('volumechange');

	}

	supportsFullScreen() {
		return true;
	}

	play() {
		this.ytPlayer.playVideo();
	}

	pause() {
		this.ytPlayer.pauseVideo();
	}

	init() {
		return apiLoaded
			.then(() => this.initYTPlayer())
	}

	createElement() {
		this.element = $('<div />')
			.addClass('leplayer__youtube-wrapper');
		this.youtubeElement = $('<div />')
			.addClass('leplayer__youtube');
		this.blocker = $('<div />')
			.addClass('leplayer__youtube-blocker');

		return this.element
			.append(this.blocker)
			.append(this.youtubeElement);
	}

	initYTPlayer() {
		const dfd = $.Deferred();
		const globalOptions = this.player.options;
		let ytOptions = {
			controls : 0,
			modestbranding : 1,
			rel : 0,
			showinfo : 0,
			loop : globalOptions.loop ? 1 : 0,
			autoplay : globalOptions.autoplay ? 1 : 0,
			disablekb : 0,
			fs : 0,
			time : 0
		};

		YT.ready(() => {
			this.options.ctx.replaceWith(this.element);
			this.ytPlayer = new YT.Player(this.youtubeElement[0], {
				videoId : globalOptions.youtubeVideoId,
				width : globalOptions.width || '100%',
				playerVars : ytOptions,
				events : {
					onReady : () => dfd.resolve(),
					onStateChange : this.onYTPStateChange.bind(this),
					onPlaybackRateChange : this.onYTPRateChange.bind(this)
				}
			})
		})
		return dfd.promise();
	}

	onYTPRateChange(e) {
		this.trigger('ratechange');
	}

	onYTPStateChange(e) {
		const state = e.data;
		if(state === this._state) {
			return;
		}

		this._state = state;
		switch(state) {
			case -1:
				this.trigger('loadstart');
				this.trigger('loadedmetadata');
				this.trigger('durationchange');
				this.trigger('ratechange');
				break;

			case YT.PlayerState.ENDED:
				this.trigger('ended');
				break;

			case YT.PlayerState.PLAYING:
				this.trigger('timeupdate');
				this.trigger('durationchange');
				this.trigger('playing');
				this.trigger('play');
				this.paused = false;
				break;

			case YT.PlayerState.PAUSED:
				this.trigger('canplay');
				this.trigger('pause');
				this.paused = true;
				break;

			case YT.PlayerState.BUFFERING:
				this.trigger('timeupdate');
				this.trigger('waiting');
				break;
		}

	}
}

Component.registerComponent('Youtube', Youtube);
export default Youtube;
<|endoftext|>


<commit_before>(function () {
  'use strict';
  // Polls controller
  angular
    .module('polls')
    .controller('PollInputController', PollInputController);

  PollInputController.$inject = [
    '$scope',
    '$state',
    '$stateParams',
    'PollsService',
    'OptsService',
    'Socket',
    'Action',
    'Constants',
    'Storages',
    'Notifications'
  ];

  function PollInputController(
    $scope,
    $state,
    $stateParams,
    PollsService,
    Opts,
    Socket,
    Action,
    Constants,
    Storages,
    Notifications
  ) {
    var vm = this;
    vm.form = {};
    // Biến tạm lưu option
    vm.tmp_opt = {};

    onPrepare();

    // Lấy id của poll trong đường dẫn để request API
    function onPrepare() {
      if (!$stateParams.pollId) {
        vm.poll = new PollsService();
        onCreate();
      } else {
        Action.loadPollById($stateParams.pollId).then(_poll => {
          vm.poll = _poll;
          onCreate();
        });
      }
    }

    function onCreate() {
      vm.bk_poll = _.clone(vm.poll);
      vm.opts = vm.poll.opts || [];
      if (vm.poll._id) {
        if (!vm.poll.isCurrentUserOwner && !$scope.isAdmin) {
          $state.go('home');
          $scope.handleShowMessage('MS_CM_AUTH_ERROR', true);
          return;
        }
        $scope.handleChangePageTitle(vm.poll.title);
        vm.poll.close = vm.poll.close ? moment(vm.poll.close).utc() : vm.poll.close;
        vm.isClosed = moment(vm.poll.close).utc().isAfter(new moment().utc());
        // Lắng nghe các request từ server socket
        prepareSocketListener();
      } else {
        // Kiểm tra có poll lưu trong storage hay không
        var poll = JSON.parse(Storages.get_local(Constants.storages.draft_poll, JSON.stringify({})));
        vm.poll = _.extend(vm.poll, poll);
      }
      // Kiểm tra thông báo
      prepareParamNotification();
    }

    /**
     * PREPARE
     */
    function prepareSocketListener() {
      if (!Socket.socket) {
        Socket.connect();
      }
      Socket.emit('subscribe_poll', {
        pollId: vm.poll._id,
        userId: $scope.user._id
      });
      Socket.on('poll_delete', res => {
        $scope.handleShowMessage('LB_POLLS_DELETED', true);
        $state.go('polls.list');
      });
      Socket.on('opts_request', res => {
        Opts.get({ optId: res }, _opt => {
          vm.opts.push(_opt);
        });
      });
      $scope.$on('$destroy', function () {
        Socket.emit('unsubscribe', {
          pollId: vm.poll._id,
          userId: $scope.user._id
        });
        Socket.removeListener('poll_delete');
        Socket.removeListener('opts_request');
      });
    }
    function prepareParamNotification() {
      if ($stateParams.notif) {
        Notifications.markReadNotif($stateParams.notif);
      }
    }

    /**
     * HANDLES
     */
    // Lưu poll
    vm.handleSavePoll = isValid => {
      if (!isValid) {
        $scope.$broadcast('show-errors-check-validity', 'vm.form.pollForm');
        $scope.handleShowMessage('LB_POLL_SAVE_ERROR', true);
        return false;
      }

      if (!handleValidateCloseDate()) {
        $scope.handleShowMessage('LB_POLLS_CLOSE_INVALID', true);
        return;
      }
      if (!vm.poll.isPublic) {
        // Gọi function show dialog từ scope cha
        $scope.handleShowConfirm({
          content: 'LB_POLLS_PRIVATE_SAVE',
          type: 1,
          button: 'LB_SAVE'
        }, confirm => {
          handleSave();
        });
      } else {
        handleSave();
      }
      function handleSave() {
        vm.poll.opts = vm.opts;
        Action.savePoll(vm.poll)
          .then(res => {
            Storages.remove_local(Constants.storages.draft_poll);
            $state.go('polls.view', { pollId: res.slug });
          })
          .catch(err => {
            $scope.handleShowMessage(err.message, true);
          });
      }
    };
    // Hủy nhập poll
    vm.handleDiscard = handleDiscard;
    function handleDiscard() {
      if (angular.equals(vm.poll, vm.bk_poll)) {
        handle_discard();
      } else {
        // Gọi function show dialog từ scope cha
        $scope.handleShowConfirm({
          content: 'LB_POLLS_CONFIRM_DISCARD',
          type: 2,
          button: 'LB_DISCARD'
        }, confirm => {
          handle_discard();
        });
      }
      function handle_discard() {
        if (vm.poll._id) {
          $state.go('polls.view', { pollId: vm.poll.slug });
        } else {
          $state.go('home');
        }
      }
    }
    // Gọi màn hình nhập thông tin options
    vm.handleStartInputOption = handleStartInputOption;
    vm.isFocusOptionInput = false;
    function handleStartInputOption(opt) {
      vm.tmp_opt = (opt) ? angular.clone(opt) : { poll: vm.poll._id, title: '', body: '', status: 1 };
      angular.element('body').toggleClass('aside-panel-open');
      vm.isFocusOptionInput = true;
    }
    // Xóa option
    vm.handleRemoveOption = handleRemoveOption;
    function handleRemoveOption(opt) {
      // Gọi function show dialog từ scope cha
      $scope.handleShowConfirm({
        content: 'LB_POLLS_CONFIRM_DELETE_OPT',
        type: 3,
        button: 'LB_DELETE'
      }, confirm => {
        handle_delete();
      });

      function handle_delete() {
        vm.opts = _.without(vm.opts, opt);
        if (opt._id) {
          var _opt = new Opts(opt);
          _opt.$remove(() => {
            Socket.emit('opts_update', { pollId: vm.poll._id });
          });
        }
      }
    }
    // Đồng ý đề xuất của thành viên
    vm.handleApproveOption = handleApproveOption;
    function handleApproveOption(opt) {
      // Gọi function show dialog từ scope cha
      $scope.handleShowConfirm({
        content: 'LB_POLLS_CONFIRM_APPROVE',
        type: 1,
        button: 'LB_APPROVE'
      }, confirm => {
        handle_approve();
      });
      function handle_approve() {
        opt.status = 1;
        var _opt = new Opts({ _id: opt._id, status: opt.status });
        _opt.$update(() => {
          Socket.emit('opts_update', { pollId: vm.poll._id });
        });
      }
    }
    // Từ chối đề xuất
    vm.handleRejectOption = handleRejectOption;
    function handleRejectOption(opt) {
      // Gọi function show dialog từ scope cha
      $scope.handleShowConfirm({
        content: 'LB_POLLS_CONFIRM_REJECT',
        type: 2,
        button: 'LB_REJECT'
      }, confirm => {
        handle_reject();
      });
      function handle_reject() {
        opt.status = 3;
        var _opt = new Opts({ _id: opt._id, status: opt.status });
        _opt.$update(() => {
        });
      }
    }
    // Lưu option
    vm.handleSaveOption = handleSaveOption;
    function handleSaveOption(isValid) {
      if (!isValid) {
        $scope.$broadcast('show-errors-check-validity', 'vm.form.optForm');
        return false;
      }
      // Kiểm tra poll đã tồn tại
      if (vm.tmp_opt._id) {
        // Update vào option cũ
        var opt = _.findWhere(vm.opts, { _id: vm.tmp_opt._id });
        opt = _.extend(opt, vm.tmp_opt);
      } else {
        // Nếu chưa có option nào giống với option mới thì add vào list
        if (!_.contains(vm.opts, vm.tmp_opt)) {
          vm.opts.push(_.clone(vm.tmp_opt));
        } else {
          $scope.handleShowMessage('LB_OPTION_DUPLICATE', true);
        }
      }
      vm.tmp_opt = {};
      $scope.$broadcast('show-errors-reset', 'vm.form.optForm');
      angular.element('body').removeClass('aside-panel-open');
      vm.isFocusOptionInput = false;
    }
    // Hiển thị full màn hình
    vm.handleShowFullOption = handleShowFullOption;
    function handleShowFullOption() {
      let aside = angular.element('.aside-panel')[0];
      angular.element(aside).toggleClass('full');
      angular.element('#aside-panel-full-toggle').find('i').toggleClass('r180');
    }
    // Xóa thông tin cài đặt ngày close
    vm.handleClearCloseDate = handleClearCloseDate;
    function handleClearCloseDate() {
      delete vm.poll.close;
    }
    // Kiểm tra ngày đóng poll hợp lệ hay không
    function handleValidateCloseDate() {
      if (!vm.poll.close) return true;
      if (vm.poll._id && vm.isClosed) return true;
      return moment(vm.poll.close).utc().isAfter(new moment().utc());
    }
  }
})();
<commit_msg>Commited from HaNhatLenh at Sat, Oct 21, 2017 10:05:39 PM<commit_after>(function () {
  'use strict';
  // Polls controller
  angular
    .module('polls')
    .controller('PollInputController', PollInputController);

  PollInputController.$inject = [
    '$scope',
    '$state',
    '$stateParams',
    'PollsService',
    'OptsService',
    'Socket',
    'Action',
    'Constants',
    'Storages',
    'Notifications'
  ];

  function PollInputController(
    $scope,
    $state,
    $stateParams,
    PollsService,
    Opts,
    Socket,
    Action,
    Constants,
    Storages,
    Notifications
  ) {
    var vm = this;
    vm.form = {};
    // Biến tạm lưu option
    vm.tmp_opt = {};

    onPrepare();

    // Lấy id của poll trong đường dẫn để request API
    function onPrepare() {
      if (!$stateParams.pollId) {
        vm.poll = new PollsService();
        onCreate();
      } else {
        Action.loadPollById($stateParams.pollId).then(_poll => {
          vm.poll = _poll;
          onCreate();
        });
      }
    }

    function onCreate() {
      vm.bk_poll = _.clone(vm.poll);
      vm.opts = vm.poll.opts || [];
      if (vm.poll._id) {
        if (!vm.poll.isCurrentUserOwner && !$scope.isAdmin) {
          $state.go('home');
          $scope.handleShowMessage('MS_CM_AUTH_ERROR', true);
          return;
        }
        $scope.handleChangePageTitle(vm.poll.title);
        vm.poll.close = vm.poll.close ? moment(vm.poll.close).utc() : vm.poll.close;
        vm.isClosed = moment(vm.poll.close).utc().isAfter(new moment().utc());
        // Lắng nghe các request từ server socket
        prepareSocketListener();
      } else {
        // Kiểm tra có poll lưu trong storage hay không
        var poll = JSON.parse(Storages.get_local(Constants.storages.draft_poll, JSON.stringify({})));
        console.log(poll);
        vm.poll = _.extend(vm.poll, poll);
      }
      // Kiểm tra thông báo
      prepareParamNotification();
    }

    /**
     * PREPARE
     */
    function prepareSocketListener() {
      if (!Socket.socket) {
        Socket.connect();
      }
      Socket.emit('subscribe_poll', {
        pollId: vm.poll._id,
        userId: $scope.user._id
      });
      Socket.on('poll_delete', res => {
        $scope.handleShowMessage('LB_POLLS_DELETED', true);
        $state.go('polls.list');
      });
      Socket.on('opts_request', res => {
        Opts.get({ optId: res }, _opt => {
          vm.opts.push(_opt);
        });
      });
      $scope.$on('$destroy', function () {
        Socket.emit('unsubscribe', {
          pollId: vm.poll._id,
          userId: $scope.user._id
        });
        Socket.removeListener('poll_delete');
        Socket.removeListener('opts_request');
      });
    }
    function prepareParamNotification() {
      if ($stateParams.notif) {
        Notifications.markReadNotif($stateParams.notif);
      }
    }

    /**
     * HANDLES
     */
    // Lưu poll
    vm.handleSavePoll = isValid => {
      if (!isValid) {
        $scope.$broadcast('show-errors-check-validity', 'vm.form.pollForm');
        $scope.handleShowMessage('LB_POLL_SAVE_ERROR', true);
        return false;
      }

      if (!handleValidateCloseDate()) {
        $scope.handleShowMessage('LB_POLLS_CLOSE_INVALID', true);
        return;
      }
      if (!vm.poll.isPublic) {
        // Gọi function show dialog từ scope cha
        $scope.handleShowConfirm({
          content: 'LB_POLLS_PRIVATE_SAVE',
          type: 1,
          button: 'LB_SAVE'
        }, confirm => {
          handleSave();
        });
      } else {
        handleSave();
      }
      function handleSave() {
        vm.poll.opts = vm.opts;
        Action.savePoll(vm.poll)
          .then(res => {
            Storages.remove_local(Constants.storages.draft_poll);
            $state.go('polls.view', { pollId: res.slug });
          })
          .catch(err => {
            $scope.handleShowMessage(err.message, true);
          });
      }
    };
    // Hủy nhập poll
    vm.handleDiscard = handleDiscard;
    function handleDiscard() {
      if (angular.equals(vm.poll, vm.bk_poll)) {
        handle_discard();
      } else {
        // Gọi function show dialog từ scope cha
        $scope.handleShowConfirm({
          content: 'LB_POLLS_CONFIRM_DISCARD',
          type: 2,
          button: 'LB_DISCARD'
        }, confirm => {
          handle_discard();
        });
      }
      function handle_discard() {
        if (vm.poll._id) {
          $state.go('polls.view', { pollId: vm.poll.slug });
        } else {
          $state.go('home');
        }
      }
    }
    // Gọi màn hình nhập thông tin options
    vm.handleStartInputOption = handleStartInputOption;
    vm.isFocusOptionInput = false;
    function handleStartInputOption(opt) {
      vm.tmp_opt = (opt) ? angular.clone(opt) : { poll: vm.poll._id, title: '', body: '', status: 1 };
      angular.element('body').toggleClass('aside-panel-open');
      vm.isFocusOptionInput = true;
    }
    // Xóa option
    vm.handleRemoveOption = handleRemoveOption;
    function handleRemoveOption(opt) {
      // Gọi function show dialog từ scope cha
      $scope.handleShowConfirm({
        content: 'LB_POLLS_CONFIRM_DELETE_OPT',
        type: 3,
        button: 'LB_DELETE'
      }, confirm => {
        handle_delete();
      });

      function handle_delete() {
        vm.opts = _.without(vm.opts, opt);
        if (opt._id) {
          var _opt = new Opts(opt);
          _opt.$remove(() => {
            Socket.emit('opts_update', { pollId: vm.poll._id });
          });
        }
      }
    }
    // Đồng ý đề xuất của thành viên
    vm.handleApproveOption = handleApproveOption;
    function handleApproveOption(opt) {
      // Gọi function show dialog từ scope cha
      $scope.handleShowConfirm({
        content: 'LB_POLLS_CONFIRM_APPROVE',
        type: 1,
        button: 'LB_APPROVE'
      }, confirm => {
        handle_approve();
      });
      function handle_approve() {
        opt.status = 1;
        var _opt = new Opts({ _id: opt._id, status: opt.status });
        _opt.$update(() => {
          Socket.emit('opts_update', { pollId: vm.poll._id });
        });
      }
    }
    // Từ chối đề xuất
    vm.handleRejectOption = handleRejectOption;
    function handleRejectOption(opt) {
      // Gọi function show dialog từ scope cha
      $scope.handleShowConfirm({
        content: 'LB_POLLS_CONFIRM_REJECT',
        type: 2,
        button: 'LB_REJECT'
      }, confirm => {
        handle_reject();
      });
      function handle_reject() {
        opt.status = 3;
        var _opt = new Opts({ _id: opt._id, status: opt.status });
        _opt.$update(() => {
        });
      }
    }
    // Lưu option
    vm.handleSaveOption = handleSaveOption;
    function handleSaveOption(isValid) {
      if (!isValid) {
        $scope.$broadcast('show-errors-check-validity', 'vm.form.optForm');
        return false;
      }
      // Kiểm tra poll đã tồn tại
      if (vm.tmp_opt._id) {
        // Update vào option cũ
        var opt = _.findWhere(vm.opts, { _id: vm.tmp_opt._id });
        opt = _.extend(opt, vm.tmp_opt);
      } else {
        // Nếu chưa có option nào giống với option mới thì add vào list
        if (!_.contains(vm.opts, vm.tmp_opt)) {
          vm.opts.push(_.clone(vm.tmp_opt));
        } else {
          $scope.handleShowMessage('LB_OPTION_DUPLICATE', true);
        }
      }
      vm.tmp_opt = {};
      $scope.$broadcast('show-errors-reset', 'vm.form.optForm');
      angular.element('body').removeClass('aside-panel-open');
      vm.isFocusOptionInput = false;
    }
    // Hiển thị full màn hình
    vm.handleShowFullOption = handleShowFullOption;
    function handleShowFullOption() {
      let aside = angular.element('.aside-panel')[0];
      angular.element(aside).toggleClass('full');
      angular.element('#aside-panel-full-toggle').find('i').toggleClass('r180');
    }
    // Xóa thông tin cài đặt ngày close
    vm.handleClearCloseDate = handleClearCloseDate;
    function handleClearCloseDate() {
      delete vm.poll.close;
    }
    // Kiểm tra ngày đóng poll hợp lệ hay không
    function handleValidateCloseDate() {
      if (!vm.poll.close) return true;
      if (vm.poll._id && vm.isClosed) return true;
      return moment(vm.poll.close).utc().isAfter(new moment().utc());
    }
  }
})();
<|endoftext|>


<commit_before>import _ from 'lodash';
import React from 'react';
import Link from 'react-router-dom/Link';
import PropTypes from 'prop-types';

import { Row, Col } from '@folio/stripes-components/lib/LayoutGrid';
import KeyValue from '@folio/stripes-components/lib/KeyValue';
import Pane from '@folio/stripes-components/lib/Pane';
import Paneset from '@folio/stripes-components/lib/Paneset';
import Button from '@folio/stripes-components/lib/Button';
import MultiColumnList from '@folio/stripes-components/lib/MultiColumnList';

import { Actions } from './lib/Accounts/Actions';
import { formatDateTime, getFullName } from './util';

class AccountActionsHistory extends React.Component {
  static manifest = Object.freeze({
    accountActions: {
      type: 'okapi',
      records: 'feefineactions',
      path: 'feefineactions?query=(accountId=%{activeRecord.accountId})',
    },
    activeRecord: {
      accountId: '0',
    },
  });

  static propTypes = {
    stripes: PropTypes.object.isRequired,
    resources: PropTypes.shape({
      accountActions: PropTypes.object,
    }),
    mutator: PropTypes.shape({
      activeRecord: PropTypes.shape({
        update: PropTypes.func.isRequired,
      }),
      feefineactions: PropTypes.shape({
        POST: PropTypes.func.isRequired,
      }),
    }),
    account: PropTypes.object,
    user: PropTypes.object,
    onCancel: PropTypes.func.isRequired,
  };
  constructor(props) {
    super(props);

    this.onSort = this.onSort.bind(this);
    this.onChangeActions = this.onChangeActions.bind(this);
    this.connectedActions = props.stripes.connect(Actions);
    this.error = this.error.bind(this);
    this.comment = this.comment.bind(this);
    this.accounts = [this.props.account];

    const { stripes } = props;

    this.sortMap = {
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.date' })]: action => action.dateAction,
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.action' })]: action => action.typeAction,
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.amount' })]: action => action.amountAction,
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.balance' })]: action => action.balance,
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.number' })]: action => action.transactionNumber,
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.created' })]: action => action.createdAt,
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.source' })]: action => action.source,
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.comments' })]: action => action.comments,
    };

    this.state = {
      actions: {
        pay: false,
        cancellation: false,
        waiveModal: false,
        comment: false,
      },
      checkedAccounts: {},
      sortOrder: [
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.date' }),
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.action' }),
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.amount' }),
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.balance' }),
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.number' }),
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.created' }),
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.source' }),
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.comments' }),
      ],
      sortDirection: ['desc', 'asc'],
      message: {},
      source: '',
      id: '',
    };
  }

  componentDidMount() {
    const { account } = this.props;
    const accountId = (account) ? account.id : '';
    this.props.mutator.activeRecord.update({ accountId });
  }

  onChangeActions(actions) {
    this.setState({
      actions,
    });
  }

  waive = () => {
    this.onChangeActions({ waiveModal: true });
  }

  pay = () => {
    this.onChangeActions({ pay: true });
  }

  error() {
    this.onChangeActions({ cancellation: true });
  }

  comment() {
    this.onChangeActions({ comment: true });
  }

  onSort(e, meta) {
    if (!this.sortMap[meta.alias]) return;

    let { sortOrder, sortDirection } = this.state;

    if (sortOrder[0] !== meta.alias) {
      sortOrder = [meta.alias, sortOrder[1]];
      sortDirection = ['asc', sortDirection[1]];
    } else {
      const direction = (sortDirection[0] === 'desc') ? 'asc' : 'desc';
      sortDirection = [direction, sortDirection[1]];
    }
    this.setState({ ...this.state, sortOrder, sortDirection });
  }

  render() {
    const { sortOrder, sortDirection } = this.state;
    const { onCancel, account, stripes } = this.props;

    const user = this.props.user;

    const columnMapping = {
      Comments: ({this.props.stripes.intl.formatMessage({ id: 'ui-users.details.columns.comments' })}<Button style={{ float: 'right', marginLeft: '50px' }} onClick={this.comment}>+ New</Button>),
    };

    const accountActionsFormatter = {
      // Action: aa => loanActionMap[la.action],
      'Action Date': action => formatDateTime(action.dateAction, stripes.locale),
      'Action': action => action.typeAction,
      'Amount': action => action.amountAction || '-',
      'Balance': action => action.balance || '-',
      'Transaction number': action => action.transactionNumber,
      'Created at': action => action.createdAt,
      'Source': action => action.source,
      'Comments': action => action.comments,
    };

    const actions = _.get(this.props.resources, ['accountActions', 'records'], []);
    const actionsSort = _.orderBy(actions, [this.sortMap[sortOrder[0]], this.sortMap[sortOrder[1]]], sortDirection);

    return (
      <Paneset isRoot>
        <Pane
          id="pane-loandetails"
          defaultWidth="100%"
          dismissible
          onClose={onCancel}
          paneTitle={<Link to={`/users/view/${user.id}`}>{getFullName(user)}</Link>}
        >
          <Row>
            <Col xs={12}>
              <Button buttonStyle="primary" onClick={this.pay}>Pay</Button>
              <Button buttonStyle="primary" onClick={this.waive}>Waive</Button>
              <Button buttonStyle="primary">Refund</Button>
              <Button buttonStyle="primary">Transfer</Button>
              <Button buttonStyle="primary" onClick={this.error}>Error</Button>

            </Col>
          </Row>

          <Row>
            <Col xs={2} >
              <KeyValue label="Fee/fine type" value={_.get(account, ['feeFineType'], '')} />
            </Col>
            <Col xs={2} >
              <KeyValue label="Fee/fine owner" value={_.get(account, ['feeFineOwner'], '')} />
            </Col>
            <Col xs={2} >
              <KeyValue label="Charge date" value={_.get(account, ['dateCreated'], '')} />
            </Col>
            <Col xs={2} >
              <KeyValue label="Original fee/fine" value={_.get(account, ['charged'], '')} />
            </Col>
            <Col xs={2} >
              <KeyValue label="Outstanding amount" value={_.get(account, ['remaining'], '')} />
            </Col>
            <Col xs={2} >
              <KeyValue label="Loan details" value="" />
            </Col>
          </Row>
          <Row>
            <Col xs={2} >
              <KeyValue label="Item type" value={_.get(account, ['itemType'], '')} />
            </Col>
            <Col xs={2} >
              <KeyValue label="Instance" value={_.get(account, ['item'], '')} />
            </Col>
            <Col xs={2} >
              <KeyValue label="Barcode (falta el ID)" value={<Link to={`/inventory/view/${_.get(account, ['itemId'], '')}?query=${_.get(account, ['itemId'], '')}`}>{_.get(account, ['barcode'], '')}</Link>} />
            </Col>
            <Col xs={2} >
              <KeyValue label="Call number" value={_.get(account, ['callNumber'], '')} />
            </Col>
            <Col xs={2} >
              <KeyValue label="Location" value="" />
            </Col>
          </Row>

          <MultiColumnList
            id="list-accountactions"
            formatter={accountActionsFormatter}
            columnMapping={columnMapping}
            visibleColumns={['Action Date', 'Action', 'Amount', 'Balance', 'Transaction number', 'Created at', 'Source', 'Comments']}
            contentData={actionsSort}
            fullWidth
            onHeaderClick={this.onSort}
            sortOrder={sortOrder[0]}
            sortDirection={`${sortDirection[0]}ending`}
            columnWidths={{ 'Action': 100, 'Amount': 100, 'Balance': 100, 'Transaction number': 100, 'Created at': 100, 'Source': 200, 'Comments': 700 }}
          />

          <this.connectedActions
            actions={this.state.actions}
            onChangeActions={this.onChangeActions}
            user={this.props.user}
            accounts={this.accounts}
          />
        </Pane>
      </Paneset>
    );
  }
}

export default AccountActionsHistory;

<commit_msg>Add date format, implement functionality for Loan details, fix two decimal places<commit_after>import _ from 'lodash';
import React from 'react';
import Link from 'react-router-dom/Link';
import PropTypes from 'prop-types';

import { Row, Col } from '@folio/stripes-components/lib/LayoutGrid';
import KeyValue from '@folio/stripes-components/lib/KeyValue';
import Pane from '@folio/stripes-components/lib/Pane';
import Paneset from '@folio/stripes-components/lib/Paneset';
import Button from '@folio/stripes-components/lib/Button';
import MultiColumnList from '@folio/stripes-components/lib/MultiColumnList';

import { Actions } from './lib/Accounts/Actions';
import { formatDateTime, getFullName } from './util';

class AccountActionsHistory extends React.Component {
  static manifest = Object.freeze({
    account: {
      type: 'okapi',
      records: 'accounts',
      path: 'accounts?query=id=%{activeRecord.accountId}&limit=1',
    },
    accountActions: {
      type: 'okapi',
      records: 'feefineactions',
      accumulate: 'true',
      path: 'feefineactions?query=(accountId=%{activeRecord.accountId})&limit=50',
    },
    activeRecord: {
      accountId: '0',
    },
  });

  static propTypes = {
    stripes: PropTypes.object.isRequired,
    resources: PropTypes.shape({
      intl: PropTypes.object.isRequired,
      accountActions: PropTypes.object,
    }),
    mutator: PropTypes.shape({
      activeRecord: PropTypes.shape({
        update: PropTypes.func.isRequired,
      }),
      feefineactions: PropTypes.shape({
        POST: PropTypes.func.isRequired,
      }),
      accountActions: PropTypes.shape({
        GET: PropTypes.func.isRequired,
      }),
    }),
    account: PropTypes.object,
    user: PropTypes.object,
    history: PropTypes.object,
    patronGroup: PropTypes.object,
    onCancel: PropTypes.func.isRequired,
    onClickViewLoanActionsHistory: PropTypes.func.isRequired,
  };
  constructor(props) {
    super(props);

    this.onSort = this.onSort.bind(this);
    this.onChangeActions = this.onChangeActions.bind(this);
    this.connectedActions = props.stripes.connect(Actions);
    this.error = this.error.bind(this);
    this.comment = this.comment.bind(this);

    const { stripes } = props;

    this.sortMap = {
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.date' })]: action => action.dateAction,
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.action' })]: action => action.typeAction,
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.amount' })]: action => action.amountAction,
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.balance' })]: action => action.balance,
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.number' })]: action => action.transactionNumber,
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.created' })]: action => action.createdAt,
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.source' })]: action => action.source,
      [stripes.intl.formatMessage({ id: 'ui-users.details.columns.comments' })]: action => action.comments,
    };

    this.state = {
      data: [],
      actions: {
        pay: false,
        cancellation: false,
        waive: false,
        waiveModal: false,
        comment: false,
        regular: false,
      },
      checkedAccounts: {},
      sortOrder: [
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.date' }),
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.action' }),
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.amount' }),
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.balance' }),
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.number' }),
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.created' }),
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.source' }),
        stripes.intl.formatMessage({ id: 'ui-users.details.columns.comments' }),
      ],
      sortDirection: ['desc', 'asc'],
      message: {},
      source: '',
      id: '',
    };
  }

  componentDidMount() {
    const { history } = this.props;
    const str = history.location.search || '';
    const n = str.indexOf('account=');
    const id = str.substring(n + 8, n + 44);
    this.props.mutator.activeRecord.update({ accountId: id });
    this.getAccountActions();
  }

  getAccountActions = () => {
    return this.props.mutator.accountActions.GET().then(records => {
      this.setState({
        data: records,
      });
    });
  }

  onChangeActions(actions) {
    this.setState({
      actions,
    });
  }

  waive = () => {
    this.onChangeActions({ waiveModal: true });
  }

  pay = () => {
    this.onChangeActions({ pay: true });
  }

  error() {
    this.onChangeActions({ cancellation: true });
  }

  comment() {
    this.onChangeActions({ comment: true });
  }

  onSort(e, meta) {
    if (!this.sortMap[meta.alias]) return;

    let { sortOrder, sortDirection } = this.state;

    if (sortOrder[0] !== meta.alias) {
      sortOrder = [meta.alias, sortOrder[1]];
      sortDirection = ['asc', sortDirection[1]];
    } else {
      const direction = (sortDirection[0] === 'desc') ? 'asc' : 'desc';
      sortDirection = [direction, sortDirection[1]];
    }
    this.setState({ ...this.state, sortOrder, sortDirection });
  }

  render() {
    const { sortOrder, sortDirection } = this.state;
    const { onCancel, stripes } = this.props;
    const account = _.get(this.props.resources, ['account', 'records', 0]) || this.props.account;

    const user = this.props.user;
    const patron = this.props.patronGroup;

    const columnMapping = {
      Comments: ({this.props.stripes.intl.formatMessage({ id: 'ui-users.details.columns.comments' })}<Button style={{ float: 'right', marginLeft: '50px' }} onClick={this.comment}>+ New</Button>),
    };

    const accountActionsFormatter = {
      // Action: aa => loanActionMap[la.action],
      'Action Date': action => formatDateTime(action.dateAction, stripes.locale),
      'Action': action => action.typeAction + (action.paymentMethod ? ('-' + action.paymentMethod) : ' '),
      'Amount': action => (action.amountAction > 0 ? parseFloat(action.amountAction).toFixed(2) : '-'),
      'Balance': action => (action.balance > 0 ? parseFloat(action.balance).toFixed(2) : '-'),
      'Transaction information': action => action.transactionNumber || '-',
      'Created at': action => action.createdAt,
      'Source': action => action.source,
      'Comments': action => action.comments,
    };

    const actions = this.state.data || [];
    const actionsSort = _.orderBy(actions, [this.sortMap[sortOrder[0]], this.sortMap[sortOrder[1]]], sortDirection);
    const amount = (account.amount) ? parseFloat(account.amount).toFixed(2) : '-';
    const remaining = (account.remaining) ? parseFloat(account.remaining).toFixed(2) : '0.00';
    const loanId = account.loanId || '';
    const disabled = (_.get(account, ['status', 'name'], '') === 'Closed');

    return (
      <Paneset isRoot>
        <Pane
          id="pane-loandetails"
          defaultWidth="100%"
          dismissible
          onClose={onCancel}
          paneTitle={`Fees/Fines - ${getFullName(user)} (${_.upperFirst(patron.group)}) `}
        >
          <Row>
            <Col xs={12}>
              <Button disabled={disabled} buttonStyle="primary" onClick={this.pay}>Pay</Button>
              <Button disabled={disabled} buttonStyle="primary" onClick={this.waive}>Waive</Button>
              <Button disabled buttonStyle="primary">Refund</Button>
              <Button disabled buttonStyle="primary">Transfer</Button>
              <Button disabled={disabled} buttonStyle="primary" onClick={this.error}>Error</Button>
            </Col>
          </Row>

          <Row>
            <Col xs={1.5} >
              <KeyValue label={this.props.stripes.intl.formatMessage({ id: 'ui-users.details.field.feetype' })} value={_.get(account, ['feeFineType'], '-')} />
            </Col>
            <Col xs={1.5} >
              <KeyValue label={this.props.stripes.intl.formatMessage({ id: 'ui-users.details.field.owner' })} value={_.get(account, ['feeFineOwner'], '-')} />
            </Col>
            <Col xs={1.5} >
              <KeyValue label={this.props.stripes.intl.formatMessage({ id: 'ui-users.details.field.billedate' })} value={_.get(account, ['metadata', 'createdDate']) ? formatDateTime(_.get(account, ['metadata', 'createdDate'])) : '-'} />
            </Col>
            <Col xs={1.5} >
              <KeyValue label={this.props.stripes.intl.formatMessage({ id: 'ui-users.details.field.billedamount' })} value={amount} />
            </Col>
            <Col xs={1.5} >
              <KeyValue label={this.props.stripes.intl.formatMessage({ id: 'ui-users.details.field.remainingamount' })} value={remaining} />
            </Col>
            <Col xs={1.5} >
              <KeyValue label={this.props.stripes.intl.formatMessage({ id: 'ui-users.details.field.latest' })} value={_.get(account, ['paymentStatus', 'name'], '-')} />
            </Col>
            <Col xs={1.5} >
              {(loanId !== 0) ?
                <KeyValue
                  label="Loan details"
                  value={
                    <button onClick={(e) => { this.props.onClickViewLoanActionsHistory(e, { id: loanId }); }}>
                      {this.props.stripes.intl.formatMessage({ id: 'ui-users.details.field.loan' })}
                    </button>}
                />
                :
                <KeyValue
                  label="Loan details"
                  value={this.props.stripes.intl.formatMessage({ id: 'ui-users.details.field.loan' })}
                />
              }
            </Col>
          </Row>
          <Row>
            <Col xs={1.5} >
              <KeyValue label={this.props.stripes.intl.formatMessage({ id: 'ui-users.details.field.instance' })} value={_.get(account, ['title'], '-')} />
            </Col>
            <Col xs={1.5} >
              <KeyValue label={this.props.stripes.intl.formatMessage({ id: 'ui-users.details.field.type' })} value={_.get(account, ['materialType'], '-')} />
            </Col>
            <Col xs={1.5} >
              <KeyValue label={this.props.stripes.intl.formatMessage({ id: 'ui-users.details.field.barcode' })} value={<Link to={`/inventory/view/${_.get(account, ['itemId'], '')}?query=${_.get(account, ['itemId'], '')}`}>{_.get(account, ['barcode'], '-')}</Link>} />
            </Col>
            <Col xs={1.5} >
              <KeyValue label={this.props.stripes.intl.formatMessage({ id: 'ui-users.details.field.callnumber' })} value={_.get(account, ['callNumber'], '-')} />
            </Col>
            <Col xs={1.5} >
              <KeyValue label={this.props.stripes.intl.formatMessage({ id: 'ui-users.details.field.location' })} value={_.get(account, ['location'], '-')} />
            </Col>
            <Col xs={1.5} >
              <KeyValue label={this.props.stripes.intl.formatMessage({ id: 'ui-users.details.field.duedate' })} value={formatDateTime(account.dueDate) || '-'} />
            </Col>
            <Col xs={1.5} >
              <KeyValue label={this.props.stripes.intl.formatMessage({ id: 'ui-users.details.field.returnedate' })} value={formatDateTime(account.returnedDate) || '-'} />
            </Col>
          </Row>

          <MultiColumnList
            id="list-accountactions"
            formatter={accountActionsFormatter}
            columnMapping={columnMapping}
            visibleColumns={['Action Date', 'Action', 'Amount', 'Balance', 'Transaction information', 'Created at', 'Source', 'Comments']}
            contentData={(account.id === (actions[0] || {}).accountId) ? actionsSort : []}
            fullWidth
            onHeaderClick={this.onSort}
            sortOrder={sortOrder[0]}
            sortDirection={`${sortDirection[0]}ending`}
            columnWidths={{ 'Action': 250, 'Amount': 100, 'Balance': 100, 'Transaction information': 200, 'Created at': 100, 'Source': 200, 'Comments': 700 }}
          />
          <this.connectedActions
            actions={this.state.actions}
            onChangeActions={this.onChangeActions}
            user={this.props.user}
            balance={account.remaining || 0}
            accounts={[account]}
            handleEdit={this.getAccountActions}
          />

        </Pane>
      </Paneset>
    );
  }
}

export default AccountActionsHistory;

<|endoftext|>


<commit_before>'use strict';

(function($, angular) {
  // Define the `header` module
  var app = angular.module('app.category', []);

  // Register `headerList` component, along with its associated controller and template
  app.component('appCategory', {
      template: '

',
      controller: [
        '$scope',
        '$element',
        'Config',
        'Util',
        'Json',
        controller]
    });

  function controller($scope, $element, config, util, json) {
    var self = this,
        beginStickyElemId = "categoryBeginSticky",
        elem = false,
        path = util.getUrlPath().substring(1),
        rootPath = config.data.data;

    var windowScroll = function(e) {
      if(!elem) {
        elem = $element.find("#" + beginStickyElemId);
      }
      if($(window).scrollTop() > elem.offset().top){
        self.headerStickyHide = false;
      } else {
        self.headerStickyHide = true;
      }
    };

    var setSubjectsStyle = function() {
      $.each(self.subjects, function(i, val) {
          if(!self.subjectsStyle[val.id]){
            self.subjectsStyle[val.id] = {};
          }
          var url = rootPath + "/" + self.category.dirName + "/" + val.dirName + "/" + val.imageUrl
          self.subjectsStyle[val.id].backgroundImage = "url(" + url + ")";
        }
      )
    };

    $(window).scroll(function(e){
      $scope.$apply(function(){
        windowScroll(e);
      });
    });

    var init = function() {
      self.category = json.getCategory(path);

      self.subjects = self.jsons.subjects[self.category.id];

      setSubjectsStyle();

      self.classes = self.jsons.classes[self.category.id];
    };

    self.listMouseEnter = function(id) {
      if(!self.ListItemLinkStyle[id]){
        self.ListItemLinkStyle[id] = {};
      }
      self.ListItemLinkStyle[id].color = self.category.color;
    };

    self.listMouseLeave = function(id) {
      self.ListItemLinkStyle[id].color = "";
    };

    self.linkMouseEnter = function(event) {
      $(event.currentTarget).css({"text-decoration": "underline"});
    };

    self.linkMouseLeave = function(event) {
      $(event.currentTarget).css({"text-decoration": "none"});
    };

    self.subjectMouseEnter = function(id) {
      if(!self.subjectItemStyle[id]){
        self.subjectItemStyle[id] = {};
      }
      self.subjectItemStyle[id].textDecoration = "underline";
    };

    self.subjectMouseLeave = function(id) {
      self.subjectItemStyle[id].textDecoration = "none";
    };

    self.getUrl = function(url) {
      return util.convertUrl(self.category.dirName + "/" + url);
    };

    self.templateUrl = config.templateUrl.category;

    self.jsons = json;
    self.category = {};
    self.subjects = {};
    self.subjectsStyle = {};
    self.classes = {};
    self.classesStyle = {};
    self.ListItemLinkStyle = {};
    self.subjectItemStyle = {};
    self.headerStickyHide = true;

    $scope.$watch(function(){return self.jsons;}, init, true);
  }

})(jQuery, window.angular);
<commit_msg>debug category undefined scroll top bug.<commit_after>'use strict';

(function($, angular) {
  // Define the `header` module
  var app = angular.module('app.category', []);

  // Register `headerList` component, along with its associated controller and template
  app.component('appCategory', {
      template: '

',
      controller: [
        '$scope',
        '$element',
        'Config',
        'Util',
        'Json',
        controller]
    });

  function controller($scope, $element, config, util, json) {
    var self = this,
        beginStickyElemId = "categoryBeginSticky",
        elem = false,
        path = util.getUrlPath().substring(1),
        rootPath = config.data.data;

    var windowScroll = function(e) {
      if(!elem) {
        elem = $element.find("#" + beginStickyElemId);
      }
      if($(window).scrollTop() > elem.offset().top){
        self.headerStickyHide = false;
      } else {
        self.headerStickyHide = true;
      }
      $scope.$digest();
    };

    var setSubjectsStyle = function() {
      $.each(self.subjects, function(i, val) {
          if(!self.subjectsStyle[val.id]){
            self.subjectsStyle[val.id] = {};
          }
          var url = rootPath + "/" + self.category.dirName + "/" + val.dirName + "/" + val.imageUrl
          self.subjectsStyle[val.id].backgroundImage = "url(" + url + ")";
        }
      )
    };

    $(window).scroll(function(e){
      windowScroll(e);
    });

    var init = function() {
      self.category = json.getCategory(path);

      self.subjects = self.jsons.subjects[self.category.id];

      setSubjectsStyle();

      self.classes = self.jsons.classes[self.category.id];
    };

    self.listMouseEnter = function(id) {
      if(!self.ListItemLinkStyle[id]){
        self.ListItemLinkStyle[id] = {};
      }
      self.ListItemLinkStyle[id].color = self.category.color;
    };

    self.listMouseLeave = function(id) {
      self.ListItemLinkStyle[id].color = "";
    };

    self.linkMouseEnter = function(event) {
      $(event.currentTarget).css({"text-decoration": "underline"});
    };

    self.linkMouseLeave = function(event) {
      $(event.currentTarget).css({"text-decoration": "none"});
    };

    self.subjectMouseEnter = function(id) {
      if(!self.subjectItemStyle[id]){
        self.subjectItemStyle[id] = {};
      }
      self.subjectItemStyle[id].textDecoration = "underline";
    };

    self.subjectMouseLeave = function(id) {
      self.subjectItemStyle[id].textDecoration = "none";
    };

    self.getUrl = function(url) {
      return util.convertUrl(self.category.dirName + "/" + url);
    };

    self.templateUrl = config.templateUrl.category;

    self.jsons = json;
    self.category = {};
    self.subjects = {};
    self.subjectsStyle = {};
    self.classes = {};
    self.classesStyle = {};
    self.ListItemLinkStyle = {};
    self.subjectItemStyle = {};
    self.headerStickyHide = true;

    $scope.$watch(function(){return self.jsons;}, init, true);
  }

})(jQuery, window.angular);
<|endoftext|>