# src/admin.js

    import React from "react";
    import _ from "lodash";
    import Set from "./set.js";
    import Select from "react-select";
    import Modal from "react-modal";
    const uuidv1 = require("uuid/v1");

    //require("./admin.scss");
    export const display_type = {
      list: "list",
      change: "change"
    };
    const customStyles = {
      content: {
        top: "50%",
        left: "50%",
        right: "auto",
        bottom: "auto",
        marginRight: "-50%",
        transform: "translate(-50%, -50%)"
      }
    };
    /** Admin Class extends React.Component. To implement a CRUD interface similar to Django Admin you need to extend the Admin class. */
    class Admin extends React.Component {
      constructor(props) {
        super(props);

        this.name = "default";
        this.name_plural = "defaults";
        this.live_search = false;
        this.field_transforms = {};
        this.header_transforms = {};
        this.extra_fields = {};
        this.list_display = [];
        this.list_display_links = [];
        this.list_per_page = 10;
        this.sort_fields = [];
        this.is_object_equal = (a, b) => a == b;
        this.pages_in_pagination = 3;
        this.actions = {
          delete: selected_objects => {}
        };
        this._change_uuid = uuidv1();
        this.queryset = [];
        this._all_selected = false;

        if (props) {
          this.bind_exclude = props.bind_exclude ? props.bind_exclude : [];
          _.keys(props).map(key => {
            if (typeof props[key] == "function") {
              if (_.includes(key, this.bind_exclude)) {
                this[key] = props[key];

                this[key].bind(this);
              }
            } else {
              this[key] = props[key];
            }
          });
        }

        /**
         * Initialize the state of the component
         */
        this.state = {
          display_type: display_type.list,
          page_number: 1,
          object: null,
          selected_objects: new Set([], this.is_object_equal),
          total: 1,
          filter_options: {},
          filter_values: []
        };

        let queryset = this.get_queryset(
          this.state.page_number,
          this.list_per_page,
          this.state.queryset
        )
          ? this.get_queryset(
              this.state.page_number,
              this.list_per_page,
              this.state.queryset
            )
          : [];
        this.state.queryset = queryset;
        this.state.total = queryset.length;
        this._handle_search = this._handle_search.bind(this);
        this._select_all = this._select_all.bind(this);
        this._select_one = this._select_one.bind(this);
        this.response_change = this.response_change.bind(this);
        this.response_add = this.response_add.bind(this);
      }
      /**
       * This function returns an array of objects that will serve as the
       * queryset for the admin interface. Typically involves an HTTP request
       * to a backend.
       * @param {number} page_number - The current page number
       * @param {number} list_per_page - Number items to list per page. Defaults to `list_per_page`
       * @param {object[]} queryset - The current queryset
       * @returns {object[]} An array of objects. - Objects to display
       */

      get_queryset(
        page_number = this.state.page_number,
        list_per_page = this.get_list_per_page(),
        queryset = this.state.queryset
      ) {
        return this.queryset;
      }

      /**
       * This functions returns a JSON Schema Form for editing
       * the objects in the array returned by get_queryset(). This method needs to be overridden to
       * so as to return a Form Component for the object.  Learn more on
       * JSON schema forms from https://github.com/mozilla-services/react-jsonschema-form
       * and JSON Schema from https://spacetelescope.github.io/understanding-json-schema/
       * @param {object} object - The current selected object.
       * @returns  A JSON Schema Form Component.
       */

      get_form(object = null) {
        return {};
      }
      /**
       * Returns a true/false value. Controls wether search is implement on live input or not.
       * Can be overriden by the live_search member variable. Default is false.
       *
       *@return {boolean}
       */

      get_live_search() {
        return this.live_search;
      }

      /**
       *Returns an object whose properties are field names corresponding to properties of any object
       *in the queryset and whose values are transform functions. The example below will transform each "name"
       *property of objects in the queryset to upper case in the list display view.
       *@example
       *get_field_transforms()
       *{
       * return { 'name' : function(name,object)
       *                   {
       *                       return name.toUpperCase()
       *                   }
       *         }
       *
       *}
       *@return {object}
       */

      get_field_transforms() {
        if (this.field_transforms) {
          return this.field_transforms;
        }
        return {};
      }

      /**
       *Returns an object whose properties are field names corresponding to properties of any object
       *in the queryset and whose values are transform functions. The example below will transform the header "name" to upper case in the list display view.
       *@example
       *get_header_transforms()
       *{
       * return { 'name' : function(name)
       *                   {
       *                       return name.toUpperCase()
       *                   }
       *         }
       *}
       *
       *@return {object}
       */

      get_header_transforms() {
        if (this.header_transforms) {
          return this.header_transforms;
        }

        return {};
      }

      /**
       * Returns an object whose properties are extra  field names not corresponding to properties of any object
       * in the queryset and whose values are display functions. This will create extra fields that are not tied to objects. Extra fields have to be manually included in the `list_display` in order to appear in the list display page. The display functions take the current object being rendered in the table row and  the field name
       *@example
       *get_extra_fields()
       *{
       * return { 'now' : function(object,label)
       *                   {
       *                      return moment(new Date()).format('LLL');
       *                   }
       *         }
       *}
       *
       *@return {object}
       */

      get_extra_fields() {
        if (this.extra_fields) {
          return this.extra_fields;
        }

        return {};
      }
      /**
       * Returns the number of items to be listed in a page. Can be overridden by `list_per_page`.
       *@return {number} The number of objects in a page
       */

      get_list_per_page() {
        if (this.list_per_page) {
          return this.list_per_page;
        }
        return 10;
      }

      /**
         * Gets an actions object whose properties are action names and values are action methods.
         * This can be overridden by the action member variable. The default "delete" method is not
         * implemented.
         *
         * Each actions object property (e.g. "delete") is passed an array of selected objects. One
         * can then handle those objects. Actions will appear on the list display page within a
         * dropdown. Selecting an action should have the action method applied to all currently
         * selected objects.
         * @example
         *
         * actions = { "delete" : (selected_objects)=>{ } }

         *@return {object} An actions object
         */

      get_actions() {
        if (this.actions) {
          return this.actions;
        }

        return {};
      }
      /**
       *   Gets the list/array of properties of the objects in the queryset that are clickable
       *    when displayed on the list display page. It can be overridden by the member variable
       *   `list_display_links`. A property is any string that should exist as a property in the objects within
       *   a queryset and works with lodash's `_.at` function.
       *
       *   The properties "name","address.street" and "emails[0]" are all acceptable by `_.at` in the example below
       *   @example
       *   let object={ name : "any name",{ address : { street : "any"}},emails: ["any@any.com"]}
       *
       *
       *
       * @return {string[]} A list of properties of the object to be displayed
       */

      get_list_display_links() {
        if (this.list_display_links) {
          return this.list_display_links;
        }
        return [];
      }
      /**
       *   Gets the list/array of properties/field names of the objects in the queryset to be
       *   displayed on the list display page. It can be overridden by the member variable
       *   list_display. A property is any string that should exist in the objects within
       *   a queryset and works with lodash's _.at function. See more at
       *   https://lodash.com/docs/#at
       *
       *   @example
       *   let object={ name : "any name",{ address : { street : "any"}},emails: ["any@any.com"]}
       *
       *   The properties "name","address.street" and "emails[0]" are all acceptable
       *
       * @return {string[]} A list of properties of the object to be displayed
       */

      get_list_display() {
        if (this.list_display) {
          return this.list_display;
        }
        return [];
      }
      /**
       * Returns an ordered queryset. The method checks to see if sorting is active and sorts
       * the current queryset based on the sort order.
       * @private
       * @return {object[]} An ordered queryset
       */

      _get_ordered_queryset() {
        if (this.sort_fields.length > 0) {
          return this.sort_by(this.sort_fields, this.state.queryset);
        }

        return this.state.queryset ? this.state.queryset : [];
      }
      /**
       * Implements search. This method should be overridden to implement a custom search
       *
       *@param {string} term - the search term
       *@param {object[]} queryset - the current queryset
       *@return {object[]} the queryset as a result of the search
       */

      search(term, queryset) {
        return queryset;
      }
      /**
       * Grants permission to delete object. This method is not implemented and can be handled via
       *  implementing actions.
       *
       *@param {object} object -
       *@return {boolean} Returns true is the object has been deleted or false otherwise
       */

      has_delete_permission(object) {
        return true;
      }
      /**
       * Grants permission to add an object. This method controls rendering of the Add
       * button
       *
       *
       *@return {boolean} Returns true is the object can be added or false otherwise
       */

      has_add_permission() {
        return true;
      }
      /**
       * Grants permission to change an object. It disables all links to the add/change page when
       * enabled
       *
       *@return {boolean} Returns true is the object can be changed or false otherwise
       */

      has_change_permission(object = null) {
        return true;
      }
      /**
       * Grants permission to the this admin interface.
       *
       *@return {boolean} Returns true if access is allowed false otherwise
       */

      has_module_permission() {
        return true;
      }
      /*
        _order_state(field)
        {

    	let index=_.findIndex(this._sort_fields,item=> item.hasOwnProperty(field));

    	if(index<0)
    	{
    	    return 'sort-not-active';

    	}
    	if(this._sort_fields[index][field]=='desc')
    	{

    	    return 'sort-reverse-active';
    	}
    	return 'sort-active';
    	}*/

      /**
         * This method adds up/down arrows to field headers on the list display page table
         *
         * @private
         * @param {string} field -  the field/property name

         */

      _order_state_arrow(field) {
        let index = _.findIndex(this.sort_fields, item =>
          item.hasOwnProperty(field)
        );

        if (index < 0) {
          return null;
        }
        if (this.sort_fields[index][field] == "desc") {
          return <span>&#8681;</span>;
        }
        return <span> &#8679;</span>;
      }
      /**
       *  This method  should be overriden and called after saving an object in the add/change view.       *  This method is not called at all here but provides hints on what to do after saving
       *  an object. Change the state display_type to "list", object to "null" and refresh the quer         *  yset.
       */

      response_add() {
        this._display_will_change();
        this.setState(
          {
            display_type: display_type.list,
            object: null,
            queryset: this.get_queryset(
              this.state.page_number,
              this.get_list_per_page(),
              this.state.queryset
            )
          },
          this._display_changed
        );

        return true;
      }
      /**
       *  This method should be overriden and called after saving an object in the add/change view.         *  This method is not called at all here but provides hints on what to do after saving
       *  an object. Change the state display_type to "list", object to "null" and refresh the quer         *  yset.
       */

      response_change() {
        this._display_will_change();
        this.setState(
          {
            display_type: display_type.list,
            object: null,
            queryset: this.get_queryset(
              this.state.page_number,
              this.get_list_per_page(),
              this.state.queryset
            )
          },
          this._display_changed
        );

        return true;
      }
      /**
         * A private method to wrap a table entry in an <a></a> tag in the display page.
         * The method checks if permission is given to display links using the has_change_permission method
         * @private
         * @param {object} object - the current object to be displayed as a table
         * entry in the display page
         * @param {string} label - the name of the field

         */

      _create_object_link(object, label) {
        if (this.has_change_permission(object)) {
          return (
            <a onClick={this._object_link_clicked(object)} href="#">
              {" "}
              {label}{" "}
            </a>
          );
        } else {
          return <span> {label} </span>;
        }
      }

      set_queryset(queryset) {
        this.setState({ queryset: queryset });
      }

      set_total(total) {
        this.setState({ total: total });
      }

      _get_prop_label(label) {
        let labels = label.split(".");
        return labels[labels.length - 1];
      }
      _get_table_header() {
        return this.get_list_display().map(item => {
          return (
            <th key={item} onClick={this._sort_handler(item)}>
              {" "}
              {this.get_header_transforms()[item]
                ? this.get_header_transforms()[item](item)
                : _.startCase(this._get_prop_label(item))}
              {this._order_state_arrow(item)}{" "}
            </th>
          );
        });
      }
      _display_changed() {
        this.display_changed(this.state.display_type, this.state.object);
      }
      /**
         * An event listener that listens to state changes in the display type i.e.
         * from list to add/change. It is fired after state changes are made.
         *
         * @param {string} display_type - the current display type of the component either "list" or "change"
         * @param {object} object - the current object or null

         */

      display_changed(display_type, object) {}
      _display_will_change() {
        this.display_will_change(this.state.display_type, this.state.object);
      }
      /**
         * An event listener that listens to state changes in the display type i.e.
         * from list to add/change . It is fired before state changes are made.
         *
         * @param {string} display_type - the current display type of the component either "list" or "change"
         * @param {object} object - the current object or null

         */

      display_will_change(display_type, object) {}
      _object_link_clicked(object) {
        return event => {
          this._display_will_change();
          this.setState(
            { display_type: display_type.change, object: object, loading: false },
            this._display_changed()
          );
          event.preventDefault();
        };
      }

      _display_field(object, item) {
        let label = _.at(object, item)["0"];

        if (_.has(this.get_field_transforms(), item)) {
          return this.get_field_transforms()[item](label, object);
        }

        if (_.at(this.get_extra_fields(), item)) {
          if (this.get_extra_fields()[item]) {
            label = this.get_extra_fields()[item](object, item);
          }
        }

        return label;
      }

      set_filter_options(filter_name, options) {
        let t = {};
        t[filter_name] = options.map(option => {
          option._filter_ = filter_name;
          return option;
        });
        this.setState({ filter_options: _.assign(this.state.filter_options, t) });
      }

      get_filter_values() {
        return this.state.filter_values;
      }
      get_filters() {
        /*
    	return {

    	    "by_id" : { "options" : [
    			    { value: 'one', label: 'One' },
    	    { value: 'two', label: 'Two' },

    	    ],
    	     "filter_function" : (option,queryset)=>
    	     {

    		 return queryset;
    	     }
               }

    	}*/
      }
      _handle_filter_change(values) {
        if (!(values instanceof Array) && values != null) {
          values = [values];
        }

        this.setState({ filter_values: values || [] });

        if (values == null || values.length <= 0) {
          this.set_queryset(
            this.get_queryset(
              this.state.page_number,
              this.get_list_per_page(),
              this.state.queryset
            )
          );
          return;
        }

        let filters = this.get_filters();
        for (let value of values) {
          let queryset = filters[value._filter_].filter_function(
            value,
            this.state.queryset
          );
          this.setState({ queryset: queryset });
        }
      }
      render_filters() {
        let options = [];
        let filters = this.get_filters();
        for (let filter of _.keys(filters)) {
          options.push({
            label: _.startCase(filter),
            value: filter,
            disabled: true,
            filter: null
          });

          if (filters[filter].options.length > 0) {
            for (let option of filters[filter].options) {
              option._filter_ = filter;
              options.push(option);
            }
          }
          if (this.state.filter_options[filter]) {
            for (let option of this.state.filter_options[filter]) {
              option._filter_ = filter;
              options.push(option);
            }
          }
        }

        return (
          <div style={{ marginLeft: "10px", width: "100%" }}>
            <Select
              name="filter-form"
              multi={true}
              onChange={this._handle_filter_change.bind(this)}
              closeOnSelect={true}
              value={this.state.filter_values}
              removeSelected={true}
              placeholder={"Select a filter"}
              options={options}
            />
          </div>
        );
      }
      sort_by(sort_fields) {
        return this.state.queryset ? this.state.queryset : [];
      }

      _sort_handler(field) {
        return event => {
          let index = _.findIndex(this.sort_fields, item =>
            item.hasOwnProperty(field)
          );
          if (index >= 0) {
            if (this.sort_fields[index][field] == "asc") {
              this.sort_fields[index][field] = "desc";
            } else {
              this.sort_fields = _.filter(
                this.sort_fields,
                item => !item.hasOwnProperty(field)
              );
            }
          } else {
            let temp = {};
            temp[field] = "asc";
            this.sort_fields.push(temp);
          }

          this.sort_by(this.sort_fields);
          this.forceUpdate();
        };
      }
      /**
       * This method is an event handler that listens to when all objects of the queryset
       * displayed within a single display page are selected
       */

      _select_all(event) {
        if (this._all_selected) {
          this.setState({ selected_objects: new Set([], this.is_object_equal) });
        } else {
          this.setState({
            selected_objects: new Set(this.state.queryset, this.is_object_equal)
          });
        }
        this._all_selected = !this._all_selected;
      }
      /**
       * This method is an event handler that listens when a single objects of the queryset
       * displayed is selected
       */

      _select_one(object) {
        return event => {
          if (this.state.selected_objects.contains(object)) {
            this.state.selected_objects.remove(object);

            this.setState({ selected_objects: this.state.selected_objects });
          } else {
            this.state.selected_objects.add(object);
            this.setState({ selected_objects: this.state.selected_objects });
          }
        };
      }

      /**
       * Generate the table body for the list display page
       *
       * @return  An array of table rows <tr/>
       */

      _get_table_body() {
        return this._get_ordered_queryset().map((object, i) => {
          return (
            <tr key={"row-" + i}>
              <td>
                {" "}
                <input
                  type="checkbox"
                  id={i + "_checkbox"}
                  onChange={this._select_one(object)}
                  checked={this.state.selected_objects.contains(object)}
                />{" "}
                <label htmlFor={i + "_checkbox"}>&nbsp;</label>{" "}
              </td>
              {this.get_list_display().map(item => {
                return (
                  <td key={item}>
                    {" "}
                    {this.get_list_display_links().find(a => {
                      return item == a;
                    })
                      ? this._create_object_link(
                          object,
                          this._display_field(object, item)
                        )
                      : this._display_field(object, item)}{" "}
                  </td>
                );
              })}
            </tr>
          );
        });
      }
      /**
       * Changes the state property "loading" to true. The state property can be used to show a
       * progress indicator.
       */

      show_progress() {
        this.setState({ loading: true });
      }
      /**
       * Changes the state property "loading" to false. This method  can be used to hide a
       * progress indicator by inspecting the "loading" property of the state object.
       */

      hide_progress() {
        this.setState({ loading: false });
      }
      /**
       * The default progress indicator. Can be overriden.
       * @return A progress indicator component
       */

      render_progress(loading) {
        if (loading) {
          return (
            <div className="fetch-progress">
              <progress />
            </div>
          );
        }
      }
      /**
       * An event listener that listens to the search event. This method calls search method which
       * implements a custom search. The method uses the "live_search" property to implement live
       * search or not.
       * @private
       * @param {object} event The search onChange event
       */

      _handle_search(event) {
        let term = event.target.value;

        if (term) {
          let key = event.which || event.keyCode;

          if (this.get_live_search() || key === 13) {
            let queryset = this.search(term, this.state.queryset);
            this.setState({ queryset: queryset, total: queryset.length });
          }
        } else {
          this.setState({
            queryset: this.get_queryset(
              this.state.page_number,
              this.get_list_per_page(),
              this.state.queryset
            ),
            total: this.get_queryset(
              this.state.page_number,
              this.list_per_page,
              this.state.queryset
            ).length
          });
        }
      }
      /**
       * Renders the search component
       *
       * @return A search input field
       */

      render_search_field() {
        return (
          <div>
            <input
              name="search"
              type="text"
              className="ra-search-field"
              placeholder="Search"
              onChange={this._handle_search}
              onKeyUp={this._handle_search}
            />
          </div>
        );
      }
      /**
       * Renders the add object button. Checks to see if permission is given by has_add_permission
       *
       * @return An add button component
       */

      render_add_button() {
        if (this.has_add_permission()) {
          return (
            <button
              className={"ra-add-button"}
              onClick={this._object_link_clicked(null)}
            >
              {" "}
              Add {_.startCase(this.name)}
            </button>
          );
        }
      }
      /**
       * Renders the table in the display page. This calls _get_table_header and _get_table_body
       *
       * @return An a table displaying the state queryset set objects
       */

      render_table() {
        return (
          <table className="table">
            <thead>
              <tr key="header">
                <th>
                  <input
                    type="checkbox"
                    id="all_boxes"
                    onChange={this._select_all}
                  />{" "}
                  <label htmlFor="all_boxes">&nbsp;</label>
                </th>
                {this._get_table_header()}
              </tr>
            </thead>
            <tbody>{this._get_table_body()}</tbody>
          </table>
        );
      }
      /**
       * An event listener that listens to actions selected.
       *
       *@param {object} event -  the DOM on-change event
       */

      action_selected(event) {
        let action = event.target.value;

        console.log(this.state.selected_objects.getItems());
        this.get_actions()[action](this.state.selected_objects.getItems());
        this.get_queryset(this.state.page_number, this.get_list_per_page());
      }
      /**
       * An event listener that listens when a page is  selected.
       *
       *@param {number} page -  the page number selected
       */

      selectPage(page) {
        return event => {
          this.setState({ page_number: page.page }, () => {
            this.setState({
              queryset: this.get_queryset(
                this.state.page_number,
                this.get_list_per_page(),
                this.state.queryset
              )
            });
          });
          event.preventDefault();
        };
      }
      /**
       * An event listener that listens when the next page is  selected.
       *
       */

      nextPage() {
        this.setState({
          page_number: Math.min(this.state.page_number + 1, this.state.total)
        });
      }
      /**
       * An event listener that listens when the previous page is  selected.
       *
       */

      prevPage() {
        this.setState({ page_number: Math.max(this.state.page_number - 1, 1) });
      }
      /**
       * Renders the pagination UI
       *
       * @return A component that renders the pagination controls
       */

      render_pagination() {
        let pages = [];
        if (this.state.total) {
          let numpages = Math.ceil(this.state.total / this.get_list_per_page());
          let pages_in_pagination =
            numpages < this.pages_in_pagination
              ? numpages
              : this.pages_in_pagination;

          if (this.state.page_number == 1) {
            for (let i = 0; i < pages_in_pagination; i++) {
              pages.push(i + 1);
            }
          } else if (this.state.page_number == numpages) {
            for (let i = 0; i < pages_in_pagination; i++) {
              pages.push(numpages - i);
            }
          } else {
            for (let i = 0; i < pages_in_pagination; i++) {
              pages.push(this.state.page_number + i);
            }
          }
          /*
    		for(var i=0;i<numpages;i++)
    	    {
    		pages.push(i+1);

    	    }*/
        }

        return (
          <div className="pull-right">
            <span className="summary">
              {this.get_list_per_page() * (this.state.page_number - 1) + 1}-
              {Math.min(
                this.get_list_per_page() * (this.state.page_number - 1) +
                  this.get_list_per_page(),
                this.state.total
              )}{" "}
              of {this.state.total}{" "}
            </span>

            <nav aria-label="Page navigation">
              <ul className="pagination">
                <li key={"left"} className="page-item">
                  <a
                    href="#"
                    aria-label="Previous"
                    onClick={this.prevPage.bind(this)}
                    className="page-link"
                  >
                    <span aria-hidden="true">&laquo;</span>
                  </a>
                </li>

                {pages.map(page => {
                  return (
                    <li key={page} className="page-item">
                      <a
                        href="#"
                        className="page-link"
                        onClick={this.selectPage({ page })}
                      >
                        {page}
                      </a>
                    </li>
                  );
                })}
                <li className="page-item">
                  <a
                    href="#"
                    key={"right"}
                    onClick={this.nextPage.bind(this)}
                    aria-label="Next"
                    className="page-link"
                  >
                    <span aria-hidden="true">&raquo;</span>
                  </a>
                </li>
              </ul>
            </nav>
          </div>
        );
      }
      /**
       * Renders the actions select component
       *
       * @return A component that renders a select input for all actions in the list display page
       */

      render_actions() {
        return (
          <select
            className="ra-action-button"
            onChange={this.action_selected.bind(this)}
            defaultValue=""
          >
            <option key="key" value="" disabled={true}>
              Choose an action
            </option>
            {_.keys(this.get_actions()).map(action => {
              return (
                <option key={action} value={action}>
                  {" "}
                  {_.startCase(action)}
                </option>
              );
            })}
          </select>
        );
      }
      /**
       * Renders the back button component in the add/change view
       * @ignore
       * @return A component that renders a back button
       */

      render_back_button() {
        return (
          <div>
            <button
              className={"ra-back-button"}
              onClick={() => {
                this.setState({ display_type: display_type.list, object: null });
                this.get_queryset(
                  this.state.page_number,
                  this.get_list_per_page(),
                  this.state.queryset
                );
              }}
            >
              {" "}
              Back{" "}
            </button>
          </div>
        );
      }
      /**
       * Renders the admin interface component
       *
       * @return A component that renders the admin interface
       */
      render_list_view() {
        return (
          <div>
            {this.render_add_button()}
            {this.render_below_add_button()}
            {this.render_search_field()}
            {this.render_below_search_field()}
            {this.render_actions()}
            {this.render_below_actions()}
            {this.render_filters()}
            {this.render_below_filters()}

            {this.render_table()}
            {this.render_below_table()}
            {this.render_progress(this.state.loading)}
            {this.render_below_progress()}

            {this.render_pagination()}
          </div>
        );
      }
      render_below_add_button() {}
      render_below_search_field() {}

      render_below_actions() {}

      render_below_filters() {}

      render_below_table() {}

      render_below_progress() {}

      render_change_view() {
        return (
          <div>
            {this.render_progress(this.state.loading)}
            {this.render_back_button()}
            {this.get_form(this.state.object)}
          </div>
        );
      }
      render_above_change_view() {}
      render_below_change_view() {}
      render_above_list_view() {}
      render_below_list_view() {}
      render_list_page() {
        return (
          <div>
            {this.render_above_list_view()}
            {this.render_list_view()}
            {this.render_below_list_view()}
            {this.render_change_modal()}
          </div>
        );
      }

      render_change_modal() {
        return (
          <Modal
            isOpen={this.state.display_type != display_type.list}
            contentLabel={this.name || ""}
            parentSelector={() => document.querySelector("#react-crud-admin")}
            styles={customStyles}
          >
            {this.render_above_change_view()}
            {this.render_change_view()}
            {this.render_below_change_view()}
          </Modal>
        );
      }
      render_permission_denied() {
        return (
          <div>
            {" "}
            <h1> Permission Denied </h1>
          </div>
        );
      }
      render() {
        if (!this.has_module_permission()) {
          return this.render_permission_denied();
        }

        return <div id="react-crud-admin">{this.render_list_page()}</div>;
      }
      show_list_view() {
        this._display_will_change();
        this.setState({ display_type: display_type.list }, this._display_changed);
      }
    }

    export default Admin;