Source: CTable.jsx

/**
 * @typedef {Function} CTable#OnEditorChanges
 * @param {string} colname
 * @param {bool} is_modified
 * @param {*} value
 * @param {bool} valid
 */

/**
 * @typedef {Object} CTable#EditorChange
 * @property {bool} is_modified
 * @property {*} value
 * @property {bool} valid
 */

/**
* @event CTable#cteditorchanged
* @property {string} initiator
* @property {CTable#EditorChange[]} changes
*/


/**
 * Base table class.
 *
 * @arg this.props.server {Object} JRPC object.
 * @arg this.props.allow_guest {boolean} Allow getting data without login.
 *
 * @fires CTable#cteditorchanged
 */

class CTable extends Component {

  constructor() {
    super();

    this.topButtonClick = this.topButtonClick.bind(this);
    this.headerXScroll = this.headerXScroll.bind(this);
    this.tableXScroll = this.tableXScroll.bind(this);
    this.onRowClick = this.onRowClick.bind(this);
    this.onTableSelectDropdownClick = this.onTableSelectDropdownClick.bind(this);
    this.onTableSelectClick = this.onTableSelectClick.bind(this);
    this.onSaveClick = this.onSaveClick.bind(this);
    this.onCancelClick = this.onCancelClick.bind(this);
    this.onEditorChanges = this.onEditorChanges.bind(this);
    this.reloadData = this.reloadData.bind(this);
    this.getAffectedKeys = this.getAffectedKeys.bind(this);
    this.showError = this.showError.bind(this);

    this.onPanel0DropdownClick = this.onPanel0DropdownClick.bind(this);
    this.onPanel1DropdownClick = this.onPanel1DropdownClick.bind(this);

    this.askUser = this.askUser.bind(this);
    this.userResolveYes = this.userResolveYes.bind(this);
    this.userResolveNo = this.userResolveNo.bind(this);

    this.onResetColumns = this.onResetColumns.bind(this);
    this.onCloseColumns = this.onCloseColumns.bind(this);

    this.onResetSorting = this.onResetSorting.bind(this);
    this.onSortingChange = this.onSortingChange.bind(this);
    this.onCloseSorting = this.onCloseSorting.bind(this);

    this.onResetFilter = this.onResetFilter.bind(this);
    this.onFilterChange = this.onFilterChange.bind(this);
    this.onCloseFilter = this.onCloseFilter.bind(this);



    this.setState({
      width: 50,
      fontSize: 100,
      table_list: [],
      current_table: {},
      links: [],
      progress: true,
      last_row_clicked: null,
      topline_buttons: [

        {name:"back", icon: "arrow_back", label:_("Go back"), enabled: false, style:"", icon_only:false, panel:1},

        {name:"enter", icon: "subdirectory_arrow_right", label:_("Enter"), enabled: false, style:"", icon_only:false, panel:1},
        {name:"enter", icon: "subdirectory_arrow_right", label:_("Enter"), enabled: false, style:"", icon_only:false, panel:1},
        {name:"enter", icon: "subdirectory_arrow_right", label:_("Enter"), enabled: false, style:"", icon_only:false, panel:1},
        {name:"enter", icon: "subdirectory_arrow_right", label:_("Enter"), enabled: false, style:"", icon_only:false, panel:1},
        {name:"enter", icon: "subdirectory_arrow_right", label:_("Enter"), enabled: false, style:"", icon_only:false, panel:1},

        {name:"add", icon: "add", label:_("Add"), enabled: true, style:"is-primary", icon_only:true, panel:1},
        {name:"edit", icon: "edit", label:_("Edit"), enabled: false, style:"is-warning", icon_only:true, panel:1},
        {name:"duplicate", icon: "content_copy", label:_("Duplicate"), enabled: false, style:"is-warning", icon_only:true, panel:1},
        {name:"delete", icon: "delete", label:_("Delete"), enabled: false, style:"is-danger", icon_only:true, panel:1},

        {name:"reload", icon: "refresh", label:_("Reload"), enabled: true, style:"", icon_only:true, panel:0},

        {name:"filter", icon: "filter_alt", label:_("Filter"), enabled: true, style:"", icon_only:true, panel:0},
        {name:"sort", icon: "sort", label:_("Sorting"), enabled: true, style:"", icon_only:true, panel:0},
        {name:"columns", icon: "list_alt", label:_("Columns"), enabled: true, style:"", icon_only:true, panel:0},

        {name:"select_all", icon: "select_all", label:_("Select all"), enabled: true, style:"", icon_only:true, panel:0},
        {name:"clear_all", icon: "remove_selection", label:_("Clear all"), enabled: true, style:"", icon_only:true, panel:0},

        {name:"zoom_in", icon: "zoom_in", label:_("Zoom In"), enabled: true, style:"", icon_only:true, panel:0},
        {name:"zoom_out", icon: "zoom_out", label:_("Zoom Out"), enabled: true, style:"", icon_only:true, panel:0},
        {name:"zoom_reset", icon: "search", label:_("Reset Zoom"), enabled: false, style:"", icon_only:true, panel:0},

      ],

      view_columns: [],
      view_sorting: [],
      view_filtering: [],

      table_path:[ ],
      table_path_labels:[ ],
      table_columns:[ ],
      table_subtables:[ ],
      table_rows:[ ],
      table_row_status:[ ],

      return_keys: null,

      table_select_menu_active: false,
      editor_show: false,
      columns_panel_show: false,
      sorting_panel_show: false,
      filtering_panel_show: false,
      editor_affected_rows: [],
      editor_changes: {},
      editor_operation: '',
      panel0_menu_active: false,
      panel1_menu_active: false,
      ask_dialog_active: false,
      ask_dialog_text:"text",
      ask_dialog_promise_resolve: null,
      ask_dialog_promise_reject: null,
    });

  }

  componentDidMount() {
      var self = this;
      self.props.server.version().then(x => console.log(x));
      let tables_p = self.props.server.CTableServer.tables();
      let links_p = self.props.server.CTableServer.links();

      //self.props.server.slots.tableChanged.push(this.reloadData); Slots support

      Promise.all([tables_p, links_p]).then(x => {
        self.state.table_list = x[0];
        self.state.links = x[1];

        var default_table = self.state.table_list.filter(x => x.is_default);
        if (default_table.length == 0){
          default_table = [self.state.table_list[0]];
        }

        self.loadTable(default_table[0].name, null);
        self.setState({});
      });
  }

  resetColumns(){
    var self = this;
    self.state.view_columns = self.state.table_columns.map(x => { return {name:x.name, enabled:x.enabled} } );
  }

  resetSorting(){
    var self = this;
    self.state.view_sorting = self.state.table_columns.map(x => {

      var sorting = self.state.current_table.default_sorting.filter(y => x.name in y);

      if (sorting.length > 0) {
        return {name:x.name, sorting:sorting[0][x.name]};
      } else {
        return {name:x.name, sorting:""};
      }

    });
  }

  resetFiltering(){
    this.state.view_filtering = deep_copy(this.state.current_table.default_filtering);
  }

  loadTable(name, path_part){
    var self = this;

    var table = self.state.table_list.filter(x => x.name == name)[0];

    let table_columns_p = self.props.server.CTableServer.columns(table.name);
    let table_subtables_p = self.props.server.CTableServer.subtables(table.name);

    Promise.all([table_columns_p, table_subtables_p]).then((mx) => {
      var c, sb;
      [c, sb] = mx;

      self.state.current_table = table;
      self.state.width = table.width;
      self.state.table_columns = c;

      self.resetColumns();
      self.resetSorting();
      self.resetFiltering();

      self.state.table_subtables = sb;
      this.state.table_rows = [];
      this.state.table_row_status = [];

      if(path_part !== null){
        this.state.view_columns = path_part.view_settings.view_columns;
        this.state.view_filtering = path_part.view_settings.view_filtering;
        this.state.view_sorting = path_part.view_settings.view_sorting;
        this.state.return_keys = path_part.keys;
      }

      self.setState({}, self.reloadData);
    });
  }

  reloadData(){
    this.setState({progress: true});

    var keys = [];
    if(this.state.return_keys === null){
      keys = this.getAffectedKeys();
    } else {
      keys = [this.state.return_keys];
    }

    var sorting = this.state.view_sorting.filter(x => x.sorting != "").map(function (x) {
      return {[x.name]: x.sorting};
    });

    var filters = this.state.view_filtering.map(x => [x.operator, x.column, x.value]);

    let table_rows_p = this.props.server.CTableServer.select(this.full_table_path(),filters,sorting).then((r) => {
      this.state.table_rows = r['rows'];
      this.state.table_row_status = [];
      this.state.table_rows.forEach(x => {


        var issel = false;

        keys.forEach(w =>{ if(!issel) {issel = Object.keys(w).map(q => w[q] == x[q]).every(u => u)} });

        this.state.table_row_status.push({selected: issel});

      })
      this.state.progress = false;
      this.state.last_row_clicked = null;
      this.state.return_keys = null;
      this.setState({}, () => {
        this.enablePanelButtons()
      });
    }).catch((e) => {this.showError(e); this.setState({progress: false});});
  }

  topButtonClick(e) {
    var tg = unwind_button_or_link(e);


    if(tg.dataset['name'] == "zoom_in" || tg.dataset['name'] == "zoom_out" || tg.dataset['name'] == "zoom_reset") {

        if(tg.dataset['name'] == "zoom_in"){
          if (this.state.fontSize < 300){
              this.state.fontSize = this.state.fontSize + 25;
          }

        }

        if(tg.dataset['name'] == "zoom_out"){
          if (this.state.fontSize > 25){
            this.state.fontSize = this.state.fontSize - 25;
          }
        }

        if(tg.dataset['name'] == "zoom_reset"){
          this.state.fontSize = 100;
        }


        if (this.state.fontSize != 100)
          this.state.topline_buttons.filter(x => x.name == "zoom_reset").forEach(x => x.enabled = true);
        else
          this.state.topline_buttons.filter(x => x.name == "zoom_reset").forEach(x => x.enabled = false);

        this.setState({});
        return;

    }


    if(tg.dataset['name'] == "select_all"){
      if(this.state.editor_show == true) return;
      this.state.table_row_status = [];
      this.state.table_rows.forEach(x => {this.state.table_row_status.push({selected: true})});
      this.setState({}, () => this.enablePanelButtons());
      return;
    }

    if(tg.dataset['name'] == "clear_all"){
      if(this.state.editor_show == true) return;
      this.state.table_row_status = [];
      this.state.table_rows.forEach(x => {this.state.table_row_status.push({selected: false})});
      this.setState({}, () => this.enablePanelButtons());
      return;
    }

    if(tg.dataset['name'] == "enter"){
      var self = this;

      var gk = this.getAffectedKeys()[0];
      var subtab = this.state.table_subtables.filter(x => x.name == tg.dataset['table'])[0];

      var key_const = [];
      Object.keys(gk).forEach((k)=>{key_const.push(["eq",k,gk[k]])});

      this.props.server.CTableServer.options(this.full_table_path(), key_const).then((r) => {
        var uid_str = [];
        r["keys"].forEach(k => {uid_str.push(r["rows"][0][k])})

        this.state.table_path_labels.push(r["rows"][0][r["label"]]+" ("+uid_str.join(",")+")");
        self.setState({});
      }).catch((e) => {this.showError(e)});

      this.state.table_path.push({table: this.state.current_table.name, keys:gk, label:this.state.current_table.label, mapping:subtab.mapping, view_settings:deep_copy({view_columns: this.state.view_columns, view_filtering:this.state.view_filtering, view_sorting:this.state.view_sorting}), table_row_status:deep_copy(this.state.table_row_status)});

      this.hideAllEditors();
      this.loadTable(tg.dataset['table'], null);
      return;
    }

    if(tg.dataset['name'] == "back"){
      this.state.table_path_labels.pop();
      var path_part = this.state.table_path.pop();
      this.hideAllEditors();
      this.loadTable(path_part.table, path_part);
      return;
    }

    if(tg.dataset['name'] == "columns"){
      if(this.state.columns_panel_show){
        this.setState({columns_panel_show: false});
      } else {
        this.hideAllEditors();
        this.setState({columns_panel_show: true});
      }
      return;
    }

    if(tg.dataset['name'] == "sort"){
      if(this.state.sorting_panel_show){
        this.setState({sorting_panel_show: false});
      } else {
        this.hideAllEditors();
        this.setState({sorting_panel_show: true});
      }
      return;
    }

    if(tg.dataset['name'] == "filter"){
      if(this.state.filtering_panel_show){
        this.setState({filtering_panel_show: false});
      } else {
        this.hideAllEditors();
        this.setState({filtering_panel_show: true});
      }
      return;
    }

    if(tg.dataset['name'] == "reload"){
      this.reloadData();
      return;
    }

    if(tg.dataset['name'] == "add"){
      this.hideAllEditors();
      this.setState({editor_show: true, editor_affected_rows: [], editor_changes: {}, editor_operation: 'add'});
      return;
    }

    if(tg.dataset['name'] == "edit"){
      var nrecords = this.getAffectedKeys().length;
      if(nrecords == 0) return; // no rows affected
      this.hideAllEditors();
      this.setState({editor_show: true, editor_affected_rows: this.state.table_rows.filter((x,i) => this.state.table_row_status[i].selected), editor_changes: {}, editor_operation: 'edit'});
      return;
    }

    if(tg.dataset['name'] == "duplicate"){
      var nrecords = this.getAffectedKeys().length;
      if(nrecords == 0) return; // no rows affected
      this.askUser(N_("Duplicate %d record?","Duplicate %d records?", nrecords)).then(()=>{
        this.setState({progress: true});
        this.props.server.CTableServer.duplicate(this.full_table_path(), this.getAffectedKeys()).then(()=> {this.reloadData();}).catch((e) => {this.showError(e); this.setState({progress: false})});
      }, ()=>{});
    }

    if(tg.dataset['name'] == "delete"){
      var nrecords = this.getAffectedKeys().length;
      if(nrecords == 0) return; // no rows affected
      this.askUser(N_("Delete %d record?","Delete %d records?", nrecords)).then(()=>{
        this.setState({progress: true});
        this.props.server.CTableServer.delete(this.full_table_path(), this.getAffectedKeys()).then(()=> {this.reloadData();}).catch((e) => {this.showError(e); this.setState({progress: false})});
      }, ()=>{});

    }

  }

  full_table_path(){
    var path = this.state.table_path.map(x => { return {table:x.table, keys:x.keys, mapping:x.mapping}; });
    return [].concat(path, [{table: this.state.current_table.name}]);
  }

  getAffectedKeys(){
    var affected_rows = this.state.table_rows.filter((x,i) => this.state.table_row_status[i].selected);
    var keys = this.state.table_columns.filter((x) => x.is_key).map((x) => x.name);
    var keys_values = affected_rows.map((x) => {
      var res = {};
      keys.forEach((k) => res[k] = x[k]);
      return res;
    });
    return keys_values;
  }

  headerXScroll(e){
    document.querySelector(".ctable-scroll-main-table").scrollLeft = e.target.scrollLeft;
  }

  tableXScroll(e){
    document.querySelector(".ctable-scroll-head-table").scrollLeft = e.target.scrollLeft;
  }

  onRowClick(e){
    if(this.state.editor_show == true) return;

    this.state.table_return = {};

    var tg = unwind_tr(e);
    var ns = [];
    var nv = this.state.last_row_clicked;
    this.state.last_row_clicked = Number(tg.dataset['rowindex']);

    if (e.getModifierState("Shift")){
      if (nv !== null){
        if (nv > Number(tg.dataset['rowindex'])){
            while (nv >= Number(tg.dataset['rowindex'])){
              ns.push(nv);
              nv = nv - 1;
            }
        } else {
            while (nv <= Number(tg.dataset['rowindex'])){
              ns.push(nv);
              nv = nv + 1;
            }
        }
      }
    } else {
      ns = [Number(tg.dataset['rowindex'])];
    }

    //console.log(ns);

    var target_state = !this.state.table_row_status[Number(tg.dataset['rowindex'])].selected;
    ns.forEach((n) => {this.state.table_row_status[n].selected = target_state});
    this.setState({}, () => this.enablePanelButtons());
  }

  enablePanelButtons(){
    var sel_count = this.state.table_row_status.filter(x => x.selected == true).length;

    if(sel_count == 0) {
      this.state.topline_buttons.filter(x => x.name == "edit").forEach(x => x.enabled = false);
      this.state.topline_buttons.filter(x => x.name == "duplicate").forEach(x => x.enabled = false);
      this.state.topline_buttons.filter(x => x.name == "delete").forEach(x => x.enabled = false);
    } else {
      this.state.topline_buttons.filter(x => x.name == "edit").forEach(x => x.enabled = true);
      this.state.topline_buttons.filter(x => x.name == "duplicate").forEach(x => x.enabled = true);
      this.state.topline_buttons.filter(x => x.name == "delete").forEach(x => x.enabled = true);
    }

    var self = this;

    this.state.topline_buttons.filter(x => x.name == "back").forEach(x => x.enabled = self.state.table_path.length > 0);

    if(sel_count == 1 && this.state.table_subtables.length > 0) {
        this.state.table_subtables.forEach(function (t,i){
          self.state.topline_buttons.filter(x => x.name == "enter").forEach((x,j) => {
            if(i == j) {
              x.enabled = true;
              x.label = t.label;
              x.table = t.name;
            }
          });
        });
    } else {
        self.state.topline_buttons.filter(x => x.name == "enter").forEach(x => {
            x.enabled = false;
          });
    }

    this.setState({});
  }

  onTableSelectDropdownClick(){
    this.setState({table_select_menu_active: !this.state.table_select_menu_active});
  }

  onTableSelectClick(x){
    var tbl = this.state.table_list.filter(y => y.name == unwind_button_or_link(x).dataset.label)[0];
    this.setState({table_select_menu_active: false});
    this.loadTable(tbl.name, null);
  }


  onSaveClick(){
    if (Object.keys(this.state.editor_changes).filter(x => this.state.editor_changes[x].is_modified == true && this.state.editor_changes[x].valid == false).length > 0){
      return; //Has invalid fields
    }

    if (Object.keys(this.state.editor_changes).filter(x => this.state.editor_changes[x].is_modified == true).length == 0){
      return; //Has no modified fields
    }

    var modified_data = Object.keys(this.state.editor_changes).filter(x => this.state.editor_changes[x].is_modified == true && this.state.editor_changes[x].valid == true);
    var data_to_send = {};
    modified_data.forEach(x => {data_to_send[x] = this.state.editor_changes[x].value});


    if(this.state.editor_operation == 'add'){
      this.setState({progress: true});
      this.props.server.CTableServer.insert(this.full_table_path(), data_to_send).then(()=> {this.setState({editor_show: false}); this.reloadData();}).catch((e) => {this.showError(e); this.setState({progress: false});});
    }

    if(this.state.editor_operation == 'edit'){
      var nrecords = this.state.editor_affected_rows.length;
      if(nrecords == 0) return; // no rows affected
      if(nrecords == 1) {
        this.setState({editor_show: false, progress: true});
        this.props.server.CTableServer.update(this.full_table_path(), this.getAffectedKeys(), data_to_send).then(()=> {this.setState({editor_show: false});  this.reloadData();}).catch((e) => {this.showError(e); this.setState({progress: false});});
      } else {
        this.askUser(N_("Update %d record?","Update %d records?", nrecords)).then(()=>{
          this.setState({editor_show: false, progress: true});
          this.props.server.CTableServer.update(this.full_table_path(), this.getAffectedKeys(), data_to_send).then(()=> {this.setState({editor_show: false});  this.reloadData();}).catch((e) => {this.showError(e); this.setState({progress: false});});
        }, ()=>{});
      }
    }

  }

  hideAllEditors(){
    this.setState({sorting_panel_show: false, columns_panel_show: false, editor_show: false});
  }

  showError(e){
    alert(String(e.code) + ": " + e.message);
  }

  onCancelClick(){
    this.setState({editor_show: false, editor_changes: [], editor_operation: ''});
  }

  onEditorChanges(colname, is_modified, value, valid){
      this.state.editor_changes[colname] = {is_modified: is_modified, value: value, valid: valid};

      this.base.querySelectorAll(".ctable-editor-control div").forEach(x => x.dispatchEvent(new CustomEvent("cteditorchanged", { detail: {initiator:colname, changes:this.state.editor_changes} })));
  }

  onCloseColumns(){
    this.setState({columns_panel_show: false});
  }

  onResetColumns(){
    this.resetColumns();
    this.setState({});

  }

  onSortingChange(){
    this.reloadData();
  }

  onFilterChange(){
    this.reloadData();
  }

  onResetSorting(){
    this.resetSorting();
    this.setState({});
    this.reloadData();
  }

  onCloseSorting(){
    this.setState({sorting_panel_show: false});
    this.reloadData();
  }


  onResetFilter(){
    this.resetFiltering();
    this.setState({});
    this.reloadData();
  }

  onCloseFilter(){
    this.setState({filtering_panel_show: false});
    this.reloadData();
  }

  onApplySorting(){
    this.reloadData();
  }

  onPanel0DropdownClick(){
    this.setState({panel0_menu_active: !this.state.panel0_menu_active});
  }

  onPanel1DropdownClick(){
    this.setState({panel1_menu_active: !this.state.panel1_menu_active});
  }

  askUser(question){
    return new Promise((resolve, reject) => {
      this.setState({ask_dialog_text: question, ask_dialog_active: true, ask_dialog_promise_resolve: resolve, ask_dialog_promise_reject: reject});
    });
  }

  userResolveYes(){
    this.setState({ask_dialog_active: false});
    this.state.ask_dialog_promise_resolve();
  }


  userResolveNo(){
    this.setState({ask_dialog_active: false});
    this.state.ask_dialog_promise_reject();
  }


  render() {

  var self = this;

  return <div>
  <div class={cls("modal", self.state.ask_dialog_active ? "is-active" : "")}>
    <div class="modal-background" onClick={this.userResolveNo}></div>
    <div class="modal-content">
      <div class="mb-4" style="font-weight: bold;">
        <p>{self.state.ask_dialog_text}</p>
      </div>
      <div class="has-text-center">
        <button class="button is-primary is-soft mr-4" style="min-width: 8em;" onClick={this.userResolveYes}>{_("Yes")}</button>
        <button class="button is-warning is-soft" style="min-width: 8em;" onClick={this.userResolveNo}>{_("No")}</button>
      </div>
    </div>
    <button class="modal-close is-large" aria-label="close" onClick={this.userResolveNo}></button>
  </div>
  <section class="section pt-3 pb-0 pl-0 pr-0 ctable-top-panel">
    <div class="ctable-top-panel-block" style={sty("max-width",self.state.width+2+"em")}>
      <div  class="pl-3 pr-2">
        <table class="ctable-title-table">
          <colgroup>
            <col span="1" style="width: 20%;"/>
            <col span="1" style="width: 60%;"/>
            <col span="1" style="width: 20%;"/>
          </colgroup>
          <tr>
            <td>
              <div class={cls("dropdown", self.state.table_select_menu_active ? "is-active" : "")}>
                <div class="dropdown-trigger">
                  <button class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu" onClick={this.onTableSelectDropdownClick}>
                    <span class="icon"><span class="material-symbols-outlined">menu</span></span>
                    <span class="icon is-small"><span class="material-symbols-outlined">arrow_drop_down</span></span>
                  </button>
                </div>
                <div class="dropdown-menu" id="dropdown-menu" role="menu">
                  <div class="dropdown-content">
                    {self.state.table_list.map(x =>
                        <a class={cls("dropdown-item", x.name == self.state.current_table.name ? "is-active" : "")} data-label={x.name} onClick={this.onTableSelectClick}><span class="material-symbols-outlined-small">lists</span> {x.label}</a>
                    )}
                    <hr class="dropdown-divider" />
                    {self.state.links.map(x =>
                      <a class="dropdown-item" href={x.url}><span class="material-symbols-outlined-small">link</span> {x.label}</a>
                    )}
                  </div>
                </div>
              </div>
            </td>
            <td>
              <div class="ctable-top-panel-text">
                <div style="display:inline-block;">
                {self.state.table_path_labels.map(x =>
                  <><span class="material-symbols-outlined-small">check_box</span>&nbsp;{x}&nbsp;</>
                )}
                  <><span class="material-symbols-outlined-small">lists</span>&nbsp;{self.state.current_table.label}&nbsp;</>
                </div>
              </div>
            </td>
            <td class="has-text-right">
              <div class="dropdown is-right">
                <div class="dropdown-trigger">
                  <button class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu">
                    <span class="icon"><span class="material-symbols-outlined">person</span></span>
                    <span class="icon is-small"><span class="material-symbols-outlined">arrow_drop_down</span></span>
                  </button>
                </div>
                <div class="dropdown-menu" id="dropdown-menu" role="menu">
                  <div class="dropdown-content has-text-left">
                    <a class="dropdown-item"><span class="material-symbols-outlined-small">login</span> Войти в систему</a>
                    <hr class="dropdown-divider" />
                    <a class="dropdown-item"><span class="material-symbols-outlined-small">logout</span> Выйти из системы</a>
                  </div>
                </div>
              </div>
            </td>
          </tr>
        </table>
      </div>
      <div  class="ctable-command-panel">
        <div class="ctable-button-row">
          <div class="ctable-button-row-left">
            {self.state.topline_buttons.filter(x => x.enabled  && x.panel == 0).map(x =>
              <div class="has-text-centered m-1"  style="display:inline-block;">
                <button class={cls("button","is-small","is-soft",x.style)} data-name={x.name} onClick={this.topButtonClick} title={x.label}><span class="material-symbols-outlined">{x.icon}</span>{x.icon_only ? "" : " "+x.label}</button>
              </div>
            )}
          </div>
          <div class="ctable-button-row-right has-text-right">
            <div class={cls("dropdown", "is-right", self.state.panel0_menu_active ? "is-active" : "")}>
              <div class="dropdown-trigger">
                <button class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu-panel0" onClick={this.onPanel0DropdownClick}>
                  <span class="icon"><span class="material-symbols-outlined">build</span></span>
                  <span class="icon is-small"><span class="material-symbols-outlined">arrow_drop_down</span></span>
                </button>
              </div>
              <div class="dropdown-menu" id="dropdown-menu-panel0" role="menu">
                <div class="dropdown-content has-text-left">
                  {self.state.topline_buttons.filter(x => x.enabled  && x.panel == 0).map(x =>
                    <a class={cls("dropdown-item",x.style)} data-name={x.name} onClick={this.topButtonClick} data-table={x.table}><span class="material-symbols-outlined-small">{x.icon}</span> {x.label}</a>
                  )}
                </div>
              </div>
            </div>
          </div>
        </div>
        <div class="ctable-button-row">
          <div class="ctable-button-row-left">
            {self.state.topline_buttons.filter(x => x.enabled  && x.panel == 1).map(x =>
              <div class="has-text-centered m-1"  style="display:inline-block;">
              <button class={cls("button","is-small","is-soft",x.style)} data-name={x.name} onClick={this.topButtonClick} title={x.label} data-table={x.table}><span class="material-symbols-outlined">{x.icon}</span>{x.icon_only ? "" : " "+x.label}</button>
              </div>
            )}
          </div>
          <div class="ctable-button-row-right has-text-right">
            <div class={cls("dropdown", "is-right", self.state.panel1_menu_active ? "is-active" : "")}>
              <div class="dropdown-trigger">
                <button class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu-panel1" onClick={this.onPanel1DropdownClick}>
                  <span class="icon"><span class="material-symbols-outlined">more_vert</span></span>
                  <span class="icon is-small"><span class="material-symbols-outlined">arrow_drop_down</span></span>
                </button>
              </div>
              <div class="dropdown-menu" id="dropdown-menu-panel1" role="menu">
                <div class="dropdown-content has-text-left">
                  {self.state.topline_buttons.filter(x => x.enabled  && x.panel == 1).map(x =>
                    <a class={cls("dropdown-item", "is-soft", x.style)} data-name={x.name} onClick={this.topButtonClick}><span class="material-symbols-outlined-small">{x.icon}</span> {x.label}</a>
                  )}
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <CHeaderTable width={self.state.width} fontSize={self.state.fontSize} table={self} columns={self.state.table_columns} view_columns={self.state.view_columns} view_sorting={self.state.view_sorting} view_filtering={self.state.view_filtering} onHeaderXScroll={self.headerXScroll} progress={self.state.progress} />
    </div>
  </section>
  <CPageTable width={self.state.width} fontSize={self.state.fontSize} columns={self.state.table_columns} view_columns={self.state.view_columns} row_status={self.state.table_row_status} rows={self.state.table_rows} onRowClick={self.onRowClick}  onTableXScroll={self.tableXScroll} editorShow={self.state.editor_show || self.state.sorting_panel_show || self.state.columns_panel_show || self.state.filtering_panel_show }/>
  {self.state.editor_show ? <CEditorPanel width={self.state.width} columns={self.state.table_columns} affectedRows={self.state.editor_affected_rows} noSaveClick={self.onSaveClick} noCancelClick={self.onCancelClick} onEditorChanges={self.onEditorChanges} /> : ""}
  {self.state.columns_panel_show ? <CColumnsPanel width={self.state.width} table={self} onColumnChange={self.onColumnChange} onResetColumns={self.onResetColumns}  onCloseColumns={self.onCloseColumns}/>: ""}
  {self.state.sorting_panel_show ? <CSortingPanel width={self.state.width} table={self} onResetSorting={self.onResetSorting}  onCloseSorting={self.onCloseSorting} onSortingChange={self.onSortingChange} />: ""}
  {self.state.filtering_panel_show ? <CFilterPanel width={self.state.width} table={self} onResetFilter={self.onResetFilter}  onCloseFilter={self.onCloseFilter} onChangeFilter={self.onFilterChange} />: ""}

  </div>;

  }


}