frontend/reports/reports.js

const { remote } = require('electron');
const { ReporteController } = remote.require('./controllers');


/** clase que controla las consultas estadisticas  */
class ReportsComponent {
  constructor() {

    // element html
    this.form = document.forms['form-estadistics'];
    this.loadingComponent = document.querySelector('app-loading');
    this.productQuestions = this.form.querySelector('#product-questions');
    this.deliveryQuestions = this.form.querySelector('#delivery-questions');
    this.period = this.form.querySelector('#from-until');
    this.errorFrom = this.form.querySelector('#error-from');
    this.errorAreaBusiness = this.form.querySelector('#error-area-business');
    this.errorTo = this.form.querySelector('#error-to');

    // iterable
    this.errorsQuestions = this.form.querySelectorAll('.error-questions');

    // charts
    this.canvasRow = this.form.querySelector('#canvas-estadistics');

    // diagrama
    this.chart = null;

    // table
    this.table = this.canvasRow.querySelector('#table-data');

    /**
     * estado del rango de fechas
     * @type {boolean}
     */
    this.range = false;

    this.handleChangeBusiness = this.handleChangeBusiness.bind( this );

    this.setEvents();
  }

  /** permite establecer el rango de fechas */
  set _range( range ) {
    this.range = range;
  }

  /** obtiene el estado del rango de fechas  */
  get _range() {
    return this.range;
  }

  /**  establece los eventos del formulario */
  setEvents() {
    this.form.addEventListener('submit', this.generateStadistics.bind( this ) );
  }

  /**
   * funcion que controla el select de la seccion a consultar
   * @param {*} $event evento de cambio 
   */
  handleChangeBusiness( $event ) {

    const { value } = $event.target;

    // muestra el select dependiendo de la opcion seleccionada
    switch ( value ) {
      case 'delivery':

        this.clearFilter();
        showElement( this.deliveryQuestions );
        this.showPeriod('none');

        break;

      case 'product':

        this.clearFilter();
        showElement( this.productQuestions );
        this.showPeriod('none');

        break;

      default:
        this.clearFilter();
        break;
    }
  }

  /**
   * funcion que controla el cambio del select de la consulta
   * @param {*} $event evento change 
   */
  handleChangeQuestions( $event ) {

    const { value } = $event.target;
    const regex = /period/g;

    if ( regex.test( value ) ) {

      this.showPeriod();
      this._range = true;

    } else {

      this.showPeriod('none');
      this._range = false;

    }

    this.deleteCharts();
  }

  /** Limpia el filtro de busqueda  */
  clearFilter() {

    hideElement( this.deliveryQuestions );
    hideElement( this.productQuestions );

    this.showPeriod('none');
    this._range = false;
    this.table.innerHTML = '';

    this.deleteCharts();
  }

  /** funcion que muestra al usuario el formulario del periodo */
  showPeriod( value = 'flex' ) {

    if ( value === 'none' ) {

      for ( const input of this.period.querySelectorAll('input[type="date"]') ) {
        input.value = '';
      }

      this.resetForm();
    }

    this.period.style.display = value;
  }

  /**
   * Funcion que genera las estadisticas
   * @param {*} $event evento de envio de formulario
   */
  generateStadistics( $event ) {

    $event.preventDefault();

    this.deleteCharts();

    const formData = new FormData( this.form );

    // valores booleanos para la consulta
    const data = {
      delivery_note: formData.get('area-business') === 'delivery',
      products: formData.get('area-business') === 'product',
      question_delivery: formData.get('delivery-questions') || null,
      question_products: formData.get('product-questions') || null,
      from: formData.get('from') || null,
      to: formData.get('to') || null
    };

    // console.log(data );

    this.validate( data, async () => {

      this.canvasRow.style.display = 'flex';

      if ( data.products ) {

        // total de productos x categoria
        if ( data.question_products === 'total-product-category' ) {

          try {
            
            let response = await ReporteController.buscarTotalProductosPorCategoria();
           
            if ( !response ) {
              renderTableCategories({ results: [] }, this.table );
              return;
            }

            renderTableCategories( response, this.table );

            this.createPieChart('#chart-model', response );

          } catch ( e ) {
              console.error( e );

          }

        // productos maximo vendidos
        } else if ( data.question_products === 'product-max-sold-general' ) {

          try {

            let response = await ReporteController.buscarCantidadMaximaVendida();

            if ( !response ) {
              renderTableProductQuantity({ results: [] }, this.table );
              return;
            }

            renderTableProductQuantity( response, this.table );
            this.createBarChart('#chart-model', response );

          } catch (e) {
            console.error( e );
          }

        // productos maximo vendidos periodo
        } else if ( data.question_products === 'product-max-sold-period' ) {

          try {

            let response = await ReporteController.buscarCantidadMaximaVendida({
              fecha_inicio: data.from,
              fecha_fin: data.to
           });

            if ( !response ) {
              renderTableProductQuantity({ results: [] }, this.table );
              return;
            }

            renderTableProductQuantity( response, this.table );
            this.createBarChart('#chart-model', response );

          } catch (e) {
            console.error( e );
          }

        // periodo de ventas anual
        } else if ( data.question_products === 'product-sold-general' ) {

          try {

            let response = await ReporteController.buscarCantidadProductosVendidosAnual();

            if ( !response ) {
              renderTablePeriodSeller({ results: [] }, this.table );
              return;
            }

            renderTablePeriodSeller( response, this.table );
            this.createLineChart('#chart-model', response );

          } catch (e) {
            console.error( e );
          }
        }

      } else {  // delivery_note

        // cantidad notas x vendedor
        if ( data.question_delivery === 'quantity-general' ) {

          try {

            let response = await ReporteController.buscarNotasVendidasPorVendedor();

            if ( !response ) {
              renderTableSellers({ results: [] }, this.table );
              return;
            }

            renderTableSellers( response, this.table );
            this.createBarChart('#chart-model', response );

          } catch (e) {
            console.error( e );

          }

        // cantidad notas x vendedor periodo
        } else if ( data.question_delivery === 'quantity-period' ) {

           try {
             
             let response = await ReporteController.buscarNotasVendidasPorVendedor({
               fecha_inicio: data.from,
               fecha_fin: data.to
            });

            if ( !response ) {
              renderTableSellers({ results: [] }, this.table );
              return;
            }

            renderTableSellers( response, this.table );
            this.createBarChart('#chart-model', response );

           } catch (e) {
             console.error( e );
           }

        // cantidad notas x estado
        } else if ( data.question_delivery === 'delivery-general' ) {

          try {

            let response = await ReporteController.buscarNotasCategoria();

            if ( !response ) {
              renderTableDeliveryState({ results: [] }, this.table );
              return;
            }

            renderTableDeliveryState( response, this.table );
            this.createPieChart('#chart-model', response );

          } catch (e) {
            console.error( e );

          }

        } else {

          try {

            let response = await ReporteController.buscarNotasCategoria({
              fecha_inicio: data.from + ' 00:00:00',
              fecha_fin: data.to + ' 23:59:59'
            });

            // console.log( response );

            if ( !response ) {
              renderTableDeliveryState({ results: [] }, this.table );
              return;
            }

            renderTableDeliveryState( response, this.table );
            this.createPieChart('#chart-model', response);

          } catch (e) {
            console.error( e );

          }
        }
      }
    });
  }

  /**
   * Permite maquetar y modelar un diagrama de barras
   * @param {string} idChart identificador dom del elemento
   * @param {Object} objectModel modelo del objeto
   * @param {Array<string>} objectModel.keys identificadores de las barras
   * @param {Array<number>} objectModel.values valores numericos para modelar los datos 
   */
  createBarChart( idChart, objectModel ) {

    const canvas = document.querySelector( idChart );
    const { keys, values } = objectModel;
    const { backgroundColor, borderColor } = this.createRandomColor( values, 0.2 );

    canvas.parentNode.style.width = '90%';

    // chart viene del objeto global
    this.chart = new Chart( canvas, {
      type: 'bar',
      data: {
       labels: keys,
       datasets: [{
           label: 'Cantidad',
           data: values,
           backgroundColor,
           borderColor,
           borderWidth: 1
       }]
     },
     options: {
       scales: {
         y: {
           beginAtZero: true
         }
       },
       responsive: true
     }
    });
  }

  /**
   * Permite maquetar y modelar un diagrama de torta
   * @param {string} idChart identificador dom del elemento
   * @param {Object} objectModel modelo del objeto
   * @param {Array<string>} objectModel.keys identificadores de las barras
   * @param {Array<number>} objectModel.values valores numericos para modelar los datos 
   */
  createPieChart( idChart, objectModel ) {

    const canvas = document.querySelector( idChart );
    const { keys, values } = objectModel;

    canvas.parentNode.style.width = 'initial';

    // se generan los colores aleatorios
    const { backgroundColor, borderColor } = this.createRandomColor( values, 0.2 );

    this.chart = new Chart( canvas, {
      type: 'pie',
      data: {
        labels: keys,
        datasets: [{
          data: values,
          backgroundColor,
          borderColor,
          borderWidth: 1
        }]
      },
      options: {
       responsive: true
      }
    });
  }

  /**
   * Permite maquetar y modelar un diagrama de lineas
   * @param {string} idChart identificador dom del elemento
   * @param {Object} objectModel modelo del objeto
   * @param {Array<string>} objectModel.keys identificadores de las barras
   * @param {Array<number>} objectModel.values valores numericos para modelar los datos 
   */
  createLineChart( idChart, objectModel ) {

    const canvas = document.querySelector( idChart );
    const { keys, values } = objectModel;
    const { backgroundColor, borderColor } = this.createRandomColor( values, 0.2 );

    canvas.parentNode.style.width = '100%';

    this.chart = new Chart( canvas, {
      type: 'line',
      data: {
        labels: keys,
        datasets: [{
          label: 'cantidad de productos',
          data: values,
          backgroundColor: backgroundColor[0],
          borderColor: borderColor[0],
          borderWidth: 1
        }]
      },
      options: {
       responsive: true
      }
    });
  }

  /** permite retirar el modelo del dom  */
  deleteCharts() {

    this.canvasRow.style.display = 'none';

    if ( this.chart  ) {
      this.chart.destroy();
    }

    this.table.innerHTML = '';
    this.chart = null;
  }

  /**
   * @param {Object} data objeto de validacion
   * @param {boolean} data.delivery_note flag que indica la consulta es de notas de entrega
   * @param {boolean} data.products flag que indica la consulta es de productos
   * @param {?string} data.question_delivery consulta realizada a notas de entrega
   * @param {?string} data.question_products consulta realizada a productos
   * @param {?string} data.from inicio de rango de fechas
   * @param {?string} data.to final de rango de fechas
   * @param {CallbackValidateForm} callback llamada de retorno con los datos para ser procesados por el controlador  
   */
  validate( data, callback ) {

    this.resetForm();

    let errors = 0;

    if ( !data.delivery_note && !data.products ) {

      renderErrors( this.errorAreaBusiness, 'El campo es requerido' );
      errors++;

    }

    if ( !data.question_delivery && !data.question_products ) {

      for ( const element of this.errorsQuestions ) {
        renderErrors( element, 'El campo es requerido' );
      }

      errors++;
    }

    // valida si el rango de fechas esta activo
    if ( this._range ) {

      if ( data.from && data.to ) {

        const date1 = new Date( data.from );
        const date2 = new Date( data.to );

        if ( date1 >= date2 ) {

          renderErrors( this.errorFrom, 'La fecha de desde no debe ser mayor o igual a la fecha hasta' );
          errors++;
        }

      } else if ( data.from && !data.to ) {

        renderErrors( this.errorTo, 'La fecha hasta es requerida' );
        errors++;

      } else {

        renderErrors( this.errorFrom, 'La fecha desde es requerida' );
        errors++;

      }
    }

    if ( errors > 0 ) {
      return;
    }

    callback();
  }

  /** Limpia el fomrulario de consulta */
  resetForm() {

    hideElement( this.errorTo );
    hideElement( this.errorFrom );
    hideElement( this.errorAreaBusiness );

    for ( const element of this.errorsQuestions ) {
      hideElement( element );
    }
  }

  /**
   * 
   * @param {Array<number>} values valores numericos 
   * @param {number} opacity opacidad de la linea del chart 
   * @returns {{ backgroundColor: Array<string>, borderColor: Array<string> }}
   */
  createRandomColor( values,  opacity = 1 ) {

    // return { backgroundColor: string[], borderColor: string[] }

    const min = 0;
    const max = 255;

    let backgroundColor = [];
    let borderColor = [];

    values.forEach(() => {

      let color = `rgba(
        ${ Math.floor( Math.random() * ( max - min ) ) + min },
        ${ Math.floor( Math.random() * ( max - min ) ) + min },
        ${ Math.floor( Math.random() * ( max - min ) ) + min },
        ${opacity}
      )`;

      // insertamos en el array
      backgroundColor.push( color );

      color = color.replace(/0.2/, 1);

      borderColor.push( color );
    });

    return { backgroundColor, borderColor };
  }

  /**  genera el reporte final de la aplicacion */
  async generateReport() {

    this.loadingComponent._show = 'true';
    
    try {

      const results = await Promise.all([ 
        ReporteController.buscarNotasCategoria(),
        ReporteController.buscarNotasVendidasPorVendedor(),
        ReporteController.buscarTotalProductosPorCategoria(),
        ReporteController.buscarCantidadMaximaVendida(),
        ReporteController.buscarCantidadProductosVendidosAnual()
      ]);
      
      await ReporteController.generarReporte( results );

    } catch ( error ) {
      console.log( error );

    } finally {
      this.loadingComponent._show = 'false';
    
    } 
  }
}

/** @type {ReportsComponent} */
const reportsComponent = new ReportsComponent();