gantt-1.0.8/js/gantt.js

js/gantt.js
class ClassGanttView {
  constructor(element_id, settingsGantt, data = [], links = []) {
    this.id_gantt = element_id;
    this.settingsGantt = settingsGantt;
    this.Gantt = gantt;
    if (!settingsGantt.use_cdn) {
      this.Gantt = Gantt.getGanttInstance();
    }
    this.Gantt.config.sort = true;
    this.Gantt.config.fit_tasks = true;
    this.Gantt.config.static_background = false;
    this.Gantt.config.open_tree_initially = true;
    this.Gantt.config.order_branch = 'marker';
    this.Gantt.config.order_branch_free = true;
    this.Gantt.config.date_format = "%Y-%m-%d %H:%i";
    this.Gantt.config.date_grid = settingsGantt.date_format;
    this.Gantt.config.wai_aria_attributes = false;

    this.Gantt.config.duration_unit = 'day';
    this.Gantt.config.duration_step = 1;
    this.Gantt.config.time_step = 1440;
    if (settingsGantt.format_date_actually === 'datetime') {
      this.Gantt.config.duration_unit = 'minute';
      this.Gantt.config.time_step = 1;
      this.formatDuration('minute');
    }

    this.i18n = this.geni18n()

    this.defaultHeightTask = {
      bar_height: 26,
      row_height: 35
    }

    if (settingsGantt['link_css']) {
      this.Gantt.config.link_css = settingsGantt['link_css'];
    }

    this.options = settingsGantt.control_bar ?? {};

    this.setPlugin({});

    this.taskLayer = {};

    this.zoomConfig = {};
    this.setScale()

    this.markerToday();

    this.setLocales();

    this.setColumn();

    this.setLightBox();

    this.onCollapse();

    this.onUndoRedo();

    this.onInDentOutDent();

    this.toggleGridChart();

    this.modalDashboard();

    this.exportTo();

    this.copyTask();

    this.onZoomFit();

    this.onFullScreen();

    this.onFullScreenInGantt();

    this.searchText()

    this.searchDate()

    this.groupSource()

    this.setTooltip()

    this.limitCreateLink()

    this.onPermissionTaskLink();

    this.validateField();

    this.onScrollToDay();

    this.requiredParent();

    this.importMMP(settingsGantt.import)

    this.dp = this.Gantt.createDataProcessor({
      url: settingsGantt.ajax,
      mode: "POST"
    });
    this.onHandlerRequest();

    // Settings Show end.
    if (settingsGantt.show_end) {
      this.onShowEndColumn();
    }

    // Settings Show process.
    if (settingsGantt.show_progress) {
      this.onShowProgressColumn();
    }

    // Settings work time gantt chart.
    if (settingsGantt.work_time && settingsGantt.work_day.length) {
      this.setWorkTime(settingsGantt.work_day, settingsGantt.holidays);
    }

    // Settings readonly.
    if (!settingsGantt.edit_task) {
      this.onReadonly()
    }

    // Settings add task.
    if (!settingsGantt.add_task) {
      this.onAddTask()
    }

    // Settings form type multiple select.
    if (settingsGantt.setting_resource.resource.length) {
      this.multipleSelectForm()
    }

    // Settings form type date.
    if (settingsGantt.time_input_mode === 'responsive') {
      this.dateForm()
    }

    // Schedule planned date.
    if (settingsGantt.planned_date && settingsGantt.auto_schedule_planned && !settingsGantt.use_cdn) {
      this.onSchedulePlanned();
    }

    // Settings form type number.
    if (settingsGantt.custom_field.length) {
      this.numberForm()
    }

    // Settings Auto auto type.
    if (this.options.auto_type  && !settingsGantt.use_cdn) {
      this.onAutoType();
    }

    // Settings Auto auto type.
    if (settingsGantt.planned_date && !settingsGantt.use_cdn) {
      this.onShowPlanedGantt();
      this.onDurationPlan();
    }

    // Settings Auto schedule.
    if (this.options.auto_schedule && !settingsGantt.use_cdn) {
      this.onAutoSchedule();
    }

    // Settings Shows the critical path in the chart.
    if (this.options.critical_path && !settingsGantt.use_cdn) {
      this.onHighlightCriticalPath();
    }

    // Settings click drag.
    if (this.options.click_drag && !settingsGantt.use_cdn) {
      this.onClickDrag()
    }

    // Settings drag project.
    if (this.options.drag_project) {
      this.onDragProject();
    }

    // Settings round_dnd_dates.
    if (this.options.round_dnd_dates) {
      this.onMinimumStep();
    }

    // Settings Hide weekend scale.
    if (this.options.hide_weekend_scale && !settingsGantt.use_cdn) {
      this.onHideNotWorkingTime();
    }

    // Settings Hide more task by level.
    if (this.settingsGantt.hide_add_task_level) {
      let level = Math.max(parseInt(this.settingsGantt.hide_add_task_level_value) || 0, 0);
      this.onHideAddTask(level);
    }

    // Settings Show column WBS.
    if (this.options.show_column_wbs) {
      this.showColumnWBS();
    }

    // Settings Limit editing of completed tasks.
    if (this.options.lock_completed_task && !settingsGantt.native_dialog) {
      this.onLookTaskComplete();
    }

    // Settings Highlights drag task.
    if (this.options.highlight_drag_task && !settingsGantt.use_cdn) {
      this.highLightDragTask();
      this.highLightProject();
    }

    // Settings Dynamic progress summary.
    if (this.options.dynamic_progress) {
      this.onDynamicProgress();
    }

    // Settings Show slack.
    if (this.options.show_slack && !settingsGantt.use_cdn) {
      this.showSlack();
    }

    // Settings Text progress.
    if (this.options.progress_text) {
      this.onProgressText();
    }

    // Settings Column buttons.
    if (settingsGantt.column_buttons && settingsGantt.add_task) {
      this.onShowButtonColumn();
    }

    // Settings Last of the day.
    if (settingsGantt.last_of_the_day) {
      this.options.last_of_the_day = true;
    }

    // Settings Show button detail.
    if (settingsGantt.show_button_detail && !settingsGantt.native_dialog) {
      this.onButtonDetail();
    }

    // Settings Show button complete.
    if (!settingsGantt.native_dialog) {
      this.onButtonComplete();
    }

    // Settings Native dialog.
    if (settingsGantt.native_dialog) {
      this.onNativeDialog();
    }

    // Settings Hide show column.
    if (settingsGantt.hide_show_column) {
      this.hideShowColumn()
    }

    if (settingsGantt.order) {
      this.onPostOrder()
    }
  }

  geni18n() {
    return {
      date: {
        month_full: [
          Drupal.t("January"),
          Drupal.t("February"),
          Drupal.t("March"),
          Drupal.t("April"),
          Drupal.t("May"),
          Drupal.t("June"),
          Drupal.t("July"),
          Drupal.t("August"),
          Drupal.t("September"),
          Drupal.t("October"),
          Drupal.t("November"),
          Drupal.t("December"),
        ],
        month_short: [
          Drupal.t("Jan"),
          Drupal.t("Feb"),
          Drupal.t("Mar"),
          Drupal.t("Apr"),
          Drupal.t("May"),
          Drupal.t("Jun"),
          Drupal.t("Jul"),
          Drupal.t("Aug"),
          Drupal.t("Sep"),
          Drupal.t("Oct"),
          Drupal.t("Nov"),
          Drupal.t("Dec"),
        ],
        day_full: [
          Drupal.t("Sunday"),
          Drupal.t("Monday"),
          Drupal.t("Tuesday"),
          Drupal.t("Wednesday"),
          Drupal.t("Thursday"),
          Drupal.t("Friday"),
          Drupal.t("Saturday"),
        ],
        day_short: [
          Drupal.t("Sun"),
          Drupal.t("Mon"),
          Drupal.t("Tue"),
          Drupal.t("Wed"),
          Drupal.t("Thu"),
          Drupal.t("Fri"),
          Drupal.t("Sat"),
        ]
      },
      labels: {
        new_task: Drupal.t("New task"),
        icon_save: Drupal.t("Save"),
        icon_cancel: Drupal.t("Cancel"),
        icon_details: Drupal.t("Details"),
        icon_edit: Drupal.t("Edit"),
        icon_delete: Drupal.t("Delete"),
        gantt_save_btn: Drupal.t("New Label"),
        gantt_cancel_btn: Drupal.t("New Label"),
        gantt_delete_btn: Drupal.t("New Label"),
        confirm_closing: Drupal.t(""),// Your changes will be lost, are you sure?
        confirm_deleting: Drupal.t("Task will be deleted permanently, are you sure?"),
        section_description: Drupal.t("Description"),
        section_time: Drupal.t("Time period"),
        section_baseline: Drupal.t("Time planned"),
        baseline_enable_button: Drupal.t("Set"),
        baseline_disable_button: Drupal.t("Cancel"),
        button_responsive_toggle: Drupal.t("Set"),
        button_responsive_disable_toggle: Drupal.t("Cancel"),
        section_type: Drupal.t("Type"),
        section_parent: Drupal.t("Task parent"),

        /* grid columns */
        column_wbs: Drupal.t("WBS"),
        column_text: Drupal.t("Task name"),
        column_start_date: Drupal.t("Start time"),
        column_end_date: Drupal.t("End time"),
        column_duration: Drupal.t("Duration"),
        column_planned_duration: Drupal.t("Planned duration"),
        column_progress: Drupal.t("Progress"),
        column_add: Drupal.t(""),
        column_priority: Drupal.t("Priority"),
        column_totalSlack: Drupal.t("Total slack"),
        column_freeSlack: Drupal.t("Free slack"),

        /* link confirmation */
        link: Drupal.t("Link"),
        confirm_link_deleting: Drupal.t("will be deleted"),
        link_start: Drupal.t(" (start)"),
        link_end: Drupal.t(" (end)"),

        type_task: Drupal.t("Task"),
        type_project: Drupal.t("Project"),
        type_milestone: Drupal.t("Milestone"),

        minutes: Drupal.t("Minutes"),
        hours: Drupal.t("Hours"),
        days: Drupal.t("Days"),
        weeks: Drupal.t("Week"),
        months: Drupal.t("Months"),
        years: Drupal.t("Years"),

        /* message popup */
        message_ok: Drupal.t("OK"),
        message_cancel: Drupal.t("Cancel"),

        /* constraints */
        section_constraint: Drupal.t("Constraint"),
        constraint_type: Drupal.t("Constraint type"),
        constraint_date: Drupal.t("Constraint date"),
        asap: Drupal.t("As Soon As Possible"),
        alap: Drupal.t("As Late As Possible"),
        snet: Drupal.t("Start No Earlier Than"),
        snlt: Drupal.t("Start No Later Than"),
        fnet: Drupal.t("Finish No Earlier Than"),
        fnlt: Drupal.t("Finish No Later Than"),
        mso: Drupal.t("Must Start On"),
        mfo: Drupal.t("Must Finish On"),

        /* resource control */
        resources_filter_placeholder: Drupal.t("type to filter"),
        resources_filter_label: Drupal.t("hide empty"),
      }
    }
  }

  setConfig(config) {
    if (Object.keys(config).length) {
      for (const key_config in config) {
        this.Gantt[key_config] = config[key_config];
      }
    }
  }

  onSchedulePlanned(Gantt = this.Gantt) {
    let listSchedulePlanned = [];
    const schedulePlanned = (task) => {
      if (!task.planned_start || !task.planned_end) return
      let link_source = task.hasOwnProperty('$source') ? task.$source : [];
      if (link_source.length) {
        link_source.forEach(function (id_link) {
          if (Gantt.isLinkExists(id_link)) {
            const link = Gantt.getLink(id_link)
            let task_target = Gantt.getTask(link.target)
            if (!task_target.planned_start || !task_target.planned_end) return
            if (!link.hasOwnProperty('lag') || typeof link.lag === null) {
              link.lag = 0
            }
            const lag = link.lag;
            let date_schedule = null;
            let diff_time = 0;
            switch (link.type) {
              case '1':
                date_schedule = Gantt.calculateEndDate({start_date: task.planned_start, unit: Gantt.config.duration_unit, duration: Number(lag)})
                diff_time = Gantt.calculateDuration({start_date: task_target.planned_start, end_date: date_schedule})
                if (diff_time === 0) return;
                break;

              case '2':
                date_schedule = Gantt.calculateEndDate({start_date: task.planned_end, unit: Gantt.config.duration_unit, duration: Number(lag)})
                diff_time = Gantt.calculateDuration({start_date: task_target.planned_end, end_date: date_schedule})
                if (diff_time === 0) return;
                break;

              case '3':
                date_schedule = Gantt.calculateEndDate({start_date: task.planned_start, unit: Gantt.config.duration_unit, duration: Number(lag)})
                diff_time = Gantt.calculateDuration({start_date: task_target.planned_end, end_date: date_schedule})
                if (diff_time === 0) return;
                break;

              default:
                date_schedule = Gantt.calculateEndDate({start_date: task.planned_end, unit: Gantt.config.duration_unit, duration: Number(lag)})
                diff_time = Gantt.calculateDuration({start_date: task_target.planned_start, end_date: date_schedule})
                if (diff_time === 0) return;
            }
            if (diff_time !== 0) {
              let target_duration = Gantt.calculateDuration({start_date: task_target.planned_start, end_date: task_target.planned_end})
              let closest_work = null;
              switch (link.type) {
                case '2':
                case '3':
                  closest_work =  Gantt.getClosestWorkTime({date: date_schedule, dir: "past", unit: "minute"});
                  task_target.planned_end = closest_work
                  task_target.planned_start = Gantt.calculateEndDate({start_date: task_target.planned_end, unit: Gantt.config.duration_unit, duration: -target_duration})
                  break;

                default:
                  closest_work =  Gantt.getClosestWorkTime({date: date_schedule, dir: "future", unit: "minute"});
                  task_target.planned_start = closest_work
                  task_target.planned_end = Gantt.calculateEndDate({start_date: task_target.planned_start, unit: Gantt.config.duration_unit, duration: target_duration})
              }

              Gantt.refreshTask(task_target.id);
              Gantt.message(Drupal.t("<b>@task_target</b> is scheduled schedule has been rescheduled to @date due to @task_source restrictions.", {"@date": Gantt.date.date_to_str("%Y-%m-%d %H:%i")(task_target.planned_start), "@task_target": task_target.text || "", "@task_source": task.text || ""}))
              listSchedulePlanned.push({
                id: task_target.id,
                planned_start: task_target.planned_start,
                planned_end: task_target.planned_end
              })
            }
            schedulePlanned(task_target)
          }
        })
      }
    }

    Gantt.attachEvent("onAfterLinkAdd", function(id, link){
      if (Gantt.config.auto_scheduling) {
        const task = Gantt.getTask(link.source)
        schedulePlanned(task)
        link.listSchedulePlanned = [...listSchedulePlanned]
      }
      listSchedulePlanned = [];
      return true;
    });

    Gantt.attachEvent("onAfterLinkUpdate", function(id, link){
      if (Gantt.config.auto_scheduling) {
        const task = Gantt.getTask(link.source)
        schedulePlanned(task)
        link.listSchedulePlanned = [...listSchedulePlanned]
      }
      listSchedulePlanned = [];
      return true;
    });

    Gantt.attachEvent("onLightboxSave", function(id, task, is_new) {
      if (!is_new && Gantt.config.auto_scheduling) {
        schedulePlanned(task)
        task.listSchedulePlanned = [...listSchedulePlanned]
        listSchedulePlanned = [];
      }
      return true;
    })
  }

  setPlugin(plugins) {
    if (!Object.keys(plugins).length) {
      plugins = {
        quick_info: false,
        keyboard_navigation: true,
        click_drag: true,
        fullscreen: true,
        marker: true,
        critical_path: true,
        tooltip: true,
        multiselect: true,
        auto_scheduling: true,
        drag_timeline: true,
        grouping: true,
        overlay: true,
        undo: true,
        export_api: true,
      }
    }

    if (this.settingsGantt.input_search_text) {
      plugins.keyboard_navigation = false;
    }

    this.Gantt.plugins(plugins);

    if (plugins.drag_timeline) {
      this.Gantt.config.drag_timeline = {
        ignore: "",
        useKey: "ctrlKey"
      };

    }
  }

  modalDashboard($this = this) {
    let Gantt = this.Gantt
    let settingsGantt = this.settingsGantt;
    let optionsControl = settingsGantt.control_bar_label;
    let options = this.options;
    Gantt.attachEvent('onGanttReady', function () {
      let elGantt = Gantt.$root.closest('.gantt-wrapper')
      if (elGantt.querySelector('[data-action="dashboard"]')) {
        elGantt.querySelector('[data-action="dashboard"]').addEventListener('click', function () {
          if (settingsGantt.use_cdn) {
            delete optionsControl.auto_type
            delete optionsControl.auto_schedule
            delete optionsControl.critical_path
            delete optionsControl.click_drag
            delete optionsControl.drag_project
            delete optionsControl.hide_weekend_scale
            delete optionsControl.highlight_drag_task
            delete optionsControl.show_slack
          }

          let content = '<div class="wrapper-dashboard form-switch">';

          for (const key in optionsControl) {
            const checked = options[key] || Number.isInteger(options[key]) ? 'checked' : '';
            content += `
            <div class="form-check d-inline-block">
              <input type="checkbox" ${checked} data-action="${key}" id="${key}" autocomplete="off" class="form-check-input" />
              <label for="${key}" class="form-check-label" >${optionsControl[key]}</label>
            </div>
            `;
          }
          content += '</div>'

          const endPopup = () => {
            modal = null;
          }

          let modal = Gantt.modalbox({
            title: Drupal.t('Dashboard'),
            text: content,
            buttons: [
              {label: Drupal.t('Close'), css: "link-cancel-btn", value: "cancel"}
            ],
            width: "760px",
            type: "popup-css-class-here",
            callback: function (result) {
              switch (result) {
                case "cancel":
                  endPopup();
                  break;
              }
            }
          });

          let buttons = document.querySelectorAll('.wrapper-dashboard input[data-action]');
          if (buttons.length) {
            for (let i = 0; i < buttons.length; i++) {
              buttons[i].onclick = function() {
                switch (this.dataset['action']) {
                  case 'auto_type':
                    $this.onAutoType(this.checked)
                    break;

                  case 'auto_schedule':
                    $this.onAutoSchedule(this.checked)
                    break;

                  case 'critical_path':
                    $this.onHighlightCriticalPath(this.checked)
                    break;

                  case 'drag_project':
                    $this.onDragProject(this.checked)
                    break;

                  case 'click_drag':
                    $this.onClickDrag(this.checked)
                    break;

                  case 'round_dnd_dates':
                    $this.onMinimumStep(this.checked)
                    break;

                  case 'hide_weekend_scale':
                    $this.onHideNotWorkingTime(this.checked)
                    break;

                  case 'show_column_wbs':
                    $this.showColumnWBS(this.checked)
                    break;

                  case 'lock_completed_task':
                    if (!settingsGantt.native_dialog) {
                      $this.onLookTaskComplete(this.checked)
                    }
                    break;

                  case 'highlight_drag_task':
                    $this.highLightDragTask(this.checked)
                    break;

                  case 'dynamic_progress':
                    $this.onDynamicProgress(this.checked)
                    break;

                  case 'show_slack':
                    $this.showSlack(this.checked)
                    break;

                  case 'progress_text':
                    $this.onProgressText(this.checked)
                    break;
                  default:
                    endPopup()
                }
                $this.render()
              }
            }
          }
        })
      }
    })
  }

  formatMinuteTime(duration) {
    const minute_duration = duration;
    let minute = minute_duration % 60;
    let hour = Math.floor(minute_duration / 60 % 24);
    let day = Math.floor(minute_duration / 60 / 24);
    let result = [];
    if (day) {
      day = Drupal.t("@dayd", { '@day': day})
      result.push(day)
    }
    if (hour) {
      hour = Drupal.t("@hourh", { '@hour': hour})
      result.push(hour)
    }
    if (minute) {
      minute = Drupal.t("@minutem", { '@minute': minute})
      result.push(minute)
    }
    return result.join(' ')
  }

  formatDuration(value = 'day', $this = this, Gantt = this.Gantt) {
    if (value === 'minute') {
      let column_duration = Gantt.config.columns.find(item => item.name === 'duration');
      if (column_duration) {
        column_duration.template = function(task) {
          return $this.formatMinuteTime(task.duration)
        };
      }
    }
  }

  onScrollToDay(Gantt = this.Gantt, settingsGantt = this.settingsGantt) {
    Gantt.attachEvent('onGanttReady', function () {
      setTimeout(function () {
        let date = new Date();
        const visibleTimelineWidth = Gantt.$task.offsetWidth / 2;
        const position = Gantt.posFromDate(date) - visibleTimelineWidth;
        Gantt.scrollTo(position);
        Gantt.message(Drupal.t("Scrolled to @today", {"@today" : Gantt.date.date_to_str("%Y-%m-%d %H:%i")(date)}));
      }, 1000)
    })
  }

  searchDate(Gantt = this.Gantt) {
    const genCondition = (value_start, value_end) => {
      if (value_start && value_end) {
        return '(task.start_date.valueOf() >= value_start.valueOf() && task.start_date.valueOf() < value_end.valueOf()) || (task.end_date.valueOf() > value_start.valueOf() && task.end_date.valueOf() < value_end.valueOf()) || (task.start_date.valueOf() <= value_start.valueOf() && task.end_date.valueOf() > value_end.valueOf())'
      }
      if (value_start) {
        return '(task.start_date.valueOf() >= value_start.valueOf()) || (task.end_date.valueOf() > value_start.valueOf())'
      }
      if (value_end) {
        return '(task.start_date.valueOf() < value_end.valueOf()) || (task.end_date.valueOf() < value_end.valueOf())'
      }
      return null;
    }
    const filterLogic = (task, match = false) => {
      // check children
      Gantt.eachTask(function (child) {
        if (filterLogic(child)) {
          match = true;
        }
      }, task.id);

      // check task
      if (eval(condition)) {
        if (!data_storage.includes(task.id)) {
          data_storage.push(task.id);
        }
        match = true;
      }
      return match;
    }

    let data_storage = [];
    let condition = null;
    let value_start = null;
    let value_end = null;
    Gantt.attachEvent("onGanttReady", function () {
      let elGantt = Gantt.$root.closest('.gantt-wrapper')
      let buttons = elGantt.querySelectorAll('[data-action^="date"]');
      if (buttons.length) {
        for (let i = 0; i < buttons.length; i++) {
          buttons[i].addEventListener('change', Drupal.debounce(function(e) {
            let date_position = e.target.dataset['action'];
            let date_value_left;
            value_start = null;
            value_end = null;
            if (date_position === 'date-start') {
              value_start = e.target.value ? new Date(e.target.value + " 00:00:00") : null
              date_value_left = elGantt.querySelector('[data-action="date-end"]') || null
              if (date_value_left) {
                value_end = date_value_left.value ? Gantt.date.add(new Date(date_value_left.value + " 00:00:00"), 1, 'day') : null;
              }
            }
            else {
              value_end = e.target.value ? Gantt.date.add(new Date(e.target.value + " 00:00:00"), 1, 'day') : null;
              date_value_left = elGantt.querySelector('[data-action="date-start"]') || null
              if (date_value_left) {
                value_start = date_value_left.value ? new Date(date_value_left.value + " 00:00:00") : null;
              }
            }
            condition = genCondition(value_start, value_end);
            data_storage = [];
            Gantt.refreshData();
            Gantt.config.gantt_data_filter = data_storage;
          }, 500));
        }
      }
    });

    Gantt.attachEvent("onBeforeTaskDisplay", function (id, task) {
      if (!condition) {
        return true;
      }
      return filterLogic(task);
    });
  }

  onMoveRowAction(value = true, Gantt = this.Gantt) {
    if (!value && !Gantt.checkEvent('onBeforeRowDragMove')) {
      Gantt.attachEvent("onBeforeRowDragMove", function(id, parent, tindex) {
        return false;
      }, {id: 'on_move_action'});
    }
    else {
      Gantt.detachEvent('on_move_action')
    }
  }

  groupSource($this = this, Gantt = this.Gantt, settingsGantt = this.settingsGantt) {
    Gantt.attachEvent('onGanttReady', function () {
      let elGantt = Gantt.$root.closest('.gantt-wrapper')
      let button = elGantt.querySelector('[data-action="group"]')
      if (settingsGantt.use_cdn || !settingsGantt.setting_resource.resource_group.length) {
        button.closest('li').remove();
      }
      else {
        button.addEventListener('change', function (e) {
          if (e.target.value) {
            Gantt.groupBy({
              groups: Gantt.serverList(e.target.value),
              relation_property: e.target.value,
              group_id: "key",
              group_text: "label",
              default_group_label: Drupal.t("Unassigned")
            });
            Gantt.sort('order', false)
            if (!Gantt.checkEvent('onBeforeRowDragMove')) {
              $this.onMoveRowAction(false)
            }
          }
          else {
            Gantt.groupBy(false)
            Gantt.sort('order', false)
            $this.onMoveRowAction(false)
          }
        })
        settingsGantt.setting_resource.resource_group.forEach( field => {
          if (settingsGantt.setting_resource.resource.includes(field)) {
            const option = document.createElement("option")
            option.value = field
            option.text = settingsGantt.server_list_resource[field].label
            button.appendChild(option);
          }
        })
      }
    })
  }

  validateField(Gantt = this.Gantt, settingsGantt = this.settingsGantt) {
    const validateStorage = settingsGantt.validate_field || [];
    if (Object.keys(validateStorage).length) {
      Gantt.attachEvent("onLightbox", function (id) {
        const task = Gantt.getTask(id);
        if (task.$new) {
          if (validateStorage?.text?.default_value) {
            const description = Gantt.getLightboxSection('description');
            if (description) description.setValue(validateStorage.text.default_value);
          }
          if (settingsGantt.custom_field && validateStorage?.custom_field?.default_value) {
            const custom_field = Gantt.getLightboxSection('custom_field');
            if (custom_field) custom_field.setValue(validateStorage.custom_field.default_value);
          }
          if (validateStorage.hasOwnProperty('custom_resource') && Object.keys(validateStorage.custom_resource).length) {
            for (const key in validateStorage.custom_resource) {
              const custom_resource = Gantt.getLightboxSection(key);
              if (custom_resource && validateStorage.custom_resource[key].default_value) {
                custom_resource.setValue(validateStorage.custom_resource[key].default_value)
              }
            }
          }

          if (settingsGantt.priority && validateStorage?.priority?.default_value) {
            const priority = Gantt.getLightboxSection(settingsGantt.priority);
            if (priority) priority.setValue(validateStorage.priority.default_value);
          }

          const constraint = Gantt.getLightboxSection('constraint');
          if (constraint && validateStorage?.constraint) {
            if (validateStorage?.constraint?.default_value?.first) {
              let date = new Date(validateStorage.constraint.default_value.second) || null
              constraint.setValue(null, {constraint_type: validateStorage.constraint.default_value.first, constraint_date: date})
            }
          }

          const time = Gantt.getLightboxSection('time');
          if (time && validateStorage?.time) {
            if (validateStorage?.time?.default_value?.start) {
              let date = new Date(validateStorage.time.default_value.start) || null
              let date_end = new Date(validateStorage.time.default_value.end) || null
              time.setValue(null, {start_date: date, end_date: date_end})
            }
          }

          const time_planned = Gantt.getLightboxSection('baseline');
          if (time_planned && validateStorage?.time_planned) {
            if (validateStorage?.time_planned?.default_value?.start) {
              let date = new Date(validateStorage.time_planned.default_value.start) || null
              let date_end = new Date(validateStorage.time_planned.default_value.end) || null
              time_planned.setValue(null, {start_date: date, end_date: date_end})
            }
          }
        }
        return true
      })

      Gantt.attachEvent("onLightboxSave", function (id, task, is_new) {
        if (validateStorage.hasOwnProperty('text')) {
          if (task.text.length > validateStorage.text.max_length) {
            Gantt.alert(Drupal.t("Description cannot be too long"));
            return false;
          }
        }

        if (settingsGantt.custom_field && validateStorage.hasOwnProperty('custom_field')) {
          const label = validateStorage.custom_field.label;
          const required = validateStorage.custom_field.required;
          const max = validateStorage.custom_field.max;
          const min = validateStorage.custom_field.min;
          if (required && !Number.isInteger(parseInt(task.custom_field))) {
            Gantt.alert(Drupal.t("@label cannot be empty.", { '@label': label }));
            return false;
          }
          if (min && max) {
            if (min < task.custom_field && task.custom_field <= max) {
              Gantt.alert(Drupal.t("@label value must be greater than @min and less than @max", { '@label': label, '@max': max, '@mix': min }));
              return false;
            }
          }
          else if (min && typeof max === 'null') {
            Gantt.alert(Drupal.t("@label value must be greater than @min", { '@label': label, '@mix': min }));
            return false;
          }
          else if (max && typeof min === 'null') {
            Gantt.alert(Drupal.t("@label value must be less than @max", { '@label': label, '@max': max }));
            return false;
          }
        }

        if (validateStorage.hasOwnProperty('custom_resource')) {
          for (const idKey in validateStorage.custom_resource) {
            const label = validateStorage.custom_resource[idKey].label;
            const required = validateStorage.custom_resource[idKey].required;
            const cardinality = validateStorage.custom_resource[idKey].cardinality;
            if (task.hasOwnProperty(idKey)) {
              const data = task[idKey];
              if (required && data && data.length === 0) {
                Gantt.alert(Drupal.t("@label cannot be empty.", { '@label': label }));
                return false
              }
              if (cardinality !== -1 && data && data.length > cardinality) {
                Gantt.alert(Drupal.t("@label Choose up to @cardinality.", { '@label': label, '@cardinality': cardinality }));
                return false
              }
            }
          }
        }
        return true;
      })
    }
  }

  requiredParent(Gantt = this.Gantt, settingsGantt = this.settingsGantt) {
    if (settingsGantt.required_parent) {
      Gantt.attachEvent("onLightboxSave", function (id, task, is_new) {
        if (!task.parent) {
          Gantt.alert(Drupal.t("Parent task is required"));
          return false;
        }
        return true;
      })
    }
  }

  setTooltip($this= this, Gantt = this.Gantt, settingsGantt = this.settingsGantt) {

    // Set format date tooltips.
    Gantt.templates.tooltip_date_format = function (date) {
      let formatFunc = Gantt.date.date_to_str(settingsGantt.date_format);
      return formatFunc(date);
    };

    // Setup tooltip content.
    Gantt.templates.tooltip_text = function (start, end, task) {
      let text_end = Gantt.templates.tooltip_date_format(new Date(end.valueOf()));
      // Option Last of the day.
      if (settingsGantt.last_of_the_day && Gantt.config.duration_unit === 'day') {
        text_end = Gantt.templates.tooltip_date_format(new Date(end.valueOf() - 1));
      }

      let duration = Drupal.t('@days days', {'@days': Gantt.calculateDuration(task)});
      if(Gantt.config.duration_unit === 'minute') {
        duration = $this.formatMinuteTime(Gantt.calculateDuration(task))
      }

      let creator = Drupal.t("Unassigned");
      if (typeof task !== 'undefined' && task.hasOwnProperty('creator')) {
        let result = [];

        if (settingsGantt.creator.length && !Gantt.serverList(settingsGantt.creator).length) {
          Gantt.serverList(settingsGantt.creator, settingsGantt.server_list_resource[settingsGantt.creator].data)
        }
        const serverList = Gantt.serverList(settingsGantt['creator']);
        if (task.creator.length && serverList.length) {
          task.creator.forEach(id => {
            let find = serverList.find(item => item.key == id);
            if (find) {
              result.push(find.label)
            }
          })
        }
        if (result.length) {
          creator = result.join(", ")
        }
      }
      let html = [];
      html.push(Drupal.t('@label: @content', {'@label': $this.i18n.labels.column_text, '@content': task.text}))
      html.push(Drupal.t('@label: @content', {'@label': task.type === 'milestone' ? 'Milestone' : $this.i18n.labels.column_start_date, '@content': Gantt.templates.tooltip_date_format(start)}))
      if (!(task.type === 'milestone')) {
        html.push(Drupal.t('@label: @content', {'@label': $this.i18n.labels.column_end_date, '@content': text_end}))
        html.push(Drupal.t('@label: @content', {'@label': $this.i18n.labels.column_duration, '@content': duration}))
      }
      html.push(Drupal.t('Creator: @creator', {'@creator': creator}))
      return html.join('<br>')
    };
  }

  onPermissionTaskLink(Gantt = this.Gantt) {
    Gantt.attachEvent("onLightboxSave", function (id, task, is_new) {
      if (Gantt.isReadonly(id)) {
        Gantt.alert(Drupal.t("You cannot edit this task."));
        return false;
      }
      return true;
    })

    Gantt.attachEvent("onLightboxDelete", function (id, task, is_new) {
      if (Gantt.isReadonly(id)) {
        Gantt.alert(Drupal.t("You cannot delete this task."));
        return false;
      }
      return true;
    })

    Gantt.attachEvent("onLinkDblClick", function(id, e) {
      let link = Gantt.getLink(id);
      if (link.readonly) {
        Gantt.message({
          type: 'error',
          text: Drupal.t("You do not have permission to edit this link."),
          expire: 4000
        });
        return false;
      }
      return true;
    })
  }

  onShowEndColumn(Gantt = this.Gantt) {
    let indexCol = Gantt.config.columns.findIndex((item) => item.name === 'start_date');
    Gantt.config.columns.splice(indexCol + 1, 0, {
      name: "end_date",
      width: 80,
      label: this.i18n.labels.column_end_date,
      align: "center",
      resize: true
    });
  }

  onShowProgressColumn(Gantt = this.Gantt) {
    let indexCol = Gantt.config.columns.findIndex((item) => item.name === 'duration');
    Gantt.config.columns.splice(indexCol + 1, 0, {
      name: "progress",
      width: 80,
      label: this.i18n.labels.column_progress,
      align: "center",
      resize: true,
      template: function (task) {
        let progress = (task.progress || 0) * 100
        return `${progress.toFixed(0)}%`;
      }
    });
  }

  limitCreateLink(Gantt = this.Gantt) {
    Gantt.attachEvent("onBeforeLinkAdd", function(id, link) {
      if (Gantt.isLinkExists(link.source + "-" + link.target)) {
        Gantt.message({
          text: Drupal.t("A link already exists"),
          expire: 4000
        });
        return false;
      }
      return true;
    });
  }

  hideShowColumn($this = this, Gantt = this.Gantt) {
    const id_gantt = this.id_gantt;
    let colHeader = `
        <div class="gantt-dropdown" onclick="controlColumns('${id_gantt}', this)">&#9660;</div>
      `;
    Gantt.config.columns.push({
      name: "control_columns",
      label: colHeader,
      width: 25,
      min_width: 25,
      max_width: 25,
      sort: false,
      hide: false,
      template: () => {
        return '';
      }
    })

    let hideColumnStorage = window.localStorage.getItem('hide_column_' + id_gantt)
    hideColumnStorage = JSON.parse(hideColumnStorage);
    let i18n = Gantt.i18n.getLocale(drupalSettings.path.currentLanguage)
    Gantt.config.columns = Gantt.config.columns.filter(item => {
      if (!item?.label && i18n.labels.hasOwnProperty("column_" + item.name)) {
        item.label = i18n.labels["column_" + item.name];
      }
      if (hideColumnStorage && hideColumnStorage[item.name]) item.hide = true;
      return true;
    })
  }

  importMMP(urlImport, Gantt = this.Gantt) {
    const upload = (Gantt, file, callback, urlImport) => {
      Gantt.importFromMSProject({
        data: file,
        taskProperties: [
          "Summary",
          "Milestone",
        ],
        callback: async function (project) {
          if (project) {
            Gantt.clearAll();
            // Mark tasks import.
            for (let i = 0; i < project.data.data.length; i++) {
              project.data.data[i].isImport = true;
            }
            let data = jQuery.extend(true, {}, project?.data || {data: [], links: []})
            Gantt.config.readonly = true
            Gantt.parse(data);
            await jQuery.post(urlImport, data).done(function (result) {
              if (data?.data.length) {
                data.data.forEach((item) => {
                  delete item.isImport
                  const id_mapping = item.id
                  item.id = result['data'][id_mapping]['id']
                  item.parent = result['data'][id_mapping]['parent']
                })
              }
              if (data?.links.length && Object.keys(result['links'])) {
                data.links.forEach((item) => {
                  const id_mapping = item.id
                  item.id = result['links'][id_mapping]['id']
                  item.source = result['links'][id_mapping]['source']
                  item.target = result['links'][id_mapping]['target']
                })
              }
              console.log("Data imported");
            })
              .fail(function (xhr, status, error) {
                console.error("Import failed:", error);
                Gantt.message({
                  type: "error",
                  text: Drupal.t('Import failed @message!', { '@status': xhr.responseText }),
                  expire: 4000
                })
              });
            Gantt.clearAll();
            Gantt.config.readonly = false
            Gantt.parse(data);
            if (callback) {
              callback(project);
            }
          }
        }
      });
    }

    Gantt.attachEvent("onTaskLoading", function (task) {
      if (task.$custom_data) {
        if (task.$custom_data.Summary === "1") {
          task.type = "project";
        }
        if (task.$custom_data.Milestone === "1" || task.duration == "0") {
          task.type = "milestone";
        }
      }
      return true;
    });

    // Import microsoft project.
    Gantt.attachEvent('onGanttReady', function () {
      let fileDnD = fileDragAndDrop();
      fileDnD.init(Gantt.$container);

      const sendFile = (file) => {
        fileDnD.showUpload();
        upload(Gantt, file, function () {
          fileDnD.hideOverlay();
        }, urlImport)
      }

      fileDnD.onDrop(sendFile);
      // Manual Upload file MPP.
      let elGantt = Gantt.$root.closest('.gantt-wrapper');
      let submitFile = elGantt.querySelector("#mspImportBtn");
      if (submitFile) {
        submitFile.addEventListener('click', function (event) {
          event.preventDefault();
          let fileInput = elGantt.querySelector("#mspFile");
          if (fileInput.files[0]) {
            sendFile(fileInput.files[0]);
          }
        });
      }
    })
  }

  onNativeDialog(Gantt = this.Gantt) {
    let settingsGantt = this.settingsGantt;
    const urlGen = (url, param = []) => {
      let regex = /-arg_[0-9]-/g;
      let args = url.match(regex);
      args.forEach(function (element, index) {
        url = url.replace(element, param[index])
      })
      return url;
    }

    Gantt.attachEvent("onTaskDblClick", function (id, e) {
      if (id == null) {
        return false;
      }
      let task = Gantt.getTask(id);
      if (task['type'] === 'project') {
        return false;
      }
      else {
        let url = task.link_detail;
        if (url === undefined) {
          url = settingsGantt.link_detail.edit;
          if (task.hasOwnProperty("readonly")) {
            url = settingsGantt.link_detail.display;
          }
          url = urlGen(url, [id]);
        }
        let ajaxSettings = {
          url: url,
          dialogType: 'modal',
          dialog: {width: '80%'},
        };
        Drupal.ajax(ajaxSettings).execute();
        return false;
      }
    });

    Gantt.attachEvent("onBeforeLightbox", function (id) {
      let task = Gantt.getTask(id);
      if (task == null) {
        return false;
      }
      if (task.$new) {
        if (settingsGantt.add_task !== true) {
          Gantt.deleteTask(task.id);
          return false;
        }
        let add_link = settingsGantt.add_link + '&parent=' + task.parent + '&parent_field=' + settingsGantt.parent_field;
        let ajaxSettings = {
          url: add_link,
          dialogType: 'modal',
          dialog: {width: '80%'},
        };
        Drupal.ajax(ajaxSettings).execute();
        Gantt.deleteTask(task.id);
        return false;
      }
      return true;
    });

  }

  onAddTask(Gantt = this.Gantt) {
    Gantt.config.columns = Gantt.config.columns.filter(item => item.name !== 'add');
  }

  onReadonly(Gantt = this.Gantt) {
    Gantt.config.readonly = true
  }

  toggleGridChart(Gantt = this.Gantt) {
    Gantt.attachEvent("onGanttReady", function() {
      let elGantt = Gantt.$root.closest('.gantt-wrapper');
      let buttons = elGantt.querySelectorAll('[data-action="toggle_grid"], [data-action="toggle_chart"]');
      if (buttons.length) {
        for (let i = 0; i < buttons.length; i++) {
          buttons[i].onclick = function() {
            if (this.dataset['action'] === 'toggle_grid') {
              Gantt.config.show_grid = !Gantt.config.show_grid;
            }
            else {
              Gantt.config.show_chart = !Gantt.config.show_chart;
            }
            Gantt.render()
          }
        }
      }
    })
  }

  exportTo($this = this, Gantt = this.Gantt, settingsGantt = this.settingsGantt) {
    const addContentCss = async (config) => {
      const { content_css, link_css } = Gantt.config;
      const addStyleToHeader = (style) => {
        config.header = (config.header || '') + '<style>' + style + '</style>';
      };

      if (content_css && content_css.length) {
        addStyleToHeader(content_css);
      } else {
        const response = await jQuery.get(decodeURIComponent(link_css)).catch(() => '');
        if (response.length) {
          Gantt.config.content_css = response;
          addStyleToHeader(response);
        }
      }
    };

    Gantt.attachEvent("onGanttReady", function() {
      let elGantt = Gantt.$root.closest('.gantt-wrapper');
      if(elGantt.querySelector('[data-action="export"]')) {
        elGantt.querySelector('[data-action="export"]').addEventListener('click', function () {
          let content = `
            <div class="d-inline-block">
              <button class="gantt_btn_set gantt_left_btn_set text-nowrap" data-action="excel" type="button"><i class="bi bi-filetype-xlsx"></i> ${Drupal.t('Export excel')}</button>
              <button class="gantt_btn_set gantt_left_btn_set text-nowrap" data-action="msproject" type="button"><i class="bi bi-bar-chart-steps"></i> ${Drupal.t('Export MSProject')}</button>
              <button class="gantt_btn_set gantt_left_btn_set text-nowrap" data-action="ical" type="button"><i class="bi bi-calendar-week"></i> ${Drupal.t('Export calendar')}</button>
              <button class="gantt_btn_set gantt_left_btn_set text-nowrap" data-action="pdf" type="button"><i class="bi bi-filetype-pdf"></i> ${Drupal.t('Export pdf')}</button>
              <button class="gantt_btn_set gantt_left_btn_set text-nowrap" data-action="png" type="button"><i class="bi bi-filetype-png"></i> ${Drupal.t('Export png')}</button>
            </div>
            <hr>
            <div>
              ${settingsGantt.calendar_ical}
            </div>
          `;

          const endPopup = () => {
            modal = null;
          }

          const convertDuration = (item_task, settingsGantt) => {
            if (!settingsGantt.use_cdn) {
              item_task.freeSlack = Gantt.config.duration_unit === 'minute' ? $this.formatMinuteTime(Gantt.getFreeSlack(item_task.id)) : Gantt.getFreeSlack(item_task.id)
              item_task.totalSlack = Gantt.config.duration_unit === 'minute' ? $this.formatMinuteTime(Gantt.getTotalSlack(item_task.id)) : Gantt.getTotalSlack(item_task.id)
            }

            item_task.duration = Gantt.config.duration_unit === 'minute' ? $this.formatMinuteTime(item_task.duration) : item_task.duration;
          }

          const convertProgress = (item_task) => {
            item_task.progress = Math.floor(item_task.progress * 100).toString() + '%';
          }

          const convertDate = (item_task, dateToStr) => {
            item_task.start_date = item_task.start_date ? dateToStr(item_task.start_date) : null
            item_task.end_date = item_task.end_date ? dateToStr(item_task.end_date) : null
            item_task.planned_start = item_task.planned_start ? dateToStr(item_task.planned_start) : null
            item_task.planned_end = item_task.planned_end ? dateToStr(item_task.planned_end) : null
          }

          const convertResource = (item_task, settingsGantt) => {
            item_task.wbs = Gantt.getWBSCode(item_task)

            let serverListPriority = Gantt.serverList('priority')
            if(item_task.hasOwnProperty('priority') && item_task.priority.length && serverListPriority) {
              let resource = serverListPriority.find(resource => resource.key == item_task.priority);
              if (resource) {
                item_task.priority = resource.label;
              }
            }

            for (const key in settingsGantt.server_list_resource) {
              let serverList = Gantt.serverList(key)
              let text = [];
              if(item_task.hasOwnProperty(key) && item_task[key].length && serverList) {
                item_task[key].forEach(item => {
                  let resource = serverList.find(resource => resource.key == item);
                  if (resource) {
                    text.push(resource.label)
                  }
                })
              }
              if (text.length) {
                item_task[key] = text.join(', ');
              }
            }
          }

          const convertDataExport = (Gantt, settingsGantt, action) => {
            let data = [];
            let links = [];
            switch (action) {
              case 'excel':
                Gantt.eachTask(function (task) {
                  let item_task = {...task};
                  if (typeof Gantt.config.gantt_data_filter !== 'undefined') {
                    if (Gantt.config.gantt_data_filter.includes(task.id)) {
                      convertResource(item_task, settingsGantt)
                      convertDate(item_task, Gantt.date.date_to_str("%d/%m/%Y %H:%i"))
                      convertDuration(item_task, settingsGantt)
                      convertProgress(item_task)
                      data.push(item_task);
                    }
                  }
                  else {
                    convertResource(item_task, settingsGantt)
                    convertDate(item_task, Gantt.date.date_to_str("%d/%m/%Y %H:%i"))
                    convertDuration(item_task, settingsGantt)
                    convertProgress(item_task)
                    data.push(item_task);
                  }
                })
                break;
              case 'msproject':
                Gantt.eachTask(function (task) {
                  let item_task = {...task};

                  let link_source = item_task.hasOwnProperty('$source') ? item_task.$source : [];
                  if (typeof Gantt.config.gantt_data_filter !== 'undefined') {
                    link_source.forEach(function (id_link) {
                      if (Gantt.isLinkExists(id_link)) {
                        let link = Gantt.getLink(id_link)
                        let task_target = Gantt.getTask(link.target)
                        if (Gantt.config.gantt_data_filter.includes(task_target.id)) {
                          links.push(link)
                        }
                      }
                    })
                  }
                  else {
                    link_source.forEach(function (id_link) {
                      if (Gantt.isLinkExists(id_link)) {
                        let link = Gantt.getLink(id_link)
                        links.push(link)
                      }
                    })
                  }

                  if (typeof Gantt.config.gantt_data_filter !== 'undefined') {
                    if (Gantt.config.gantt_data_filter.includes(task.id)) {
                      convertResource(item_task, settingsGantt)
                      convertDate(item_task, Gantt.date.date_to_str("%d-%m-%Y %H:%i"))
                      data.push(item_task);
                    }
                  }
                  else {
                    convertResource(item_task, settingsGantt)
                    convertDate(item_task, Gantt.date.date_to_str("%d-%m-%Y %H:%i"))
                    data.push(item_task);
                  }
                })
                break;
              default:
                Gantt.eachTask(function (task) {
                  let item_task = {...task};
                  if (typeof Gantt.config.gantt_data_filter !== 'undefined') {
                    if (Gantt.config.gantt_data_filter.includes(task.id)) {
                      data.push(item_task);
                    }
                  }
                  else {
                    data.push(item_task);
                  }
                })
            }
            return {
              data: data,
              links: links,
            };
          }

          let modal = Gantt.modalbox({
            title: Drupal.t('Export'),
            text: content,
            buttons: [
              {label: Drupal.t('Close'), css: "link-cancel-btn", value: "cancel"}
            ],
            width: "760px",
            type: "popup-css-class-here",
            callback: function (result) {
              switch (result) {
                case "cancel":
                  endPopup();
                  break;
              }
            }
          });

          let buttons = document.querySelectorAll('[data-action="excel"],' +
            '[data-action="msproject"],' +
            '[data-action="ical"],' +
            '[data-action="pdf"],' +
            '[data-action="png"]');
          if (buttons.length) {
            for (let i = 0; i < buttons.length; i++) {
              buttons[i].onclick = async function() {
                let config = {
                  name: document.title,
                  locale: navigator.language
                }
                switch (this.dataset['action']) {
                  case 'excel':
                    const data_export = convertDataExport(Gantt, settingsGantt, 'excel')
                    config.name += '.xlsx'
                    config.data = data_export.data
                    let columns = [];
                    Gantt.config.columns.forEach(function (col) {
                      if (!['add', 'buttons', 'control_columns'].includes(col.name)) {
                        let obj = {
                          id: col.name,
                          header: col.label,
                        }
                        if (['duration', 'custom_field', 'freeSlack', 'totalSlack'].includes(col.name)) {
                          obj.type = Gantt.config.duration_unit === 'minute' ? 'string' : 'number'
                        }
                        if (['start_date', 'end_date', 'planned_start', 'planned_end'].includes(col.name)) {
                          obj.type = 'date'
                        }
                        columns.push(obj)
                      }
                    })
                    columns.push({
                      id: 'progress',
                      header: 'Progress',
                    })
                    config.columns = columns;
                    Gantt.exportToExcel(config);
                    break;

                  case 'msproject':
                    config.name += '.xml'
                    config.data = convertDataExport(Gantt, settingsGantt, 'msproject')
                    Gantt.exportToMSProject(config);
                    break;

                  case 'ical':
                    config.name += '.ics'
                    Gantt.exportToICal(config);
                    break;

                  case 'pdf':
                    config.raw = true
                    config.name += '.pdf'
                    await addContentCss(config);
                    Gantt.exportToPDF(config);
                    break;

                  case 'png':
                    config.raw = true
                    config.name += '.png'
                    await addContentCss(config);
                    Gantt.exportToPNG(config);
                    break;

                  default:
                    Gantt.exportToExcel();
                }
              }
            }
          }
        })
      }
    });
  }

  copyTask(Gantt = this.Gantt, settingsGantt = this.settingsGantt) {
    let tasksToCopy = [];

    Gantt.ext.keyboardNavigation.addShortcut("ctrl+c", function (e) {
      tasksToCopy = [];
      Gantt.eachSelectedTask(function (task_id) {
        tasksToCopy.push(task_id);
      });
    }, "taskRow");

    Gantt.ext.keyboardNavigation.addShortcut("ctrl+v", function (e) {
      const newParent = Gantt.getSelectedId();

      Gantt.batchUpdate(function () {
        for (let i = 0; i < tasksToCopy.length; i++) {
          const task = Gantt.copy(Gantt.getTask(tasksToCopy[i]));
          task.id = +new Date() + '+' + Math.floor(Math.random() * 10);
          Gantt.addTask(task, newParent)
        }
        Gantt.getTask(newParent).$open = true;
      })
    }, "taskRow");
  }

  onInDentOutDent(Gantt = this.Gantt) {
    let settingsGantt = this.settingsGantt;
    Gantt.attachEvent("onGanttReady", function() {
      let elGantt = Gantt.$root.closest('.gantt-wrapper');
      let buttons = elGantt.querySelectorAll('[data-action="indent"], [data-action="outdent"], [data-action="del"]');
      if (buttons.length) {
        for (let i = 0; i < buttons.length; i++) {
          if (!settingsGantt.edit_task) {
            buttons[i].setAttribute('disable', '')
            continue;
          }
          buttons[i].onclick = function() {
            const action = this.dataset['action'];
            if (action === 'del') {
              Gantt.confirm(Drupal.t("This will delete all selected tasks and their subtasks. Are you sure you want to delete?"), function (result) {
                if (result) {
                  Gantt.performAction(action)
                }
              });
              return false;
            }
            Gantt.performAction(this.dataset['action'])
          }
        }
      }
    });

    const shiftTask = (task_id, direction) => {
      let task = Gantt.getTask(task_id);
      task.start_date = Gantt.date.add(task.start_date, direction, "day");
      task.end_date = Gantt.calculateEndDate(task.start_date, task.duration);
      Gantt.updateTask(task.id);
    }

    let actions = {
      indent: function indent(task_id) {
        let prev_id = Gantt.getPrevSibling(task_id);
        while (Gantt.isSelectedTask(prev_id)) {
          let prev = Gantt.getPrevSibling(prev_id);
          if (!prev) break;
          prev_id = prev;
        }
        if (prev_id) {
          let new_parent = Gantt.getTask(prev_id);
          Gantt.moveTask(task_id, Gantt.getChildren(new_parent.id).length, new_parent.id);
          new_parent.type = Gantt.config.types.project;
          new_parent.$open = true;
          Gantt.updateTask(task_id);
          Gantt.updateTask(new_parent.id);
          return task_id;
        }
        return null;
      },
      outdent: function outdent(task_id, initialIndexes, initialSiblings) {
        let cur_task = Gantt.getTask(task_id);
        let old_parent = cur_task.parent;
        if (Gantt.isTaskExists(old_parent) && old_parent !== Gantt.config.root_id) {
          let index = Gantt.getTaskIndex(old_parent) + 1;
          let prevSibling = initialSiblings[task_id].first;

          if(Gantt.isSelectedTask(prevSibling)){
            index += (initialIndexes[task_id] - initialIndexes[prevSibling]);
          }
          Gantt.moveTask(task_id, index, Gantt.getParent(cur_task.parent));
          if (!Gantt.hasChild(old_parent))
            Gantt.getTask(old_parent).type = Gantt.config.types.task;
          Gantt.updateTask(task_id);
          Gantt.updateTask(old_parent);
          return task_id;
        }
        return null;
      },
      del: function (task_id) {
        if(Gantt.isTaskExists(task_id) && !Gantt.isReadonly(task_id)) Gantt.deleteTask(task_id);
        return task_id;
      },
      moveForward: function (task_id) {
        shiftTask(task_id, 1);
      },
      moveBackward: function (task_id) {
        shiftTask(task_id, -1);
      }
    };
    let cascadeAction = {
      indent: true,
      outdent: true,
      del: true
    };

    Gantt.performAction = function (actionName) {
      let action = actions[actionName];
      if (!action) {
        return;
      }

      Gantt.batchUpdate(function () {
        // Need to preserve order of items on indent/outdent, remember order before changing anything:
        let indexes = {};
        let siblings = {};
        Gantt.eachSelectedTask(function (task_id) {
          Gantt.ext.undo.saveState(task_id, "task");
          indexes[task_id] = Gantt.getTaskIndex(task_id);
          siblings[task_id] = {
            first: null
          };

          let currentId = task_id;
          while(Gantt.isTaskExists(Gantt.getPrevSibling(currentId)) && Gantt.isSelectedTask(Gantt.getPrevSibling(currentId))){
            currentId = Gantt.getPrevSibling(currentId);
          }
          siblings[task_id].first = currentId;
        });

        let updated = {};
        Gantt.eachSelectedTask(function (task_id) {

          if (cascadeAction[actionName]) {
            if (!updated[Gantt.getParent(task_id)]) {
              let updated_id = action(task_id, indexes, siblings);

              updated[updated_id] = true;
            } else {
              updated[task_id] = true;
            }
          } else {
            action(task_id, indexes);
          }
        });
      });
    };
  }

  searchText(Gantt = this.Gantt) {
    let filterValue = "";

    Gantt.attachEvent("onGanttReady", function () {
      let elGantt = Gantt.$root.closest('.gantt-wrapper')
      if (elGantt.querySelector('[data-action="search"]')) {
        elGantt.querySelector('[data-action="search"]').addEventListener('input', Drupal.debounce(function(e) {
          filterValue = e.target.value;
          Gantt.refreshData();
          e.target.focus()
        }, 500));
      }
    });

    const filterLogic = (task, match = false) => {
      // check children
      Gantt.eachTask(function (child) {
        if (filterLogic(child)) {
          match = true;
        }
      }, task.id);

      // check task
      if (task.text.toLowerCase().indexOf(filterValue.toLowerCase()) > -1) {
        match = true;
      }
      return match;
    }

    Gantt.attachEvent("onBeforeTaskDisplay", function (id, task) {
      if (!filterValue) {
        return true;
      }
      return filterLogic(task);
    });
  }

  onButtonDetail(Gantt = this.Gantt, value = true) {
    if (!value) {
      Gantt.config.buttons_left = Gantt.config.buttons_left.filter(function (item) {
        return item !== 'button_detail'
      })
      Gantt.detachEvent("action_button_detail");
      Gantt.detachEvent("before_lightbox_button_detail");
      Gantt.resetLightbox()
      return true;
    }

    Gantt.locale.labels.detail_button = Drupal.t("Detail");
    Gantt.attachEvent("onLightboxButton", function (button_id, node, e) {
      if (button_id === "detail_button") {
        const id = Gantt.getState().lightbox;
        const task = Gantt.getTask(id)
        if (task?.link_detail && task.link_detail.length) {
          let ajaxSettings = {
            url: task.link_detail,
            dialogType: 'modal',
            dialog: {width: '80%'},
          };
          Drupal.ajax(ajaxSettings).execute();
          Gantt.hideLightbox();
        }
      }
    }, {id: "action_button_detail"});
    Gantt.attachEvent("onBeforeLightbox", function (id) {
      let task = Gantt.getTask(id);
      if (task.$new) {
        Gantt.config.buttons_left = Gantt.config.buttons_left.filter(item => item !== 'detail_button');
      }
      else {
        if (!Gantt.config.buttons_left.includes('detail_button')) {
          Gantt.config.buttons_left.push('detail_button')
        }
      }
      Gantt.resetLightbox()
      return true;
    }, {id: "before_lightbox_button_detail"});
    Gantt.resetLightbox()
  }

  onUndoRedo(Gantt = this.Gantt) {
    let settingsGantt = this.settingsGantt;
    Gantt.attachEvent("onGanttReady", function() {
      let elGantt = Gantt.$root.closest('.gantt-wrapper');
      let buttons = elGantt.querySelectorAll('[data-action="undo"], [data-action="redo"]')
      if (buttons.length) {
        for (let i = 0; i < buttons.length; i++) {
          if (!settingsGantt.edit_task) {
            buttons[i].setAttribute('disable', 'disabled')
            continue;
          }
          buttons[i].onclick = function() {
            const action = this.dataset['action'];
            if (!settingsGantt.edit_task) return;
            if (action === 'undo') {
              Gantt.undo()
            }
            else {
              Gantt.redo()
            }
          }
        }
      }
    });

    Gantt.attachEvent("onBeforeUndo", function (action) {
      if (action && action.commands[0].type === 'move') {
        const value = action.commands[0].value
        let task = Gantt.getTask(value.id)
        task.pre_order = Gantt.getTaskIndex(value.id)
        task.pre_parent = Gantt.getParent(value.id)
      }
      return true;
    });

    Gantt.attachEvent("onBeforeRedo", function (action) {
      if (action && action.commands[0].type === 'move') {
        const value = action.commands[0].value
        let task = Gantt.getTask(value.id)
        task.pre_order = Gantt.getTaskIndex(value.id)
        task.pre_parent = Gantt.getParent(value.id)
      }
      return true;
    });
  }

  onCollapse(Gantt = this.Gantt) {
    Gantt.attachEvent('onGanttReady', function () {
      let elGantt = Gantt.$root.closest('.gantt-wrapper');
      let buttons = elGantt.querySelectorAll('[data-action="open"], [data-action="close"]')
      if (buttons.length) {
        for (let i = 0; i < buttons.length; i++) {
          buttons[i].onclick = function () {
            let buttonsCollapse = document.querySelectorAll('[data-action="open"], [data-action="close"]')
            const action = this.dataset['action'];
            buttonsCollapse.forEach(button => {
              button.style.display = (button.dataset['action'] === action) ? 'none' : null
            })
            Gantt.batchUpdate(function () {
              Gantt.eachTask(function (task) {
                Gantt[action](task.id)
              });
            });
          }
        }
      }
    })
  }

  onFullScreen(Gantt = this.Gantt) {
    Gantt.attachEvent('onGanttReady', function () {
      let elGantt = Gantt.$root.closest('.gantt-wrapper')
      if (elGantt.querySelector('[data-action="fullscreen"]')) {
        elGantt.querySelector('[data-action="fullscreen"]').addEventListener('click', function () {
          Gantt.expand()
        })
      }
    })
  }

  onFullScreenInGantt(Gantt = this.Gantt) {
    Gantt.attachEvent("onTemplatesReady", function () {
      let toggle = document.createElement("i");
      toggle.className = "fa fa-expand gantt-fullscreen";
      Gantt.toggleIcon = toggle;
      Gantt.$container.appendChild(toggle);
      toggle.onclick = function() {
        Gantt.ext.fullscreen.toggle();
      };
    });
    Gantt.attachEvent("onExpand", function () {
      let icon = Gantt.toggleIcon;
      if (icon) {
        icon.className = icon.className.replace("fa-expand", "fa-compress");
      }

    });
    Gantt.attachEvent("onCollapse", function () {
      let icon = Gantt.toggleIcon;
      if (icon) {
        icon.className = icon.className.replace("fa-compress", "fa-expand");
      }
    });
  }

  onHandlerRequest(Gantt = this.Gantt) {
    // Attach event to handle updates before they are applied
    this.dp.attachEvent("onAfterUpdate", function (id, state, tid, $response) {
      if (Gantt.isTaskExists(tid) && state === 'inserted') {
        let task = Gantt.getTask(tid);
        if ($response.hasOwnProperty('link_detail')) {
          task.creator = [drupalSettings.user.uid]
          task.link_detail = $response.link_detail
          task.entity_type = $response.entity_type || null
          task.entity_bundle = $response.entity_bundle || null
          if ($response.hasOwnProperty('parent_id_entity')) {
            task.parent_id_entity = $response.parent_id_entity
          }
          if ($response.hasOwnProperty('parent_field_name_entity')) {
            task.parent_field_name_entity = $response.parent_field_name_entity
          }
          if ($response.hasOwnProperty('parent_type_entity')) {
            task.parent_type_entity = $response.parent_type_entity
          }
        }
      }
      return true;
    })
    this.dp.attachEvent("onBeforeUpdate", function (id, state, data) {
      if (Gantt.isTaskExists(id)) {
        let task = Gantt.getTask(id);
        if (task?.$virtual) {
          return false;
        }
      }
      if (data.parent && data.parent !== '0' && state === 'inserted') {
        let parentTask = Gantt.getTask(data.parent);
        if (parentTask && parentTask.hasOwnProperty("group_field") && (parentTask.group_field === 'parent_field_name' || parentTask.group_field === 'parent_id')) {
          data.group_field = parentTask.group_field;
        }
        if (parentTask.hasOwnProperty('entity_type')) {
          data.entity_type = parentTask.entity_type;
        }
        if (parentTask.hasOwnProperty('entity_bundle')) {
          data.entity_bundle = parentTask.entity_bundle;
        }
        if (parentTask.hasOwnProperty('parent_id_entity')) {
          data.parent_id_entity = parentTask.parent_id_entity;
        }
        if (parentTask.hasOwnProperty('parent_type_entity')) {
          data.parent_type_entity = parentTask.parent_type_entity;
        }
        if (parentTask.hasOwnProperty('parent_field_name_entity')) {
          data.parent_field_name_entity = parentTask.parent_field_name_entity;
        }
      }
      return true;
    })

    Gantt.attachEvent("onAjaxError", function (request) {
      Gantt.message({
        type: "error",
        text: Drupal.t('Error @status!', { '@status': request.status }),
        expire: 4000
      })
      return true;
    });
  }

  onPostOrder(Gantt = this.Gantt, settingsGantt = this.settingsGantt, value = true) {
    if (!value) {
      Gantt.detachEvent('pre_order');
      Gantt.detachEvent('post_order');
      return true;
    }

    // Attach event to store pre-order information before task move
    Gantt.attachEvent("onBeforeTaskMove", function(id, parent, tindex) {
      if (Gantt._sort || Gantt.getState()['group_mode']) {
        Gantt.message({
          type: "error",
          text: Drupal.t('Can not sort in this view'),
          expire: 4000
        })
        return false
      }

      let task = Gantt.getTask(id);
      task.pre_order = Gantt.getTaskIndex(id);
      task.pre_parent = Gantt.getParent(id);
      return true;
    }, {id: 'pre_order'});

    // Attach event to handle updates before they are applied
    this.dp.attachEvent("onBeforeUpdate", function (id, state, data) {
      if ((state === 'inserted' || state === 'order') && data.hasOwnProperty('start_date')) {
        let task = Gantt.getTask(id)
        data.order = Gantt.getTaskIndex(id)
        if (state === "inserted") {
          data.destination = settingsGantt.current_path
          return true;
        }
        if (state === "order") {
          let listOrder = [];
          if (data.parent === task.pre_parent) {
            let index_start = Math.min(data.order, task.pre_order);
            let index_end = Math.max(data.order, task.pre_order);
            for (let i = index_start; i <= index_end; i++) {
              let taskBeOrder = Gantt.getTaskBy(task => task.$local_index === i && task.parent === data.parent);
              if (taskBeOrder.length > 0) {
                listOrder.push({
                  id: taskBeOrder[0].id,
                  order: Gantt.getTaskIndex(taskBeOrder[0].id)
                });
              }
            }
          }
          else {
            const child = Gantt.getChildren(data.parent)
            child.forEach(item => {
              let taskBeOrder = Gantt.getTask(item);
              let index = Gantt.getTaskIndex(taskBeOrder.id)
              if (index >= data.order) {
                listOrder.push({
                  id: taskBeOrder.id,
                  order: index
                })
              }
            })
          }
          if (listOrder.length) {
            data.listOrder = listOrder
          }
        }
      }
      return true;
    }, {id: 'post_order'})
  }

  onAutoSchedule(value = true, Gantt = this.Gantt) {
    this.options.auto_schedule = value;
    if (!value) {
      Gantt.config.auto_scheduling = false;
      Gantt.detachEvent('on_after_task_auto_schedule');
      Gantt.detachEvent('on_link_dbl_click');
      return true;
    }
    Gantt.config.auto_scheduling = true;
    Gantt.attachEvent("onAfterTaskAutoSchedule", function (task, new_date, constraint, predecessor) {
      if (task && predecessor) {
        Gantt.message({
          text: Drupal.t("<b>@text</b> has been rescheduled to @date due to @predecessor constraint", {'@text': task.text, '@date': Gantt.templates.task_date(new_date) ,'@predecessor': predecessor.text}),
          expire: 4000
        });
      }
    }, {id: 'on_after_task_auto_schedule'});

    Gantt.attachEvent("onLinkDblClick", function (id, e) {
      let link = Gantt.getLink(id);
      if (link.readonly) {
        return false;
      }

      let linkTitle;
      switch (link.type) {
        case Gantt.config.links.finish_to_start:
          linkTitle = Drupal.t("Finish to start");
          break;

        case Gantt.config.links.finish_to_finish:
          linkTitle = Drupal.t("Finish to finish");
          break;

        case Gantt.config.links.start_to_start:
          linkTitle = Drupal.t("Start to start");
          break;

        case Gantt.config.links.start_to_finish:
          linkTitle = Drupal.t("Start to finish");
          break;
      }

      const endPopup = () => {
        modal = null;
        link = null;
      }

      const saveLink = () => {
        let lagValue = modal.querySelector(".lag-input").value;
        if (Gantt.config.duration_unit === 'minute') lagValue = lagValue * 1440
        if (!isNaN(parseInt(lagValue, 10))) {
          link.lag = parseInt(lagValue, 10);
        }

        Gantt.updateLink(link.id);
        if (Gantt.autoSchedule) {
          Gantt.autoSchedule(link.source);
        }
        endPopup();
      }

      const deleteLink = () => {
        Gantt.deleteLink(link.id);
        endPopup()
      }

      const cancelEditLink = () => {
        endPopup()
      }

      let modal = Gantt.modalbox({
        title: "<div class='fs-5'>" + linkTitle + "</div>",
        text: "<div>" + Drupal.t("<b>@source</b> link to <b>@target</b>", {'@source': Gantt.getTask(link.source).text, '@target': Gantt.getTask(link.target).text}) + "</div>" +
          "<div><label>Lag <input type='number' class='lag-input form-control'/></label></div>",
        buttons: [
          {label: Drupal.t('Save'), css: "link-save-btn", value: "save"},
          {label: Drupal.t('Cancel'), css: "link-cancel-btn", value: "cancel"},
          {label: Drupal.t('Delete'), css: "link-delete-btn", value: "delete"}
        ],
        width: "500px",
        type: "popup-css-class-here",
        callback: function (result) {
          switch (result) {
            case "save":
              saveLink();
              break;

            case "cancel":
              cancelEditLink();
              break;

            case "delete":
              deleteLink();
              break;

          }
        }
      });
      let lagValue = link.lag
      if (Gantt.config.duration_unit === 'minute') lagValue = lagValue / 1440
      modal.querySelector(".lag-input").value = lagValue || 0;
    }, {id: 'on_link_dbl_click'});
  }

  onAutoType(value = true, Gantt = this.Gantt) {
    this.options.auto_type = value;
    if (!value) {
      Gantt.config.auto_types = false;
      return true;
    }
    Gantt.config.auto_types = true;
  }

  onSplitTask(value = true, Gantt = this.Gantt) {
    if (!value) {
      Gantt.config.open_split_tasks = false;
      return true;
    }
    Gantt.config.open_split_tasks = true;
  }

  onClickDrag(value = true, Gantt = this.Gantt) {
    if (!value) {
      this.options.click_drag = false;
      delete Gantt.config.click_drag;
      return true;
    }
    this.options.click_drag = true;
    Gantt.config.click_drag = {
      callback: (startPoint, endPoint, startDate, endDate, tasksBetweenDates, tasksInRow) => {
        if (tasksInRow.length === 1) {
          let parent = tasksInRow[0];
          Gantt.createTask({
            text: Drupal.t("Subtask of @parent", {'@parent': parent.text}),
            start_date: Gantt.roundDate(startDate),
            end_date: Gantt.roundDate(endDate)
          }, parent.id);
        }
        else if (tasksInRow.length === 0) {
          Gantt.createTask({
            text: this.i18n.labels.new_task,
            start_date: Gantt.roundDate(startDate),
            end_date: Gantt.roundDate(endDate)
          });
        }
      },
      useKey: "altKey",
      singleRow: true
    };
  }

  onProgressText(value = true) {
    if (!value) {
      this.options.progress_text = false;
      return true;
    }
    this.options.progress_text = true;
  }

  multipleSelectForm(Gantt = this.Gantt) {
    Gantt.form_blocks["multiselect"] = {
      render: function (sns) {
        let height = (sns.height || "23") + "px";
        let html = "<div class='gantt_cal_ltext gantt_cal_chosen gantt_cal_multiselect' style='height:" + height + ";'><select data-placeholder='...' class='chosen-select' multiple>";
        if (sns.options) {
          html += "<option value=''></option>";
          for (let i = 0; i < sns.options.length; i++) {
            if(sns.unassigned_value !== undefined && sns.options[i].key === sns.unassigned_value){
              continue;
            }
            let disabled = '';
            if (sns.options[i]?.disabled) {
              disabled = 'disabled';
            }
            html += "<option " + disabled + " value='" + sns.options[i].key + "'>" + sns.options[i].label + "</option>";
          }
        }
        html += "</select></div>";
        return html;
      },

      set_value: function (node, value, ev, sns) {
        node.style.overflow = "visible";
        node.parentNode.style.overflow = "visible";
        node.style.display = "inline-block";
        let select = jQuery(node.firstChild);

        if (value) {
          value = (value + "").split(",");
          select.val(value);
        }
        else {
          select.val([]);
        }

        let options = {width: "100%", no_results_text: Drupal.t("Oops, nothing found!")}
        if (!sns.required) {
          options.allow_single_deselect = true
        }
        select.chosen(options);

        if(sns.onchange){
          select.change(function(){
            sns.onchange.call(this);
          })
        }
        select.trigger('chosen:updated');
        select.trigger("change");
      },

      get_value: function (node, ev) {
        return jQuery(node.firstChild).val() || [];
      },

      focus: function (node) {
        jQuery(node.firstChild).focus();
      }
    };

    Gantt.form_blocks["onceselect"] = {
      render: function (sns) {
        let height = (sns.height || "23") + "px";
        let html = "<div class='gantt_cal_ltext gantt_cal_chosen gantt_cal_onceselect' style='height:" + height + ";'><select data-placeholder='...' class='chosen-select'>";
        if (sns.options) {
          html += "<option value=''></option>";
          for (let i = 0; i < sns.options.length; i++) {
            if(sns.unassigned_value !== undefined && sns.options[i].key === sns.unassigned_value){
              continue;
            }
            let disabled = '';
            if (sns.options[i]?.disabled) {
              disabled = 'disabled';
            }
            html += "<option " + disabled + " value='" + sns.options[i].key + "'>" + sns.options[i].label + "</option>";
          }
        }
        html += "</select></div>";
        return html;
      },

      set_value: function (node, value, ev, sns) {
        node.style.overflow = "visible";
        node.parentNode.style.overflow = "visible";
        node.style.display = "inline-block";
        let select = jQuery(node.firstChild);

        if (value) {
          value = (value + "").split(",");
          select.val(value);
        }
        else {
          select.val([]);
        }

        let options = {width: "100%", no_results_text: Drupal.t("Oops, nothing found!")}
        if (!sns.required) {
          options.allow_single_deselect = true
        }
        select.chosen(options);

        if(sns.onchange){
          select.change(function(){
            sns.onchange.call(this);
          })
        }
        select.trigger('chosen:updated').trigger("change");
      },

      get_value: function (node, ev) {
        let value = jQuery(node.firstChild).val() || []
        if (!Array.isArray(value)) {
          value = [value]
        }
        return value;
      },

      focus: function (node) {
        jQuery(node.firstChild).focus();
      }
    };
  }

  dateForm($this = this, Gantt = this.Gantt, settingsGantt = this.settingsGantt) {
    Gantt.form_blocks["datetime"] = {
      roundNumber: Gantt.config.duration_unit === 'minute' ? 15 : 1,
      formatFunc: Gantt.date.date_to_str(Gantt.config.duration_unit === 'minute' ? '%Y-%m-%d %H:%i' : '%Y-%m-%d'),
      dateFunc: Gantt.date.str_to_date(Gantt.config.duration_unit === 'minute' ? '%Y-%m-%d %H:%i' : '%Y-%m-%d'),
      roundTime: function (date) {
        let date_round = Gantt.roundDate({
          date: date,
          unit: Gantt.config.duration_unit,
          step: Gantt.form_blocks["datetime"].roundNumber
        });
        if (date_round.valueOf() < date.valueOf()) {
          return Gantt.date.add(date_round, Gantt.form_blocks["datetime"].roundNumber, Gantt.config.duration_unit)
        }
        return date_round;
      },
      setDuration: function(new_value, duration) {
        let val_day = new_value;
        let val_hour = null;
        let val_minute = null;
        if (Gantt.config.duration_unit === 'minute') {
          val_day = Math.floor(new_value / (60 * 24));
          val_hour = Math.floor((new_value % (60 * 24)) / 60);
          val_minute = new_value % 60;
        }

        duration.find('#duration-day').val(val_day)
        duration.find('#duration-hour').val(val_hour)
        duration.find('#duration-minute').val(val_minute)
      },
      getDuration: function (duration) {
        const val_day = Number(duration.find('#duration-day').val()) || 0
        const val_hour = Number(duration.find('#duration-hour').val()) || 0
        const val_minute = Number(duration.find('#duration-minute').val()) || 0
        let total_minute = null;
        switch (Gantt.config.duration_unit) {
          case 'day':
            total_minute = val_day * 60 * 24 + val_hour * 60 + val_minute
            break;

          case 'minute':
            total_minute = val_day * 60 * 24 + val_hour * 60 + val_minute
            break;

          default:
            total_minute = val_day * 60 * 24 + val_hour * 60 + val_minute
        }
        return total_minute;
      },
      render: function (sns) {
        let date_type = 'date'
        let step = Gantt.form_blocks["datetime"].roundNumber
        if (Gantt.config.duration_unit === 'minute') {
          date_type = 'datetime-local'
          step = Gantt.form_blocks["datetime"].roundNumber * 60
        }
        const formatFunc = Gantt.form_blocks["datetime"].formatFunc
        const roundTime = Gantt.form_blocks["datetime"].roundTime

        const default_start = new Date()
        const default_end = roundTime(default_start)

        let height = (sns.height || "34") + "px";
        let html = "<div class='gantt_cal_ltext gantt_cal_datetime' style='height:" + height + ";'>";
        html += "<span class='wrapper-start' style='display: inline-block'><input id='date_start' type='" + date_type + "' value='" + formatFunc(default_start) + "' step='" + step + "' /></span>"
        if (!sns.single_date || sns.single_date === false) {
          html += "<span class='wrapper-end' style='display: inline-block'><input id='date_end' type='" + date_type + "' value='" + formatFunc(default_end) + "' step='" + step + "' /></span>"
          html += "<span class='wrapper-duration' style='display: inline-block'>"
          html += "<button id='duration-button-diminish'>-</button>"
          html += "<span id='main-duration-day' style='display: inline-block'><input id='duration-day' min='0' type='number'/><label>" + Drupal.t('day') + "</label></span>"
          if (date_type === 'datetime-local') {
            html += "<span id='main-duration-hour' style='display: inline-block'><input id='duration-hour' min='0' max='23' type='number'/><label>" + Drupal.t('hour') + "</label></span>"
            html += "<span id='main-duration-minute' style='display: inline-block'><input id='duration-minute' min='0' max='59' type='number'/><label>" + Drupal.t('minute') + "</label></span>"
          }
          html += "<button id='duration-button-increase'>+</button>"
        }
        html += "</span>";
        html += "</div>";
        return html;
      },

      set_value: function (node, value, ev, sns) {
        node.style.overflow = "visible";
        node.parentNode.style.overflow = "visible";
        node.style.display = "inline-block";

        const date_start = jQuery(node).find('#date_start');
        const date_end = jQuery(node).find('#date_end');
        const duration = jQuery(node).find('[class^="wrapper-duration"]');
        let duration_focus = null;

        const formatFunc = Gantt.form_blocks["datetime"].formatFunc;
        const dateFunc = Gantt.form_blocks["datetime"].dateFunc;
        const roundTime = Gantt.form_blocks["datetime"].roundTime
        const getDuration = Gantt.form_blocks["datetime"].getDuration
        const setDuration = Gantt.form_blocks["datetime"].setDuration

        // set value.
        if (date_start.length && ev.start_date && !ev.$new) date_start.val(formatFunc(ev.start_date))
        if (date_end.length && ev.end_date && !ev.$new) {
          let end = ev.end_date
          if (settingsGantt.last_of_the_day && Gantt.config.duration_unit === 'day') end = Gantt.date.add(end, -1, 'day');
          date_end.val(formatFunc(end))
        }
        const start = dateFunc(date_start.val().replace('T', ' '))
        let end = date_end.length ? dateFunc(date_end.val().replace('T', ' ')) : null
        if (end) {
          if (settingsGantt.last_of_the_day && Gantt.config.duration_unit === 'day') end = Gantt.date.add(end, 1, 'day');
          const cal_duration = Gantt.calculateDuration({start_date: start, end_date: end})
          setDuration(cal_duration, duration)
        }

        const setEndDate = () => {
          const val_duration = getDuration(duration)
          const start = dateFunc(date_start.val().replace('T', ' '))
          let new_value = Gantt.calculateEndDate({start_date: start, unit: 'minute', duration: val_duration});
          if (settingsGantt.last_of_the_day && Gantt.config.duration_unit === 'day') new_value = Gantt.date.add(new_value, -1, 'day');
          date_end.val(formatFunc(new_value))
        }

        const validateDate = () => {
          if (date_start.length && !date_start.val()) date_start.val(formatFunc(new Date()))
          if (date_end.length && !date_end.val()) setEndDate()
          if (date_start.length && date_end.length) {
            const start = dateFunc(date_start.val().replace('T', ' '))
            let end = dateFunc(date_end.val().replace('T', ' '))
            if (settingsGantt.last_of_the_day && Gantt.config.duration_unit === 'day') end = Gantt.date.add(end, 1, 'day');
            if (start.valueOf() >= end.valueOf()) {
              setEndDate()
              date_end.attr('min', formatFunc(roundTime(start)))
            }
          }
        }

        const validateDuration = (new_value) => {
          new_value = Number(new_value)
          const min_focus = Number(duration_focus.attr('min')) || 0
          const max_focus = Number(duration_focus.attr('max')) || 999999
          if (min_focus <= new_value && new_value <= max_focus) {
            duration_focus.val(new_value)
          }
          else if (new_value < min_focus) {
            duration_focus.val(min_focus)
          }
          else if (new_value > max_focus) {
            duration_focus.val(max_focus)
          }
        }

        duration.find('label').on('click', function (e) {
          jQuery(this).prev().focus()
        })

        duration.find('input[id^="duration"]').on('focus', function (e) {
          duration_focus = jQuery(this)
        })

        duration.on('click', '#duration-button-diminish',function () {
          let val_duration = getDuration(duration)
          if (duration_focus) {
            duration_focus.focus()
            switch (duration_focus.attr('id')) {
              case 'duration-day':
                val_duration -= 1440
                break;

              case 'duration-hour':
                val_duration -= 60
                break;

              default:
                val_duration -= Gantt.form_blocks["datetime"].roundNumber
            }
          }
          else {
            val_duration -= Gantt.config.duration_unit === 'minute' ? Gantt.form_blocks["datetime"].roundNumber : 1440
          }
          if (val_duration > 0) {
            const start = dateFunc(date_start.val().replace('T', ' '))
            const cal_end_date = roundTime(Gantt.calculateEndDate({start_date: start, unit: 'minute', duration: val_duration}))
            const cal_duration = Gantt.calculateDuration({start_date: start, end_date: cal_end_date})
            setDuration(cal_duration, duration)
            setEndDate()
          }
        })
        duration.on('click', '#duration-button-increase',function () {
          let val_duration = getDuration(duration)
          if (duration_focus) {
            duration_focus.focus()
            switch (duration_focus.attr('id')) {
              case 'duration-day':
                val_duration += 1440
                break;

              case 'duration-hour':
                val_duration += 60
                break;

              default:
                val_duration += Gantt.form_blocks["datetime"].roundNumber
            }
          }
          else {
            val_duration += Gantt.config.duration_unit === 'minute' ? Gantt.form_blocks["datetime"].roundNumber : 1440
          }
          if (val_duration > 0) {
            const start = dateFunc(date_start.val().replace('T', ' '))
            const cal_end_date = roundTime(Gantt.calculateEndDate({start_date: start, unit: 'minute', duration: val_duration}))
            const cal_duration = Gantt.calculateDuration({start_date: start, end_date: cal_end_date})
            setDuration(cal_duration, duration)
            setEndDate()
          }
        })

        if (date_start.length && date_end.length) {
          date_start.on('change', function (e) {
            validateDate()
            if (date_start.val() && date_end.val()) {
              const start = dateFunc(date_start.val().replace('T', ' '))
              const val_duration = getDuration(duration)
              let cal_end_date = Gantt.calculateEndDate({start_date: start, unit: 'minute', duration: val_duration});
              if (settingsGantt.last_of_the_day && Gantt.config.duration_unit === 'day') cal_end_date = Gantt.date.add(cal_end_date, -1, 'day');
              date_end.val(formatFunc(cal_end_date))
            }
          })
        }
        if (date_start.length && date_end.length) {
          date_end.on('change', function (e) {
            const start = dateFunc(date_start.val().replace('T', ' '))
            let end = dateFunc(date_end.val().replace('T', ' '))
            if (start > end) {
              jQuery(this).css('color', 'red');
            }
          });
          date_end.on('change', Drupal.debounce(function (e) {
            validateDate()
            if (date_start.val() && date_end.val()) {
              const start = dateFunc(date_start.val().replace('T', ' '))
              let end = dateFunc(date_end.val().replace('T', ' '))
              if (settingsGantt.last_of_the_day && Gantt.config.duration_unit === 'day') end = Gantt.date.add(end, 1, 'day');
              const cal_duration = Gantt.calculateDuration({start_date: start, end_date: end});
              setDuration(cal_duration, duration)
              jQuery(this).css('color', 'black');
            }
          }, 500))
        }
        if (duration.length) {
          duration.on('change', 'input[id^="duration"]', function (e) {
            validateDuration(jQuery(this).val())
            setEndDate()
          })
        }
      },

      get_value: function (node, ev, sns) {
        const dateFunc = Gantt.form_blocks["datetime"].dateFunc;
        let date_start = jQuery(node).find('#date_start');
        let date_end = jQuery(node).find('#date_end');
        if (date_start.length) ev.start_date = dateFunc(date_start.val().replace('T', ' '))
        if (date_end.length) {
          ev.end_date = dateFunc(date_end.val().replace('T', ' '))
          if (settingsGantt.last_of_the_day && Gantt.config.duration_unit === 'day') ev.end_date = Gantt.date.add(ev.end_date, 1, 'day');
        }
        return true;
      }
    };

    Gantt.form_blocks["datetime_optional"] = {
      button_click: function(index, button, shead, sbody) {
        if (jQuery(sbody).is(":visible")) {
          jQuery(button).text($this.i18n.labels.button_responsive_toggle)
          jQuery(sbody).css('display', 'none')
        }
        else {
          jQuery(button).text($this.i18n.labels.button_responsive_disable_toggle)
          jQuery(sbody).css('display', 'block')
        }
      },
      roundNumber: Gantt.config.duration_unit === 'minute' ? 15 : 1,
      formatFunc: Gantt.date.date_to_str(Gantt.config.duration_unit === 'minute' ? '%Y-%m-%d %H:%i' : '%Y-%m-%d'),
      dateFunc: Gantt.date.str_to_date(Gantt.config.duration_unit === 'minute' ? '%Y-%m-%d %H:%i' : '%Y-%m-%d'),
      roundTime: function (date) {
        let date_round = Gantt.roundDate({
          date: date,
          unit: Gantt.config.duration_unit,
          step: Gantt.form_blocks["datetime_optional"].roundNumber
        });
        if (date_round.valueOf() < date.valueOf()) {
          return Gantt.date.add(date_round, Gantt.form_blocks["datetime_optional"].roundNumber, Gantt.config.duration_unit)
        }
        return date_round;
      },
      setDuration: function(new_value, duration) {
        let val_day = new_value;
        let val_hour = null;
        let val_minute = null;
        if (Gantt.config.duration_unit === 'minute') {
          val_day = Math.floor(new_value / (60 * 24));
          val_hour = Math.floor((new_value % (60 * 24)) / 60);
          val_minute = new_value % 60;
        }

        duration.find('#duration-day').val(val_day)
        duration.find('#duration-hour').val(val_hour)
        duration.find('#duration-minute').val(val_minute)
      },
      getDuration: function (duration) {
        const val_day = Number(duration.find('#duration-day').val()) || 0
        const val_hour = Number(duration.find('#duration-hour').val()) || 0
        const val_minute = Number(duration.find('#duration-minute').val()) || 0
        let total_minute = null;
        switch (Gantt.config.duration_unit) {
          case 'day':
            total_minute = val_day * 60 * 24 + val_hour * 60 + val_minute
            break;

          case 'minute':
            total_minute = val_day * 60 * 24 + val_hour * 60 + val_minute
            break;

          default:
            total_minute = val_day * 60 * 24 + val_hour * 60 + val_minute
        }
        return total_minute;
      },
      render: function (sns) {
        let date_type = 'date'
        let step = Gantt.form_blocks["datetime_optional"].roundNumber
        if (Gantt.config.duration_unit === 'minute') {
          date_type = 'datetime-local'
          step = Gantt.form_blocks["datetime_optional"].roundNumber * 60
        }
        const formatFunc = Gantt.form_blocks["datetime_optional"].formatFunc
        const roundTime = Gantt.form_blocks["datetime_optional"].roundTime

        const default_start = new Date()
        const default_end = roundTime(default_start)

        let height = (sns.height || "34") + "px";
        let html = "<div class='gantt_cal_ltext gantt_cal_datetime' style='height:" + height + ";'>";
        html += "<span class='wrapper-start' style='display: inline-block'><input id='date_start' type='" + date_type + "' value='" + formatFunc(default_start) + "' step='" + step + "' /></span>"
        if (!sns.single_date || sns.single_date === false) {
          html += "<span class='wrapper-end' style='display: inline-block'><input id='date_end' type='" + date_type + "' value='" + formatFunc(default_end) + "' step='" + step + "' /></span>"
          html += "<span class='wrapper-duration' style='display: inline-block'>"
          html += "<button id='duration-button-diminish'>-</button>"
          html += "<span id='main-duration-day' style='display: inline-block'><input id='duration-day' min='0' type='number'/><label>" + Drupal.t('day') + "</label></span>"
          if (date_type === 'datetime-local') {
            html += "<span id='main-duration-hour' style='display: inline-block'><input id='duration-hour' min='0' max='23' type='number'/><label>" + Drupal.t('hour') + "</label></span>"
            html += "<span id='main-duration-minute' style='display: inline-block'><input id='duration-minute' min='0' max='59' type='number'/><label>" + Drupal.t('minute') + "</label></span>"
          }
          html += "<button id='duration-button-increase'>+</button>"
        }
        html += "</span>";
        html += "</div>";
        return html;
      },

      set_value: function (node, value, ev, sns) {
        node.style.overflow = "visible";
        node.parentNode.style.overflow = "visible";
        node.style.display = "none";
        jQuery(node).prev().find('.gantt_custom_button_label').text($this.i18n.labels.button_responsive_toggle)
        if (!ev.$new) {
          if (ev.planned_start && ev.planned_end) {
            jQuery(node).prev().find('.gantt_custom_button_label').text($this.i18n.labels.button_responsive_disable_toggle)
            node.style.display = "inline-block";
          }
        }
        else {
          jQuery(node).prev().find('.gantt_custom_button_label').text($this.i18n.labels.button_responsive_disable_toggle)
          node.style.display = "inline-block";
        }

        const date_start = jQuery(node).find('#date_start');
        const date_end = jQuery(node).find('#date_end');
        const duration = jQuery(node).find('[class^="wrapper-duration"]');
        let duration_focus = null;

        const formatFunc = Gantt.form_blocks["datetime_optional"].formatFunc;
        const dateFunc = Gantt.form_blocks["datetime_optional"].dateFunc;
        const roundTime = Gantt.form_blocks["datetime_optional"].roundTime
        const getDuration = Gantt.form_blocks["datetime_optional"].getDuration
        const setDuration = Gantt.form_blocks["datetime_optional"].setDuration

        // set value.
        if (date_start.length && ev.planned_start && !ev.$new) date_start.val(formatFunc(ev.planned_start))
        if (date_end.length && ev.planned_end && !ev.$new) {
          let end = ev.planned_end
          if (settingsGantt.last_of_the_day && Gantt.config.duration_unit === 'day') end = Gantt.date.add(end, -1, 'day');
          date_end.val(formatFunc(end))
        }
        const start = dateFunc(date_start.val().replace('T', ' '))
        let end = date_end.length ? dateFunc(date_end.val().replace('T', ' ')) : null
        if (end) {
          if (settingsGantt.last_of_the_day && Gantt.config.duration_unit === 'day') end = Gantt.date.add(end, 1, 'day');
          const cal_duration = Gantt.calculateDuration({start_date: start, end_date: end})
          setDuration(cal_duration, duration)
        }

        const setEndDate = () => {
          const val_duration = getDuration(duration)
          const start = dateFunc(date_start.val().replace('T', ' '))
          let new_value = Gantt.calculateEndDate({start_date: start, unit: 'minute', duration: val_duration});
          if (settingsGantt.last_of_the_day && Gantt.config.duration_unit === 'day') new_value = Gantt.date.add(new_value, -1, 'day');
          date_end.val(formatFunc(new_value))
        }

        const validateDate = () => {
          if (date_start.length && !date_start.val()) date_start.val(formatFunc(new Date()))
          if (date_end.length && !date_end.val()) setEndDate()
          if (date_start.length && date_end.length) {
            const start = dateFunc(date_start.val().replace('T', ' '))
            let end = dateFunc(date_end.val().replace('T', ' '))
            if (settingsGantt.last_of_the_day && Gantt.config.duration_unit === 'day') end = Gantt.date.add(end, 1, 'day');
            if (start.valueOf() >= end.valueOf()) {
              setEndDate()
              date_end.attr('min', formatFunc(roundTime(start)))
            }
          }
        }

        const validateDuration = (new_value) => {
          new_value = Number(new_value)
          const min_focus = Number(duration_focus.attr('min')) || 0
          const max_focus = Number(duration_focus.attr('max')) || 999999
          if (min_focus <= new_value && new_value <= max_focus) {
            duration_focus.val(new_value)
          }
          else if (new_value < min_focus) {
            duration_focus.val(min_focus)
          }
          else if (new_value > max_focus) {
            duration_focus.val(max_focus)
          }
        }

        duration.find('label').on('click', function (e) {
          jQuery(this).prev().focus()
        })

        duration.find('input[id^="duration"]').on('focus', function (e) {
          duration_focus = jQuery(this)
        })

        duration.on('click', '#duration-button-diminish',function () {
          let val_duration = getDuration(duration)
          if (duration_focus) {
            duration_focus.focus()
            switch (duration_focus.attr('id')) {
              case 'duration-day':
                val_duration -= 1440
                break;

              case 'duration-hour':
                val_duration -= 60
                break;

              default:
                val_duration -= Gantt.form_blocks["datetime_optional"].roundNumber
            }
          }
          else {
            val_duration -= Gantt.config.duration_unit === 'minute' ? Gantt.form_blocks["datetime_optional"].roundNumber : 1440
          }
          if (val_duration > 0) {
            const start = dateFunc(date_start.val().replace('T', ' '))
            const cal_end_date = roundTime(Gantt.calculateEndDate({start_date: start, unit: 'minute', duration: val_duration}))
            const cal_duration = Gantt.calculateDuration({start_date: start, end_date: cal_end_date})
            setDuration(cal_duration, duration)
            setEndDate()
          }
        })
        duration.on('click', '#duration-button-increase',function () {
          let val_duration = getDuration(duration)
          if (duration_focus) {
            duration_focus.focus()
            switch (duration_focus.attr('id')) {
              case 'duration-day':
                val_duration += 1440
                break;

              case 'duration-hour':
                val_duration += 60
                break;

              default:
                val_duration += Gantt.form_blocks["datetime_optional"].roundNumber
            }
          }
          else {
            val_duration += Gantt.config.duration_unit === 'minute' ? Gantt.form_blocks["datetime_optional"].roundNumber : 1440
          }
          if (val_duration > 0) {
            const start = dateFunc(date_start.val().replace('T', ' '))
            const cal_end_date = roundTime(Gantt.calculateEndDate({start_date: start, unit: 'minute', duration: val_duration}))
            const cal_duration = Gantt.calculateDuration({start_date: start, end_date: cal_end_date})
            setDuration(cal_duration, duration)
            setEndDate()
          }
        })

        if (date_start.length && date_end.length) {
          date_start.on('change', function (e) {
            validateDate()
            if (date_start.val() && date_end.val()) {
              const start = dateFunc(date_start.val().replace('T', ' '))
              const val_duration = getDuration(duration)
              let cal_end_date = Gantt.calculateEndDate({start_date: start, unit: 'minute', duration: val_duration});
              if (settingsGantt.last_of_the_day && Gantt.config.duration_unit === 'day') cal_end_date = Gantt.date.add(cal_end_date, -1, 'day');
              date_end.val(formatFunc(cal_end_date))
            }
          })
        }
        if (date_start.length && date_end.length) {
          date_end.on('change', function (e) {
            const start = dateFunc(date_start.val().replace('T', ' '))
            let end = dateFunc(date_end.val().replace('T', ' '))
            if (start > end) {
              jQuery(this).css('color', 'red');
            }
          });
          date_end.on('change', Drupal.debounce(function (e) {
            validateDate()
            if (date_start.val() && date_end.val()) {
              const start = dateFunc(date_start.val().replace('T', ' '))
              let end = dateFunc(date_end.val().replace('T', ' '))
              if (settingsGantt.last_of_the_day && Gantt.config.duration_unit === 'day') end = Gantt.date.add(end, 1, 'day');
              const cal_duration = Gantt.calculateDuration({start_date: start, end_date: end});
              setDuration(cal_duration, duration)
              jQuery(this).css('color', 'black');
            }
          }, 500))
        }
        if (duration.length) {
          duration.on('change', 'input[id^="duration"]', function (e) {
            validateDuration(jQuery(this).val())
            setEndDate()
          })
        }
      },

      get_value: function (node, ev, sns) {
        const dateFunc = Gantt.form_blocks["datetime_optional"].dateFunc;
        let date_start = jQuery(node).find('#date_start');
        let date_end = jQuery(node).find('#date_end');
        if (!jQuery(node).is(":visible")) {
          ev.planned_start = null
          ev.planned_end = null
          return true;
        }
        if (date_start.length) ev.planned_start = dateFunc(date_start.val().replace('T', ' '))
        if (date_end.length) {
          ev.planned_end = dateFunc(date_end.val().replace('T', ' '))
          if (settingsGantt.last_of_the_day && Gantt.config.duration_unit === 'day') ev.planned_end = Gantt.date.add(ev.planned_end, 1, 'day');
        }
        return true;
      }
    };
  }

  numberForm(Gantt = this.Gantt) {
    Gantt.form_blocks["number"] = {
      render: function (sns) {
        return '<div class="gantt_cal_ltext gantt_cal_number"><input type="number" min="0"></div>';
      },
      set_value: function (node, value, task) {
        jQuery(node.firstChild).val(value);
      },
      get_value: function (node, task) {
        return jQuery(node.firstChild).val();
      },
      focus: function (node) {
        jQuery(node.firstChild).focus()
      }
    };
  }

  showSlack(value = true, $this = this, Gantt = this.Gantt) {
    this.options.show_slack = value;
    if (!value) {
      Gantt.config.show_slack = false;
      Gantt.config.columns = Gantt.config.columns.filter(item => item.name !== 'totalSlack' && item.name !== 'freeSlack');
      Gantt.removeTaskLayer(this.taskLayer.slack);
      return true;
    }
    let totalSlackColumn = {
      name: "totalSlack",
      align: "center",
      resize: true,
      width: 70,
      label: this.i18n.labels.column_totalSlack,
      template: function (task) {
        if (Gantt.isSummaryTask(task)) {
          return "";
        }
        let slack_duration = Gantt.getTotalSlack(task);
        if (Gantt.config.duration_unit === 'minute') {
          slack_duration = $this.formatMinuteTime(slack_duration)
        }
        return slack_duration;
      }
    }

    let freeSlackColumn = {
      name: "freeSlack",
      align: "center",
      resize: true,
      width: 70,
      label: this.i18n.labels.column_freeSlack,
      template: function (task) {
        if (Gantt.isSummaryTask(task)) {
          return "";
        }
        let slack_duration = Gantt.getFreeSlack(task);
        if (Gantt.config.duration_unit === 'minute') {
          slack_duration = $this.formatMinuteTime(slack_duration)
        }
        return slack_duration;
      }
    };

    let col_add = [];
    Gantt.config.columns = Gantt.config.columns.filter(item => {
      if (['add', 'buttons', 'control_columns'].includes(item.name)) {
        col_add.push(item);
        return false;
      }
      return true;
    });

    if (!Gantt.config.columns.find(item => item.name === 'freeSlack')) {
      Gantt.config.columns.push(freeSlackColumn);
    }
    if (!Gantt.config.columns.find(item => item.name === 'totalSlack')) {
      Gantt.config.columns.push(totalSlackColumn);
    }
    if (this.settingsGantt.add_task && col_add.length) {
      col_add.forEach(item => {
        Gantt.config.columns.push(item);
      })
    }

    Gantt.config.show_slack = true;
    this.taskLayer.slack = Gantt.addTaskLayer(function addSlack(task) {
      if (!Gantt.config.show_slack) {
        return null;
      }

      let slack = Gantt.getFreeSlack(task);

      if (!slack) {
        return null;
      }

      let state = Gantt.getState().drag_mode;

      if (state === 'resize' || state === 'move') {
        return null;
      }

      let slackStart = new Date(task.end_date);
      let slackEnd = Gantt.calculateEndDate(slackStart, slack);
      let sizes = Gantt.getTaskPosition(task, slackStart, slackEnd);
      let el = document.createElement('div');

      el.className = 'slack';
      el.style.left = sizes.left + 'px';
      el.style.top = sizes.top + 2 + 'px';
      el.style.width = sizes.width + 'px';
      el.style.height = sizes.height + 'px';

      return el;
    });
  }

  showColumnWBS(value = true, Gantt = this.Gantt) {
    if (!value) {
      this.options.show_column_wbs = false;
      Gantt.config.columns = Gantt.config.columns.filter(item => item.name !== 'wbs');
      return true;
    }
    this.options.show_column_wbs = true;
    Gantt.config.columns.unshift({
      name: "wbs",
      label: this.i18n.labels.column_wbs,
      width: 40,
      resize: true,
      template: Gantt.getWBSCode
    })
  }

  onShowButtonColumn(value = true, Gantt = this.Gantt) {
    this.options.column_buttons = value;
    if (this.options.add_task) {
      return false;
    }
    if (!value) {
      Gantt.detachEvent('action_button_column')
      let col_buttons = Gantt.config.columns.find(item => item.name === 'buttons');
      if (col_buttons) {
        col_buttons.name = 'add'
        col_buttons.min_width = 44
        col_buttons.max_width = 44
        col_buttons.width = 44
      }
      return true;
    }

    const id_gantt = this.id_gantt;
    let colHeader = `<div class="gantt_grid_head_cell gantt_grid_head_add" onclick="clickGridButton('${id_gantt}')"></div>`;
    let colContent = function (task) {
      return ('<i class="fa gantt_button_grid gantt_grid_edit fa-pencil" onclick="clickGridButton(\'' + id_gantt + '\', ' + task.id + ', \'edit\')"></i>' +
        '<i class="fa gantt_button_grid gantt_grid_add fa-plus" onclick="clickGridButton(\'' + id_gantt + '\', ' + task.id + ', \'add\')"></i>' +
        '<i class="fa gantt_button_grid gantt_grid_delete fa-times" onclick="clickGridButton(\'' + id_gantt + '\', ' + task.id + ', \'delete\')"></i>');
    };
    let col_add = Gantt.config.columns.find(item => item.name === 'add');
    if (col_add) {
      col_add.name = 'buttons'
      col_add.label = colHeader
      col_add.min_width = 75
      col_add.max_width = 75
      col_add.width = 75
      col_add.template = colContent
    }
  }

  onDynamicProgress(value = true, Gantt = this.Gantt) {
    this.options.dynamic_progress = value
    if (!value) {
      Gantt.detachEvent('dynamic_progress_on_parse');
      Gantt.detachEvent('dynamic_progress_after_update_task');
      Gantt.detachEvent('dynamic_progress_on_drag_task');
      Gantt.detachEvent('dynamic_progress_after_add_task');
      Gantt.detachEvent('dynamic_progress_before_delete_task');
      Gantt.detachEvent('dynamic_progress_after_delete_task');
      Gantt.detachEvent('dynamic_progress_before_change_task');
      return true;
    }
    const calculateSummaryProgress = (task) => {
      if (task.type !== Gantt.config.types.project) {
        return task.progress;
      }
      let totalToDo = 0;
      let totalDone = 0;
      Gantt.eachTask(function (child) {
        if (child.type !== Gantt.config.types.project) {
          totalToDo += child.duration;
          totalDone += (child.progress || 0) * child.duration;
        }
      }, task.id);
      if (!totalToDo) {
        return 0;
      }
      return totalDone / totalToDo;
    }

    const refreshSummaryProgress = (id, submit) => {
      if (!Gantt.isTaskExists(id)) {
        return;
      }

      let task = Gantt.getTask(id);
      let newProgress = calculateSummaryProgress(task);

      if (newProgress !== task.progress) {
        task.progress = newProgress;

        if (!submit) {
          Gantt.refreshTask(id);
        }
        else {
          Gantt.updateTask(id);
        }
      }

      if (!submit && Gantt.getParent(id) !== Gantt.config.root_id) {
        refreshSummaryProgress(Gantt.getParent(id), submit);
      }
    }

    Gantt.attachEvent("onParse", function () {
      Gantt.eachTask(function (task) {
        task.progress = calculateSummaryProgress(task);
      });
    }, { id: 'dynamic_progress_on_parse' });

    Gantt.attachEvent("onAfterTaskUpdate", function (id) {
      refreshSummaryProgress(Gantt.getParent(id), true);
    }, { id: 'dynamic_progress_after_update_task' });

    Gantt.attachEvent("onTaskDrag", function (id) {
      refreshSummaryProgress(Gantt.getParent(id), false);
    }, { id: 'dynamic_progress_on_drag_task' });
    Gantt.attachEvent("onAfterTaskAdd", function (id) {
      refreshSummaryProgress(Gantt.getParent(id), true);
    }, { id: 'dynamic_progress_after_add_task' });

    (function () {
      let idParentBeforeDeleteTask = 0;
      Gantt.attachEvent("onBeforeTaskDelete", function (id) {
        idParentBeforeDeleteTask = Gantt.getParent(id);
      }, {id: 'dynamic_progress_before_delete_task'});
      Gantt.attachEvent("onAfterTaskDelete", function () {
        refreshSummaryProgress(idParentBeforeDeleteTask, true);
      }, { id: 'dynamic_progress_after_delete_task' });
    })();

    Gantt.attachEvent("onBeforeTaskChanged", function (id, mode, old_event) {
      const task = Gantt.getTask(id);
      if (mode === Gantt.config.drag_mode.progress && task.type === Gantt.config.types.project) {
        Gantt.message(Drupal.t("@task progress can't be undone!", {'@task': task.text}));
        return false;
      }
      return true;
    }, { id: 'dynamic_progress_before_change_task' });
  }

  highLightDragTask(value = true, Gantt = this.Gantt) {
    this.options.highlight_drag_task = value;
    let taskLayer = this.taskLayer;
    if (!value) {
      delete Gantt.config.show_drag_vertical;
      delete Gantt.config.show_drag_dates;
      delete Gantt.config.drag_label_width;
      delete Gantt.config.drag_date;
      delete Gantt.templates.drag_date;
      Gantt.removeTaskLayer(taskLayer.highlight_drag_task_area);
      Gantt.removeTaskLayer(taskLayer.highlight_drag_task_date);
      Gantt.detachEvent('highlight_drag_task');
      return true;
    }
    Gantt.config.show_drag_vertical = value;
    Gantt.config.show_drag_dates = value;
    Gantt.config.drag_label_width = 70;
    Gantt.config.drag_date = Gantt.config.date_grid;
    Gantt.templates.drag_date = null;
    Gantt.templates.drag_date = Gantt.date.date_to_str(Gantt.config.drag_date);

    function addElement(config) {
      let div = document.createElement('div');
      div.style.position = "absolute";
      div.className = config.css || "";
      div.style.left = config.left;
      div.style.width = config.width;
      div.style.height = config.height;
      div.style.lineHeight = config.height;
      div.style.top = config.top;
      if (config.html) {
        div.innerHTML = config.html;
      }
      if (config.wrapper) {
        config.wrapper.appendChild(div);
      }
      return div;
    }

    //highlight area
    taskLayer.highlight_drag_task_area = Gantt.addTaskLayer({
      renderer: function highlight_area(task) {
        let sizes = Gantt.getTaskPosition(task, task.start_date, task.end_date),
          wrapper = document.createElement("div");

        addElement({
          css: 'drag_move_vertical',
          left: sizes.left + 'px',
          top: 0,
          width: sizes.width + 'px',
          height: Gantt.getVisibleTaskCount() * Gantt.config.row_height + "px",
          wrapper: wrapper
        });

        addElement({
          css: 'drag_move_horizontal',
          left: 0,
          top: sizes.top + 'px',
          width: 100 + "%",
          height: Gantt.config.row_height - 1 + 'px',
          wrapper: wrapper
        });

        return wrapper;
      },
      filter: function (task) {
        return Gantt.config.show_drag_vertical && task.id === Gantt.getState().drag_id;
      }
    });

    //show drag dates
    taskLayer.highlight_drag_task_date = Gantt.addTaskLayer({
      renderer: function show_dates(task) {
        let sizes = Gantt.getTaskPosition(task, task.start_date, task.end_date),
          wrapper = document.createElement('div');

        addElement({
          css: "drag_move_start drag_date",
          left: sizes.left - Gantt.config.drag_label_width + 'px',
          top: sizes.top + 'px',
          width: Gantt.config.drag_label_width + 'px',
          height: Gantt.config.row_height - 1 + 'px',
          html: Gantt.templates.drag_date(task.start_date),
          wrapper: wrapper
        });

        addElement({
          css: "drag_move_end drag_date",
          left: sizes.left + sizes.width + 'px',
          top: sizes.top + 'px',
          width: Gantt.config.drag_label_width + 'px',
          height: Gantt.config.row_height - 1 + 'px',
          html: Gantt.templates.drag_date(task.end_date),
          wrapper: wrapper
        });

        return wrapper;
      },
      filter: function (task) {
        return Gantt.config.show_drag_dates && task.id === Gantt.getState().drag_id;
      }
    });
  }

  highLightProject(value = true, Gantt = this.Gantt) {
    let taskLayer = this.taskLayer;
    // highlight project and task child area.
    taskLayer.highlight_project = Gantt.addTaskLayer(function (task) {
      if (Gantt.getState().selected_task == task.id && Gantt.hasChild(task.id)) {
        const sizes = Gantt.getTaskPosition(task, task.start_date, task.end_date);
        const left = [sizes.left];
        const right = [sizes.left + sizes.width];
        Gantt.eachTask(function (child) {
          const childSizes = Gantt.getTaskPosition(child, child.start_date, child.end_date);
          left.push(childSizes.left)
          right.push(childSizes.left + childSizes.width)
          if (sizes.height < childSizes.top + childSizes.height) sizes.height = childSizes.top + childSizes.height - sizes.top
        }, task.id);

        sizes.left = left.sort((a, b) => a - b)[0]

        const mostRight = right.sort((a, b) => a - b).reverse()[0]
        sizes.width = mostRight - sizes.left

        const el = document.createElement('div');
        el.className = 'group';
        el.style.left = sizes.left + 'px';
        el.style.width = sizes.width + 'px';
        el.style.top = sizes.top + 'px';
        el.style.height = sizes.height + 'px';
        return el;
      }
      return false;
    });
  }

  showColumn(column = {}) {
    let col_add = {};
    this.Gantt.config.columns = this.Gantt.config.columns.filter(function (item) {
      if (item.name === 'add' || item.name === 'buttons') {
        col_add = item;
        return false
      }
      else {
        return true
      }
    })
    if (Object.keys(column).length) {
      for (const key in column) {
        if (column[key].hasOwnProperty('data') && column[key].data.length && !this.Gantt.serverList(key)) {
          this.Gantt.serverList(key, column[key].data)
        }
        let serverList = this.Gantt.serverList(key);
        let col_obj = {
          name: key, label: column[key].label, align: "center", resize: true
        }
        if (serverList.length) {
          col_obj.template = function (task) {
            if (Array.isArray(task[key])) {
              let result = '';
              task[key].forEach(function (id) {
                let find = serverList.find(item => item.key == id);
                if (find) {
                  result += `<div style="color: ${find.color}; border: 2px solid ${find.color}6a" class="owner-label" title="${find.label}">${find.label.charAt(0)}</div>`;
                }
              });
              return result
            }
            else {
              let find = serverList.find(item => item.key == task[key]);
              if (find) {
                return '<span style="color: ' + find.color + '">' + find.label + '</span>' || '';
              }
            }
          }
        }
        this.Gantt.config.columns.push(col_obj)
      }
    }
    this.Gantt.config.columns.push(col_add)
  }

  onZoomFit(Gantt = this.Gantt) {
    document.querySelector('[data-action="zoomToFit"]').addEventListener('click', function () {
      zoomToFit()
    })

    const zoomToFit = () => {
      let project = Gantt.getSubtaskDates();
      let areaWidth = Gantt.$task.offsetWidth;
      let scaleConfigs = this.zoomConfig.levels;
      // Iterate through zoom levels to find the best fit.
      let i;
      let reverseScaleConfigs = [...scaleConfigs];
      reverseScaleConfigs = reverseScaleConfigs.reverse();
      for (i = 0; i < reverseScaleConfigs.length; i++) {
        let columnCount = getUnitsBetween(project.start_date, project.end_date, reverseScaleConfigs[i].scales[reverseScaleConfigs[i].scales.length - 1].unit, reverseScaleConfigs[i].scales[reverseScaleConfigs[i].scales.length - 1].step);
        let min_column_width = reverseScaleConfigs[i]?.min_column_width || Gantt.config.min_column_width;
        let a = (columnCount + 2) * min_column_width;
        if ((columnCount + 2) * min_column_width <= areaWidth) {
          break;
        }
      }
      // Ensure the zoom level does not exceed the available configurations.
      if (i === reverseScaleConfigs.length) {
        i--;
      }
      // Apply the determined zoom level and configuration.
      Gantt.ext.zoom.setLevel(reverseScaleConfigs[i].name);
      // applyConfig(reverseScaleConfigs[i], project);
    }

    // get number of columns in timeline
    const getUnitsBetween = (from, to, unit, step) => {
      let start = new Date(from),
        end = new Date(to);
      let units = 0;
      while (start.valueOf() < end.valueOf()) {
        units++;
        start = Gantt.date.add(start, step, unit);
      }
      return units;
    }
  }

  onHideAddTask(task_level = 0, value = true) {
    this.options.hide_add_task_level = value ? task_level : false;
  }

  onButtonComplete($this = this, Gantt = this.Gantt) {
    Gantt.locale.labels.complete_button = Drupal.t("Complete");
    Gantt.attachEvent("onBeforeLightbox", function (id) {
      let task = Gantt.getTask(id);
      if (task.$new || task.type === 'milestone' || task.progress == 1 || (task.type === 'project' && $this.options.dynamic_progress)) {
        Gantt.config.buttons_left = Gantt.config.buttons_left.filter(item => item !== 'complete_button');
      } else {
        if (!Gantt.config.buttons_left.includes('complete_button')) {
          Gantt.config.buttons_left.push('complete_button');
        }
      }
      Gantt.resetLightbox()
      return true;
    }, { id: "before_lightbox_button_complete" });

    Gantt.attachEvent("onLightboxButton", function (button_id, node, e) {
      if (button_id === "complete_button") {
        let id = Gantt.getState().lightbox;
        if (Gantt.isReadonly(id)) {
          Gantt.alert(Drupal.t("You cannot edit this task."));
          return false;
        }
        Gantt.getTask(id).progress = 1;
        Gantt.updateTask(id)
        Gantt.hideLightbox();
      }
    }, {id: "action_complete_task"});
  }

  onLookTaskComplete(value = true, Gantt = this.Gantt) {
    this.options.lock_completed_task = value;
    if (!value) {
      Gantt.config.buttons_left = Gantt.config.buttons_left.filter(item => item !== 'complete_button');
      Gantt.detachEvent("before_lightbox_complete_task");
      Gantt.resetLightbox()
      return true;
    }
    this.options.lock_completed_task = true;
    Gantt.attachEvent("onBeforeLightbox", function (id) {
      let task = Gantt.getTask(id);
      if (task.progress === 1) {
        Gantt.message({
          text: Drupal.t("The task is already completed!"),
          type: "completed"
        });
        return false;
      }
      Gantt.resetLightbox()
      return true;
    }, { id: "before_lightbox_complete_task" });
  }

  setServerList(name = null, data = [], Gantt = this.Gantt) {
    if (!name && data.length) {
      Gantt.serverList(name, data);
    }
  }

  onHideNotWorkingTime(value = true, Gantt = this.Gantt) {
    Gantt.config.skip_off_time = value;
    this.options.hide_weekend_scale = value;
  }

  onMinimumStep(value = true, Gantt = this.Gantt) {
    this.options.round_dnd_dates = value;
    Gantt.config.round_dnd_dates = !value;
  }

  onDragProject(value = true, Gantt = this.Gantt) {
    this.options.drag_project = value;
    Gantt.config.drag_project = value;
    if (!value) {
      return true;
    }
  }

  setLocales(lang = drupalSettings.path.currentLanguage, Gantt = this.Gantt) {
    if (!Gantt.i18n.getLocale(lang)) {
      Gantt.i18n.addLocale(lang, this.i18n);
    }
    Gantt.i18n.setLocale(lang);
  }

  setScale(Gantt = this.Gantt) {
    Gantt.attachEvent('onGanttReady', function () {
      let elGantt = Gantt.$root.closest('.gantt-wrapper');
      let buttons = elGantt.querySelectorAll('[data-action="zoomIn"], [data-action="zoomOut"]');
      if (buttons.length) {
        for (let i = 0; i < buttons.length; i++) {
          buttons[i].onclick = function () {
            const action = this.dataset['action'];
            if (action === 'zoomIn') {
              Gantt.ext.zoom.zoomOut()
            }
            else {
              Gantt.ext.zoom.zoomIn()
            }
          }
        }
      }
    })

    const daysStyle = (date) => {
      return !Gantt.isWorkTime(date) ? "weekend" : "";
    };

    let zoomConfig = {
      levels: [
        {
          name: "year",
          scale_height: 40,
          min_column_width: 80,
          scales: [
            {unit: "year", format: "%Y", step: 1},
          ]
        },
        {
          name: "quarter",
          scale_height: 40,
          min_column_width: 70,
          scales: [
            {unit: "year", format: "%Y", step: 1},
            {
              unit: "quarter", step: 1, format: function (date) {
                let dateToStr = Gantt.date.date_to_str("%M");
                let endDate = Gantt.date.add(Gantt.date.add(date, 3, "month"), -1, "day");
                return dateToStr(date) + " - " + dateToStr(endDate);
              }
            }
          ]
        },
        {
          name: "month",
          scale_height: 40,
          min_column_width: 20,
          scales: [
            {unit: "month", format: "%M %Y", step: 1},
            {unit: "day", format: "%j", step: 1, css: daysStyle}
          ]
        },
        {
          name: "week",
          scale_height: 40,
          min_column_width: 40,
          scales: [
            {unit: "month", format: "%M %Y", step: 1},
            {unit: "week", step: 1, format: Drupal.t("Week") + " %W"},
            {unit: "day", step: 1, format: "%D", css: daysStyle}
          ]
        },
        {
          name: "day",
          scale_height: 40,
          min_column_width: 70,
          scales: [
            {unit: "week", format: Drupal.t("Week") + " %W(%Y)",  step: 1},
            {unit: "day", format: "%D, %d/%m", step: 1, css: daysStyle}
          ]
        },
        {
          name: "hour",
          scale_height: 40,
          min_column_width: 50,
          scales: [
            {unit: "day", format: "%d %M %Y", step: 1, css: daysStyle},
            {unit: "hour", format: "%H:%i", step: 1, css: daysStyle}
          ]
        },
      ],
      activeLevelIndex: 4,
      useKey: "ctrlKey",
      trigger: "wheel",
      element: function () {
        return Gantt.$root.querySelector(".gantt_task");
      }
    }
    this.zoomConfig = zoomConfig;
    Gantt.ext.zoom.init(zoomConfig);
    Gantt.ext.zoom.attachEvent("onAfterZoom", (level, config) => {
      let elGantt = Gantt.$root.closest('.gantt-wrapper');
      let area = Gantt.$root.querySelector('.gantt_container');
      area.classList.toggle('non-zoom-day', config.name === 'year' || config.name === 'quarter');
      elGantt.querySelector('[data-action="scale_gantt"]').value = config.name;
    });

    Gantt.attachEvent("onGanttReady", function () {
      let elGantt = Gantt.$root.closest('.gantt-wrapper')
      elGantt.querySelector('[data-action="scale_gantt"]').addEventListener('change', function () {
        Gantt.ext.zoom.setLevel(this.value);
      })
    })
  }

  setColumn(Gantt = this.Gantt, settingsGantt = this.settingsGantt) {
    let column_text = Gantt.config.columns.find(item => item.name === 'text');
    column_text.min_width = 200;
    let column_duration = Gantt.config.columns.find(item => item.name === 'duration');
    column_duration.resize = true;

    const getResource = (task, source, attr = null) => {
      if (!attr) {
        attr = source;
      }
      const serverList = Gantt.serverList(source)
      if (Array.isArray(task[attr])) {
        return task[attr].map(id => {
          const find = serverList.find(item => item.key == id);
          return find ? `<div class='resource-label' title='${find.label}'>${find.label.substr(0, 1)}</div>` : '';
        }).join('');
      } else {
        const find = serverList.find(item => item.key == task[attr]);
        return find ? `<span>${find.label}</span>` : '';
      }
    }

    if (settingsGantt.priority.length) {
      Gantt.locale.labels['section_priority'] = settingsGantt.server_list_resource[settingsGantt.priority].label;
      if (!Gantt.serverList('priority').length) {
        Gantt.serverList('priority', settingsGantt.server_list_resource[settingsGantt.priority].data)
      }
      let indexCol = Gantt.config.columns.findIndex((item) => item.name === 'duration');
      Gantt.config.columns.splice(indexCol + 1, 0, {
        name: 'priority',
        label: this.i18n.labels.column_priority,
        width: 80,
        align: "center",
        resize: true,
        template: (task) => getResource(task, 'priority', 'priority')
      });
    }

    if (settingsGantt.planned_date && !settingsGantt.use_cdn) {
      let indexCol = Gantt.config.columns.findIndex((item) => item.name === 'duration');
      Gantt.config.columns.splice(indexCol + 1, 0, {
        name: 'planned_duration',
        label: this.i18n.labels.column_planned_duration,
        width: 80,
        align: "center",
        resize: true,
      });
    }

    if (settingsGantt.setting_resource.resource_column.length) {
      let cols = settingsGantt.setting_resource.resource_column.reverse();
      cols.forEach(col => {
        Gantt.locale.labels['section_'+col] = settingsGantt.server_list_resource[col].label;
        if (!Gantt.serverList(col).length) {
          Gantt.serverList(col, settingsGantt.server_list_resource[col].data)
        }
        Gantt.config.columns.unshift({
          name: col,
          label: settingsGantt.server_list_resource[col].label,
          align: "center",
          resize: true,
          template: (task) => getResource(task, col)
        })
      })
    }

    // Add custom column custom field.
    if (settingsGantt.custom_field) {
      let customColumnExist = Gantt.config.columns.findIndex((element) => element.name === 'custom_field');
      if (customColumnExist < 0) {
        Gantt.locale.labels['section_custom_field'] = settingsGantt.custom_field_name;
        Gantt.config.columns.splice(Gantt.config.columns.length - 1, 0, {
          name: 'custom_field',
          label: settingsGantt.custom_field_name,
          align: "center",
          resize: true
        });
      }
    }

  }

  setLightBox(Gantt = this.Gantt, settingsGantt = this.settingsGantt) {

    const format_date_actually = settingsGantt.format_date_actually === 'datetime' ? ["%d", "%m", "%Y", "%H:%i"] : ["%d", "%m", "%Y"];
    const mapping_time_mode = {'duration': 'duration', 'end_time': 'time', 'responsive': 'datetime'}
    // Light box task.
    Gantt.config.lightbox.sections = [
      {
        name: "description",
        height: 70,
        map_to: "text",
        type: "textarea",
        focus: true,
      },
      {
        name: "type",
        height: "auto",
        type: "typeselect",
        map_to: "type",
      },
    ];
    let el_obj = {
      name: "time",
      type: mapping_time_mode[settingsGantt.time_input_mode],
      map_to: "auto",
      autofix_end: true,
      year_range: 11,
      time_format: format_date_actually,
    }
    if (settingsGantt.time_input_mode === 'responsive') {
      delete el_obj.time_format
    }
    Gantt.config.lightbox.sections.push(el_obj)

    let position = Gantt.config.lightbox.sections.findIndex((element) => element.name === 'type');
    Gantt.config.lightbox.sections.splice(position + 1, 0, {
      name: 'custom_field',
      type: "number",
      height: 'auto',
      map_to: 'custom_field',
    })

    if (settingsGantt.setting_resource.resource_lightbox.length) {
      settingsGantt.setting_resource.resource_lightbox.forEach(item => {
        Gantt.locale.labels['section_'+item] = settingsGantt.server_list_resource[item].label;
        if (!Gantt.serverList(item).length) {
          Gantt.serverList(item, settingsGantt.server_list_resource[item].data)
        }
        let position = Gantt.config.lightbox.sections.findIndex((element) => element.name === "type")
        let obj = {
          name: item,
          type: "multiselect",
          height: 'auto',
          options: Gantt.serverList(item),
          map_to: item
        }
        if (settingsGantt.validate_field.custom_resource?.[item]?.cardinality === 1) {
          obj.type = 'onceselect'
        }
        if (settingsGantt.validate_field.custom_resource?.[item]?.required) {
          obj.required = true
        }
        Gantt.config.lightbox.sections.splice(position + 1, 0, obj)
      })
    }

    if (settingsGantt.constraint && !settingsGantt.use_cdn) {
      let position = Gantt.config.lightbox.sections.findIndex((element) => element.name === "type")
      Gantt.config.lightbox.sections.splice(position + 1, 0, {
        name: "constraint",
        type: "constraint"
      })
    }

    if (settingsGantt.priority) {
      Gantt.locale.labels['section_'+settingsGantt.priority] = settingsGantt.server_list_resource[settingsGantt.priority].label;
      if (!Gantt.serverList(settingsGantt.priority).length) {
        Gantt.serverList(settingsGantt.priority, settingsGantt.server_list_resource[settingsGantt.priority].data)
      }
      let position = Gantt.config.lightbox.sections.findIndex((element) => element.name === "type")
      Gantt.config.lightbox.sections.splice(position + 1, 0, {
        name: 'priority',
        type: "select",
        height: 'auto',
        map_to: "priority",
        default_value: "",
        options: Gantt.serverList(settingsGantt.priority)
      })
    }

    if (settingsGantt.select_parent || settingsGantt.required_parent) {
      Gantt.locale.labels.section_parent = this.i18n.labels.section_parent;
      let position = Gantt.config.lightbox.sections.findIndex((element) => element.name === "type")
      let obj = {
        name: "parent",
        height: 'auto',
        type: "parent",
        allow_root: true,
        root_label: Drupal.t("No parent"),
        sort: function (a, b) {
          a = a.$index || 0;
          b = b.$index || 0;
          return a > b ? 1 : (a < b ? -1 : 0);
        },
        filter: function (id, task) {
          return true;
        }
      }
      if (settingsGantt.required_parent) {
        obj.allow_root = false
      }
      Gantt.config.lightbox.sections.splice(position + 1, 0, obj)
    }

    const format_date_planned = settingsGantt.format_date_planned === 'datetime' ? ["%d", "%m", "%Y", "%H:%i"] : ["%d", "%m", "%Y"];
    if (settingsGantt.planned_date && !settingsGantt.use_cdn) {
      el_obj = {
        name: "baseline",
        button: true,
        type: mapping_time_mode[settingsGantt.time_input_mode] + "_optional",
        year_range: 11,
        map_to: {start_date: "planned_start", end_date: "planned_end"},
        time_format: format_date_planned,
      }
      if (settingsGantt.time_input_mode === 'responsive') {
        el_obj.button = "responsive_toggle"
        el_obj.map_to = 'auto'
        delete el_obj.time_format
      }
      let position = Gantt.config.lightbox.sections.findIndex((element) => element.name === "time")
      Gantt.config.lightbox.sections.splice(position + 1, 0, el_obj)
    }

    // Light box project.
    Gantt.config.lightbox.project_sections = [
      {
        name: "description",
        height: 70,
        map_to: "text",
        type: "textarea",
        focus: true
      },
      {
        name: "type",
        height: "auto",
        type: "typeselect",
        map_to: "type",
      },
    ];

    el_obj = {
      name: "time",
      type: mapping_time_mode[settingsGantt.time_input_mode],
      map_to: "auto",
      readonly: true,
      year_range: 11,
      time_format: format_date_actually,
    }
    if (settingsGantt.time_input_mode === 'responsive') {
      delete el_obj.time_format
    }
    Gantt.config.lightbox.project_sections.push(el_obj)

    if (settingsGantt.priority) {
      Gantt.locale.labels['section_'+settingsGantt.priority] = settingsGantt.server_list_resource[settingsGantt.priority].label;
      if (!Gantt.serverList(settingsGantt.priority).length) {
        Gantt.serverList(settingsGantt.priority, settingsGantt.server_list_resource[settingsGantt.priority].data)
      }
      let position = Gantt.config.lightbox.project_sections.findIndex((element) => element.name === "type")
      Gantt.config.lightbox.project_sections.splice(position + 1, 0, {
        name: 'priority',
        type: "select",
        height: 'auto',
        map_to: "priority",
        default_value: "",
        options: Gantt.serverList(settingsGantt.priority)
      })
    }

    if (settingsGantt.select_parent || settingsGantt.required_parent) {
      Gantt.locale.labels.section_parent = this.i18n.labels.section_parent;
      let position = Gantt.config.lightbox.project_sections.findIndex((element) => element.name === "type")
      Gantt.config.lightbox.project_sections.splice(position + 1, 0, {
        name: "parent",
        height: 27,
        type: "parent",
        allow_root: true,
        root_label: Drupal.t("No parent"),
        sort: function (a, b) {
          a = a.$index || 0;
          b = b.$index || 0;
          return a > b ? 1 : (a < b ? -1 : 0);
        },
        filter: function (id, task) {
          return true;
        }
      })
    }

    if (settingsGantt.planned_date && !settingsGantt.use_cdn) {
      el_obj = {
        name: "baseline",
        button: true,
        type: mapping_time_mode[settingsGantt.time_input_mode] + "_optional",
        year_range: 11,
        map_to: {start_date: "planned_start", end_date: "planned_end"},
        time_format: format_date_planned,
      }
      if (settingsGantt.time_input_mode === 'responsive') {
        el_obj.button = "responsive_toggle"
        el_obj.map_to = 'auto'
        delete el_obj.time_format
      }
      let position = Gantt.config.lightbox.project_sections.findIndex((element) => element.name === "time")
      Gantt.config.lightbox.project_sections.splice(position + 1, 0, el_obj)
    }

    // Light box milestone.
    Gantt.config.lightbox.milestone_sections = [
      {
        name: "description",
        height: 70,
        map_to: "text",
        type: "textarea",
        focus: true
      },
      {
        name: "type",
        height: "auto",
        type: "typeselect",
        map_to: "type",
      },
    ];
    el_obj = {
      name: "time",
      type: mapping_time_mode[settingsGantt.time_input_mode],
      map_to: "auto",
      single_date : true,
      year_range: 11,
      time_format: format_date_actually,
    }
    if (settingsGantt.time_input_mode === 'responsive') {
      delete el_obj.time_format
    }
    Gantt.config.lightbox.milestone_sections.push(el_obj)

    if (settingsGantt.setting_resource.resource_lightbox.length) {
      settingsGantt.setting_resource.resource_lightbox.forEach(item => {
        Gantt.locale.labels['section_'+item] = settingsGantt.server_list_resource[item].label;
        if (!Gantt.serverList(item).length) {
          Gantt.serverList(item, settingsGantt.server_list_resource[item].data)
        }
        let position = Gantt.config.lightbox.milestone_sections.findIndex((element) => element.name === "type")
        let obj = {
          name: item,
          type: "multiselect",
          height: 'auto',
          options: Gantt.serverList(item),
          map_to: item
        }
        if (settingsGantt.validate_field.custom_resource?.[item]?.cardinality === 1) {
          obj.type = 'onceselect'
        }
        if (settingsGantt.validate_field.custom_resource?.[item]?.required) {
          obj.required = true
        }
        Gantt.config.lightbox.milestone_sections.splice(position + 1, 0, obj)
      })
    }

    if (settingsGantt.select_parent) {
      Gantt.locale.labels.section_parent = this.i18n.labels.section_parent;
      let position = Gantt.config.lightbox.milestone_sections.findIndex((element) => element.name === "type")
      Gantt.config.lightbox.milestone_sections.splice(position + 1, 0, {
        name: "parent",
        height: 27,
        type: "parent",
        allow_root: true,
        root_label: Drupal.t("No parent"),
        sort: function (a, b) {
          a = a.$index || 0;
          b = b.$index || 0;
          return a > b ? 1 : (a < b ? -1 : 0);
        },
        filter: function (id, task) {
          return true;
        }
      })
    }
  }

  onDurationPlan(Gantt = this.Gantt, settingsGantt = this.settingsGantt) {
    Gantt.attachEvent("onBeforeTaskUpdate", function (id, task) {
      if (task.planned_start && task.planned_end) {
        task.planned_duration = Gantt.calculateDuration({start_date: task.planned_start, end_date: task.planned_end})
      }
    }, { id: 'duration_plan_on_drag_task' });
    Gantt.attachEvent("onBeforeTaskAdd", function (id, task) {
      if (task.planned_start && task.planned_end) {
        task.planned_duration = Gantt.calculateDuration({start_date: task.planned_start, end_date: task.planned_end})
      }
    }, { id: 'duration_plan_after_add_task' });
  }

  onShowPlanedGantt(value = true, Gantt = this.Gantt) {
    if (!value) {
      this.options.baseline = false;
      Gantt.config.bar_height = this.defaultHeightTask.bar_height;
      Gantt.config.row_height = this.defaultHeightTask.row_height;
      Gantt.detachEvent('convert_plan_on_task_loading')
      Gantt.removeTaskLayer(this.taskLayer.baseline)
      return true;
    }
    Gantt.locale.labels.section_baseline = this.i18n.labels.section_baseline;
    Gantt.locale.labels.baseline_enable_button = this.i18n.labels.baseline_enable_button;
    Gantt.locale.labels.baseline_disable_button = this.i18n.labels.baseline_disable_button;
    this.options.baseline = true;
    Gantt.config.bar_height = 21;
    Gantt.config.row_height = 40;
    this.taskLayer.baseline = Gantt.addTaskLayer({
      renderer: {
        render: function draw_planned(task) {
          /* Add baseline. */
          if (task.planned_start && task.planned_end) {
            let sizes = Gantt.getTaskPosition(task, task.planned_start, task.planned_end);
            let el = document.createElement('div');
            el.className = 'baseline';
            el.innerHTML = `<span>${task.text}</span>`;
            el.style.left = sizes.left + 'px';
            el.style.width = sizes.width + 'px';
            el.style.top = sizes.top + Gantt.config.bar_height + 13 + 'px';
            return el;
          }
          return false;
        },
        /* Define getRectangle in order to hook layer with the. */
        getRectangle: function (task, view) {
          if (task.planned_start && task.planned_end) {
            return Gantt.getTaskPosition(task, task.planned_start, task.planned_end);
          }
          return null;
        }
      }
    });

    Gantt.attachEvent("onTaskLoading", function (task) {
      task.planned_start = Gantt.date.parseDate(task.planned_start, "xml_date");
      task.planned_end = Gantt.date.parseDate(task.planned_end, "xml_date");
      return true;
    }, {id: 'convert_plan_on_task_loading'});
  }

  onHighlightCriticalPath(value = true, Gantt = this.Gantt) {
    this.options.critical_path = value;
    Gantt.config.highlight_critical_path = value;
  }

  setWorkTime(work_day = [], holidays = [], Gantt = this.Gantt) {
    Gantt.setWorkTime({ hours:["00:00-24:00"] });
    Gantt.config.work_time = true;
    Gantt.config.correct_work_time  = false;
    /* Add holiday. */
    if (holidays.length) {
      holidays.map(dateString => {
        let dateParts = dateString.split('-');
        let year = parseInt(dateParts[0]);
        let month = parseInt(dateParts[1]) - 1;
        let day = parseInt(dateParts[2]);
        Gantt.setWorkTime({
          date: new Date(year, month, day),
          hours: false
        });
        return new Date(year, month, day);
      });
    }

    if (work_day.length) {
      //changes the working time of working days
      for (let i = 0; i < 7; i++) {
        if (i in work_day) {
          Gantt.setWorkTime({day: i, hours: false});
        }
        else {
          Gantt.setWorkTime({ day: i});
        }
      }
    }

    Gantt.templates.timeline_cell_class = (task, date) => {
      return Gantt.isWorkTime({ task, date }) ? "" : "weekend";
    };

  }

  markerToday(Gantt = this.Gantt) {
    let dateToStr = Gantt.date.date_to_str(Gantt.config.task_date);

    let id = Gantt.addMarker({
      start_date: new Date(),
      css: "today",
      title: dateToStr(new Date())
    });

    setInterval(function () {
      let today = Gantt.getMarker(id);
      if (today) {
        today.start_date = new Date();
        today.title = dateToStr(today.start_date);
        Gantt.updateMarker(id);
      }
    }, 1000 * 60);

    // Make resize marker for two columns.
    Gantt.attachEvent("onColumnResizeStart", function (ind, column) {
      if (!column.tree || ind === 0) {
        return;
      }

      setTimeout(function () {
        let marker = document.querySelector(".gantt_grid_resize_area");
        if (!marker) {
          return;
        }
        let cols = Gantt.getGridColumns();
        let delta = cols[ind - 1].width || 0;
        if (!delta) {
          return;
        }

        marker.style.boxSizing = "content-box";
        marker.style.marginLeft = -delta + "px";
        marker.style.paddingRight = delta + "px";
      }, 1);
    });
  };

  toggleGrid(Gantt = this.Gantt) {
    Gantt.config.show_grid = !Gantt.config.show_grid;
  }

  toggleChart(Gantt = this.Gantt) {
    Gantt.config.show_chart = !Gantt.config.show_chart;
  }

  highLight() {
    this.Gantt.templates.task_class = function (start, end, task) {
      if (this.Gantt.isCriticalTask(task)) {
        return "critical_task";
      }
      return "";
    };

    this.Gantt.templates.link_class = function (link) {
      if (this.Gantt.isCriticalLink(link)) {
        return "critical_link";
      }
      return "";
    };
  }

  renderTemplate() {
    let options = this.options;
    let Gantt = this.Gantt;

    /* progress text*/
    this.Gantt.templates.progress_text = function (start, end, task) {
      if (options.progress_text) {
        return "<span style='text-align:left;'>" + Math.round(task.progress * 100) + "% </span>";
      }
      return "";
    };

    /* task css */
    Gantt.templates.task_class = function (start, end, task) {
      let result = [];
      // baseline
      if (options.baseline) {
        result.push('exist_base_line_bar');
      }
      if (options.lock_completed_task && task.progress === 1) {
        result.push('completed_task');
      }

      // group
      if (task.$virtual) {
        result.push('summary-bar');
      }
      return result.join(' ');
    };

    /* link css */
    Gantt.templates.link_class = function (link) {
      let result = [];
      // baseline
      if (options.baseline) {
        result.push('exist_base_line_link');
      }
      return result.join(' ');
    };

    /* grid row css */
    Gantt.templates.grid_row_class = function (start, end, task) {
      let result = [];
      if (Number.isInteger(options.hide_add_task_level)) {
        if (task.$level >= options.hide_add_task_level) {
          result.push('nested_task');
        }
      }
      if (task.$virtual) {
        return "summary-row"
      }
      return result.join(' ');
    };

    /* task row css */
    Gantt.templates.task_row_class = function (start, end, task) {
      if (task.$virtual)
        return "summary-row"
    };

    /* date task formatted */
    Gantt.templates.task_end_date = function (date) {
      // Option Last of the day.
      if (options.last_of_the_day && Gantt.config.duration_unit === 'day') {
        return Gantt.templates.task_date(new Date(date.valueOf() - 1));
      }
      return Gantt.templates.task_date(new Date(date.valueOf()));
    };

    /* date grid formatted */
    let gridDateToStr = Gantt.date.date_to_str(Gantt.config.date_grid);
    Gantt.templates.grid_date_format = function (date, column) {
      if (column === "end_date") {
        // Option Last of the day.
        if (options.last_of_the_day && Gantt.config.duration_unit === 'day') {
          return gridDateToStr(new Date(date.valueOf() - 1));
        }
        return gridDateToStr(new Date(date.valueOf()));
      }
      else {
        return gridDateToStr(date);
      }
    }
  }

  render() {
    this.renderTemplate();
    this.Gantt.render();
  }

  init(element_id, data = [], links = []) {
    if (this.settingsGantt.order) {
      data.sort(function(a, b) {return a.order - b.order})
    }
    this.renderTemplate();
    this.Gantt.init(element_id);
    this.Gantt.parse({
      "data": data,
      "links": links
    });
  }
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc