<commit_before>/* @flow weak */

/**
 * OfflineMobile Android Stocktakes Page
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import React from 'react';
import {
  StyleSheet,
  View,
  TouchableOpacity,
} from 'react-native';

import {
  Cell,
  DataTable,
  Header,
  HeaderCell,
  Row,
} from '../widgets/DataTable';

import Icon from 'react-native-vector-icons/Ionicons';
import { generateUUID } from '../database';
import { ListView } from 'realm/react-native';
import { Button, DeleteModal, ToggleBar } from '../widgets';
import globalStyles, { SUSSOL_ORANGE } from '../globalStyles';

/**
* Renders the page for displaying Stocktakes.
* @prop   {Realm}               database      App wide database.
* @prop   {func}                navigateTo    callBack for navigation stack.
* @state  {ListView.DataSource} dataSource    DataTable input, used to update rows being rendered.
* @state  {Realm.Results}       Stocktakes    Filtered to have only customer_stocktake.
* @state  {string}              sortBy        Locked to createdDate created date column.
* @state  {boolean}             isAscending   Direction sortBy should sort
*                                             (ascending/descending:true/false).
* @state  {boolean}             toggleCurrent Boolean control of toggle bar options.
* @state  {array}             deleteSelection Stores id of the stocktakes selected for deletion.
*/
export class StocktakesPage extends React.Component {
  constructor(props) {
    super(props);
    const dataSource = new ListView.DataSource({
      rowHasChanged: (row1, row2) => row1 !== row2,
    });
    this.state = {
      dataSource: dataSource,
      stocktakes: props.database.objects('Stocktake'),
      sortBy: 'createdDate',
      isAscending: false,
      toggleCurrent: true,
      deleteSelection: [],
    };
    this.componentWillMount = this.componentWillMount.bind(this);
    this.onDeleteConfirm = this.onDeleteConfirm.bind(this);
    this.onDeleteCancel = this.onDeleteCancel.bind(this);
    this.onDeleteButtonPress = this.onDeleteButtonPress.bind(this);
    this.onColumnSort = this.onColumnSort.bind(this);
    this.renderHeader = this.renderHeader.bind(this);
    this.renderRow = this.renderRow.bind(this);
    this.refreshData = this.refreshData.bind(this);
    this.onNewStockTake = this.onNewStockTake.bind(this);
  }

  componentWillMount() {
    this.refreshData();
  }

  onColumnSort() {
    this.setState({
      isAscending: !this.state.isAscending,
    });
    this.refreshData();
  }

  onNewStockTake() {
    this.props.database.write(() => {
      this.props.database.create('Stocktake', {
        id: generateUUID(),
        name: 'best stocktake',
        createdDate: new Date(),
        status: 'new',
        comment: 'Testing StocktakesPage',
        serialNumber: '1337',
        lines: [],
      });
    });
    this.props.navigateTo('stocktakeManager', 'New StockTake');
  }

  /**
   * Takes an array of stocktakes and deletes them from database
   */
  onDeleteConfirm() {
    const { stocktakes, deleteSelection } = this.state;
    const { database } = this.props;
    database.write(() => {
      for (let i = 0; i < deleteSelection.length; i++) {
        const stocktake = stocktakes.filtered('id == $0', deleteSelection[i])[0];
        database.delete('Stocktake', stocktake);
      }
    });
    this.setState({ deleteSelection: [] });
    this.refreshData();
  }

  onDeleteCancel() {
    this.setState({ deleteSelection: [] });
    this.refreshData();
  }

  onDeleteButtonPress(stocktake) {
    const { deleteSelection } = this.state;
    if (deleteSelection.indexOf(stocktake.id) >= 0) {
      deleteSelection.splice(deleteSelection.indexOf(stocktake.id), 1);
    } else {
      deleteSelection.push(stocktake.id);
    }
    this.refreshData();
  }

  onToggle(isCurrent) {
    this.setState({
      toggleCurrent: isCurrent,
    });
    this.refreshData();
  }

  /**
   * Updates data within dataSource in state according to the state of sortBy and
   * isAscending. This sort by on this page is always 'createdDate'.
   */
  refreshData() {
    const { stocktakes, sortBy, toggleCurrent, dataSource, isAscending } = this.state;
    const toggleFilter = toggleCurrent ? 'status == "new"' : 'status != "new"';
    const data = stocktakes
                  .filtered(toggleFilter)
                  .sorted(sortBy, !isAscending); // 2nd arg: reverse sort order if true
    this.setState({ dataSource: dataSource.cloneWithRows(data) });
  }

  renderHeader() {
    return (
      <Header style={globalStyles.dataTableHeader}>
        <HeaderCell
          style={globalStyles.dataTableHeaderCell}
          textStyle={globalStyles.dataTableText}
          width={COLUMN_WIDTHS[0]}
          text={'DESCRIPTION'}
        />
        <HeaderCell
          style={globalStyles.dataTableHeaderCell}
          textStyle={globalStyles.dataTableText}
          width={COLUMN_WIDTHS[1]}
          onPress={() => this.onColumnSort()}
          isAscending={this.state.isAscending}
          isSelected={this.state.sortBy === 'createdDate'}
          text={'CREATED DATE'}
        />
        <HeaderCell
          style={globalStyles.dataTableHeaderCell}
          textStyle={globalStyles.dataTableText}
          width={COLUMN_WIDTHS[2]}
          text={'STATUS'}
        />
        <HeaderCell
          style={[
            globalStyles.dataTableHeaderCell,
            localStyles.rightMostCell,
            localStyles.deleteCell,
          ]}
          textStyle={globalStyles.dataTableText}
          width={COLUMN_WIDTHS[3]}
          text={'DELETE'}
        />
      </Header>
    );
  }

  renderRow(stocktake) {
    const deleteColor = this.state.deleteSelection.indexOf(stocktake.id) >= 0 ?
    SUSSOL_ORANGE : 'grey';
    return (
      <Row
        style={globalStyles.dataTableRow}
        onPress={() => this.props.navigateTo('stocktakeManager', 'Create Stocktake')}
      >
        <Cell
          style={globalStyles.dataTableCell}
          textStyle={globalStyles.dataTableText}
          width={COLUMN_WIDTHS[0]}
        >
          {stocktake.comment}
        </Cell>
        <Cell
          style={globalStyles.dataTableCell}
          textStyle={globalStyles.dataTableText}
          width={COLUMN_WIDTHS[1]}
        >
          {stocktake.createdDate.toLocaleString()}
        </Cell>
        <Cell
          style={globalStyles.dataTableCell}
          textStyle={globalStyles.dataTableText}
          width={COLUMN_WIDTHS[2]}
        >
          {stocktake.status}
        </Cell>
        <TouchableOpacity
          style={{ flex: COLUMN_WIDTHS[3] }}
          onPress={() => this.onDeleteButtonPress(stocktake)}
        >
          <Cell
            style={[globalStyles.dataTableCell, localStyles.rightMostCell, localStyles.deleteCell]}
            textStyle={globalStyles.dataTableText}
            width={COLUMN_WIDTHS[3]}
          >
            <Icon name="md-remove-circle" size={15} color={deleteColor} />
          </Cell>
        </TouchableOpacity>
      </Row>
    );
  }

  render() {
    const { toggleCurrent } = this.state;
    return (
      <View style={globalStyles.pageContentContainer}>
        <View style={globalStyles.container}>
          <View style={globalStyles.pageTopSectionContainer}>
            <ToggleBar
              style={globalStyles.toggleBar}
              textStyle={globalStyles.toggleText}
              textSelectedStyle={globalStyles.toggleTextSelected}
              optionStyle={globalStyles.toggleOption}
              optionSelectedStyle={globalStyles.toggleOptionSelected}
              options={[
                { text: 'Current', onPress: () => this.onToggle(true), selected: toggleCurrent },
                { text: 'Past', onPress: () => this.onToggle(false), selected: !toggleCurrent },
              ]}
            />
            <View style={localStyles.buttonViewTop}>
              <Button
                style={globalStyles.button}
                textStyle={globalStyles.buttonText}
                text="New StockTake"
                onPress={this.onNewStockTake}
              />
            </View>
          </View>
          <DataTable
            style={globalStyles.dataTable}
            listViewStyle={localStyles.listView}
            dataSource={this.state.dataSource}
            renderRow={this.renderRow}
            renderHeader={this.renderHeader}
          />
          <DeleteModal
            isOpen={this.state.deleteSelection.length > 0}
            questionText="Are you sure you want to delete these stocktakes?"
            onCancel={() => this.onDeleteCancel()}
            onConfirm={() => this.onDeleteConfirm()}
          />
        </View>
      </View>
    );
  }
}

StocktakesPage.propTypes = {
  database: React.PropTypes.object,
  navigateTo: React.PropTypes.func.isRequired,
};
const COLUMN_WIDTHS = [6, 2, 2, 1];
const localStyles = StyleSheet.create({
  listView: {
    flex: 1,
  },
  buttonViewTop: {
    justifyContent: 'flex-end',
    alignItems: 'center',
  },
  deleteCell: {
    alignItems: 'center',
  },
  rightMostCell: {
    borderRightWidth: 0,
  },
  dataTable: {
    flex: 1,
  },
});
<commit_msg>callBack to callback<commit_after>/* @flow weak */

/**
 * OfflineMobile Android Stocktakes Page
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import React from 'react';
import {
  StyleSheet,
  View,
  TouchableOpacity,
} from 'react-native';

import {
  Cell,
  DataTable,
  Header,
  HeaderCell,
  Row,
} from '../widgets/DataTable';

import Icon from 'react-native-vector-icons/Ionicons';
import { generateUUID } from '../database';
import { ListView } from 'realm/react-native';
import { Button, DeleteModal, ToggleBar } from '../widgets';
import globalStyles, { SUSSOL_ORANGE } from '../globalStyles';

/**
* Renders the page for displaying Stocktakes.
* @prop   {Realm}               database      App wide database.
* @prop   {func}                navigateTo    callback for navigation stack.
* @state  {ListView.DataSource} dataSource    DataTable input, used to update rows being rendered.
* @state  {Realm.Results}       Stocktakes    Filtered to have only customer_stocktake.
* @state  {string}              sortBy        Locked to createdDate created date column.
* @state  {boolean}             isAscending   Direction sortBy should sort
*                                             (ascending/descending:true/false).
* @state  {boolean}             toggleCurrent Boolean control of toggle bar options.
* @state  {array}             deleteSelection Stores id of the stocktakes selected for deletion.
*/
export class StocktakesPage extends React.Component {
  constructor(props) {
    super(props);
    const dataSource = new ListView.DataSource({
      rowHasChanged: (row1, row2) => row1 !== row2,
    });
    this.state = {
      dataSource: dataSource,
      stocktakes: props.database.objects('Stocktake'),
      sortBy: 'createdDate',
      isAscending: false,
      toggleCurrent: true,
      deleteSelection: [],
    };
    this.componentWillMount = this.componentWillMount.bind(this);
    this.onDeleteConfirm = this.onDeleteConfirm.bind(this);
    this.onDeleteCancel = this.onDeleteCancel.bind(this);
    this.onDeleteButtonPress = this.onDeleteButtonPress.bind(this);
    this.onColumnSort = this.onColumnSort.bind(this);
    this.renderHeader = this.renderHeader.bind(this);
    this.renderRow = this.renderRow.bind(this);
    this.refreshData = this.refreshData.bind(this);
    this.onNewStockTake = this.onNewStockTake.bind(this);
  }

  componentWillMount() {
    this.refreshData();
  }

  onColumnSort() {
    this.setState({
      isAscending: !this.state.isAscending,
    });
    this.refreshData();
  }

  onNewStockTake() {
    this.props.database.write(() => {
      this.props.database.create('Stocktake', {
        id: generateUUID(),
        name: 'best stocktake',
        createdDate: new Date(),
        status: 'new',
        comment: 'Testing StocktakesPage',
        serialNumber: '1337',
        lines: [],
      });
    });
    this.props.navigateTo('stocktakeManager', 'New StockTake');
  }

  /**
   * Takes an array of stocktakes and deletes them from database
   */
  onDeleteConfirm() {
    const { stocktakes, deleteSelection } = this.state;
    const { database } = this.props;
    database.write(() => {
      for (let i = 0; i < deleteSelection.length; i++) {
        const stocktake = stocktakes.filtered('id == $0', deleteSelection[i])[0];
        database.delete('Stocktake', stocktake);
      }
    });
    this.setState({ deleteSelection: [] });
    this.refreshData();
  }

  onDeleteCancel() {
    this.setState({ deleteSelection: [] });
    this.refreshData();
  }

  onDeleteButtonPress(stocktake) {
    const { deleteSelection } = this.state;
    if (deleteSelection.indexOf(stocktake.id) >= 0) {
      deleteSelection.splice(deleteSelection.indexOf(stocktake.id), 1);
    } else {
      deleteSelection.push(stocktake.id);
    }
    this.refreshData();
  }

  onToggle(isCurrent) {
    this.setState({
      toggleCurrent: isCurrent,
    });
    this.refreshData();
  }

  /**
   * Updates data within dataSource in state according to the state of sortBy and
   * isAscending. This sort by on this page is always 'createdDate'.
   */
  refreshData() {
    const { stocktakes, sortBy, toggleCurrent, dataSource, isAscending } = this.state;
    const toggleFilter = toggleCurrent ? 'status == "new"' : 'status != "new"';
    const data = stocktakes
                  .filtered(toggleFilter)
                  .sorted(sortBy, !isAscending); // 2nd arg: reverse sort order if true
    this.setState({ dataSource: dataSource.cloneWithRows(data) });
  }

  renderHeader() {
    return (
      <Header style={globalStyles.dataTableHeader}>
        <HeaderCell
          style={globalStyles.dataTableHeaderCell}
          textStyle={globalStyles.dataTableText}
          width={COLUMN_WIDTHS[0]}
          text={'DESCRIPTION'}
        />
        <HeaderCell
          style={globalStyles.dataTableHeaderCell}
          textStyle={globalStyles.dataTableText}
          width={COLUMN_WIDTHS[1]}
          onPress={() => this.onColumnSort()}
          isAscending={this.state.isAscending}
          isSelected={this.state.sortBy === 'createdDate'}
          text={'CREATED DATE'}
        />
        <HeaderCell
          style={globalStyles.dataTableHeaderCell}
          textStyle={globalStyles.dataTableText}
          width={COLUMN_WIDTHS[2]}
          text={'STATUS'}
        />
        <HeaderCell
          style={[
            globalStyles.dataTableHeaderCell,
            localStyles.rightMostCell,
            localStyles.deleteCell,
          ]}
          textStyle={globalStyles.dataTableText}
          width={COLUMN_WIDTHS[3]}
          text={'DELETE'}
        />
      </Header>
    );
  }

  renderRow(stocktake) {
    const deleteColor = this.state.deleteSelection.indexOf(stocktake.id) >= 0 ?
    SUSSOL_ORANGE : 'grey';
    return (
      <Row
        style={globalStyles.dataTableRow}
        onPress={() => this.props.navigateTo('stocktakeManager', 'Create Stocktake')}
      >
        <Cell
          style={globalStyles.dataTableCell}
          textStyle={globalStyles.dataTableText}
          width={COLUMN_WIDTHS[0]}
        >
          {stocktake.comment}
        </Cell>
        <Cell
          style={globalStyles.dataTableCell}
          textStyle={globalStyles.dataTableText}
          width={COLUMN_WIDTHS[1]}
        >
          {stocktake.createdDate.toLocaleString()}
        </Cell>
        <Cell
          style={globalStyles.dataTableCell}
          textStyle={globalStyles.dataTableText}
          width={COLUMN_WIDTHS[2]}
        >
          {stocktake.status}
        </Cell>
        <TouchableOpacity
          style={{ flex: COLUMN_WIDTHS[3] }}
          onPress={() => this.onDeleteButtonPress(stocktake)}
        >
          <Cell
            style={[globalStyles.dataTableCell, localStyles.rightMostCell, localStyles.deleteCell]}
            textStyle={globalStyles.dataTableText}
            width={COLUMN_WIDTHS[3]}
          >
            <Icon name="md-remove-circle" size={15} color={deleteColor} />
          </Cell>
        </TouchableOpacity>
      </Row>
    );
  }

  render() {
    const { toggleCurrent } = this.state;
    return (
      <View style={globalStyles.pageContentContainer}>
        <View style={globalStyles.container}>
          <View style={globalStyles.pageTopSectionContainer}>
            <ToggleBar
              style={globalStyles.toggleBar}
              textStyle={globalStyles.toggleText}
              textSelectedStyle={globalStyles.toggleTextSelected}
              optionStyle={globalStyles.toggleOption}
              optionSelectedStyle={globalStyles.toggleOptionSelected}
              options={[
                { text: 'Current', onPress: () => this.onToggle(true), selected: toggleCurrent },
                { text: 'Past', onPress: () => this.onToggle(false), selected: !toggleCurrent },
              ]}
            />
            <View style={localStyles.buttonViewTop}>
              <Button
                style={globalStyles.button}
                textStyle={globalStyles.buttonText}
                text="New StockTake"
                onPress={this.onNewStockTake}
              />
            </View>
          </View>
          <DataTable
            style={globalStyles.dataTable}
            listViewStyle={localStyles.listView}
            dataSource={this.state.dataSource}
            renderRow={this.renderRow}
            renderHeader={this.renderHeader}
          />
          <DeleteModal
            isOpen={this.state.deleteSelection.length > 0}
            questionText="Are you sure you want to delete these stocktakes?"
            onCancel={() => this.onDeleteCancel()}
            onConfirm={() => this.onDeleteConfirm()}
          />
        </View>
      </View>
    );
  }
}

StocktakesPage.propTypes = {
  database: React.PropTypes.object,
  navigateTo: React.PropTypes.func.isRequired,
};
const COLUMN_WIDTHS = [6, 2, 2, 1];
const localStyles = StyleSheet.create({
  listView: {
    flex: 1,
  },
  buttonViewTop: {
    justifyContent: 'flex-end',
    alignItems: 'center',
  },
  deleteCell: {
    alignItems: 'center',
  },
  rightMostCell: {
    borderRightWidth: 0,
  },
  dataTable: {
    flex: 1,
  },
});
<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';

import { GenericPage } from './GenericPage';

import { buttonStrings, modalStrings, navStrings, tableStrings } from '../localization';
import { formatStatus, getAllPrograms } from '../utilities';
import { PageButton, BottomConfirmModal, ToggleBar } from '../widgets';
import { ByProgramModal } from '../widgets/modals/index';

import globalStyles from '../globalStyles';
import { createRecord } from '../database/utilities/index';

const DATA_TYPES_SYNCHRONISED = ['Stocktake'];

/**
 * Renders the page for displaying stocktakes.
 *
 * @prop  {Realm}         database    App wide database.
 * @prop  {func}          navigateTo  CallBack for navigation stack.
 * @state {Realm.Results} stocktakes  Result object containing all items.
 */

export class StocktakesPage extends React.Component {
  constructor(props) {
    super(props);
    this.dataFilters = {
      searchTerm: '',
      sortBy: 'createdDate',
      isAscending: false,
    };
    this.state = {
      showCurrent: true,
      selection: [],
      usesPrograms: false,
      byProgramModalOpen: false,
    };
    this.stocktakes = props.database.objects('Stocktake');
  }

  componentDidMount() {
    const { settings, database } = this.props;
    this.setState({ usesPrograms: !!getAllPrograms(settings, database) });
  }

  createNewStocktake = properties => {
    const { currentUser, database } = this.props;
    let stocktake;
    database.write(() => {
      stocktake = createRecord(database, 'Stocktake', { ...properties, createdBy: currentUser });
      stocktake.addItemsFromProgram(database);
    });
    return stocktake;
  };

  onConfirmProgramStocktake = programValues => {
    const { runWithLoadingIndicator, navigateTo } = this.props;
    const { program, name: stocktakeName } = programValues;
    runWithLoadingIndicator(() => {
      if (program && program.name) {
        const stocktake = this.createNewStocktake(programValues);
        navigateTo('stocktakeEditor', navStrings.stocktake, { stocktake });
      } else {
        navigateTo('stocktakeManager', navStrings.new_stocktake, { stocktakeName });
      }
    });
  };

  onRowPress = stocktake => {
    const { navigateTo } = this.props;

    this.clearSelection();
    navigateTo('stocktakeEditor', navStrings.stocktake, {
      stocktake,
    });
  };

  onNewStockTake = () => {
    const { navigateTo } = this.props;
    const { usesPrograms } = this.state;
    this.clearSelection();

    if (!usesPrograms) {
      navigateTo('stocktakeManager', navStrings.new_stocktake);
    } else {
      this.setState({ byProgramModalOpen: true });
    }
  };

  onDeleteConfirm = () => {
    const { selection } = this.state;
    const { database } = this.props;
    database.write(() => {
      const stocktakesToDelete = [];
      for (let i = 0; i < selection.length; i += 1) {
        const stocktake = this.stocktakes.find(s => s.id === selection[i]);
        if (stocktake.isValid() && !stocktake.isFinalised) {
          stocktakesToDelete.push(stocktake);
        }
      }
      database.delete('Stocktake', stocktakesToDelete);
    });
    this.clearSelection(true);
  };

  onToggleStatusFilter = isCurrent => {
    this.setState({ showCurrent: isCurrent }, this.refreshData);
  };

  onSelectionChange = newSelection => {
    this.setState({ selection: newSelection });
  };

  clearSelection = shouldRefreshData => {
    this.setState({ selection: [] }, () => shouldRefreshData && this.refreshData());
  };

  updateDataFilters = (newSearchTerm, newSortBy, newIsAscending) => {
    // (... != null) checks for null or undefined (implicitly type coerced to null).
    if (newSearchTerm != null) this.dataFilters.searchTerm = newSearchTerm;
    if (newSortBy != null) this.dataFilters.sortBy = newSortBy;
    if (newIsAscending != null) this.dataFilters.isAscending = newIsAscending;
  };

  /**
   * Returns updated data filtered by |searchTerm| and ordered by |sortBy| and |isAscending|.
   */
  refreshData = (newSearchTerm, newSortBy, newIsAscending) => {
    const { showCurrent } = this.state;

    this.updateDataFilters(newSearchTerm, newSortBy, newIsAscending);
    const { searchTerm, sortBy, isAscending } = this.dataFilters;
    const toggleFilter = showCurrent ? 'status != "finalised"' : 'status == "finalised"';
    const data = this.stocktakes
      .filtered(toggleFilter)
      .filtered('name BEGINSWITH[c] $0 OR serialNumber BEGINSWITH[c] $0', searchTerm)
      .sorted(sortBy, !isAscending); // |!isAscending| reverses sort order if |isAscending| is true.
    this.setState({ data });
  };

  renderCell = (key, stocktake) => {
    switch (key) {
      default:
      case 'name':
        return stocktake.name;
      case 'createdDate':
        return stocktake.createdDate.toDateString();
      case 'status':
        return formatStatus(stocktake.status);
      case 'selected':
        return {
          type: 'checkable',
          icon: 'md-remove-circle',
          isDisabled: stocktake.isFinalised,
        };
    }
  };

  renderToggleBar = () => {
    const { showCurrent } = this.state;

    return (
      <ToggleBar
        style={globalStyles.toggleBar}
        textOffStyle={globalStyles.toggleText}
        textOnStyle={globalStyles.toggleTextSelected}
        toggleOffStyle={globalStyles.toggleOption}
        toggleOnStyle={globalStyles.toggleOptionSelected}
        toggles={[
          {
            text: buttonStrings.current,
            onPress: () => this.onToggleStatusFilter(true),
            isOn: showCurrent,
          },
          {
            text: buttonStrings.past,
            onPress: () => this.onToggleStatusFilter(false),
            isOn: !showCurrent,
          },
        ]}
      />
    );
  };

  onCancelByProgram = () => {
    this.setState({ byProgramModalOpen: false });
  };

  renderNewStocktakeButton = () => (
    <PageButton text={buttonStrings.new_stocktake} onPress={this.onNewStockTake} />
  );

  render() {
    const { database, genericTablePageStyles, topRoute, settings } = this.props;
    const { data, selection, showCurrent, byProgramModalOpen } = this.state;
    return (
      <GenericPage
        data={data}
        refreshData={this.refreshData}
        renderCell={this.renderCell}
        renderTopLeftComponent={this.renderToggleBar}
        renderTopRightComponent={this.renderNewStocktakeButton}
        onRowPress={this.onRowPress}
        onSelectionChange={this.onSelectionChange}
        defaultSortKey={this.dataFilters.sortBy}
        defaultSortDirection={this.dataFilters.isAscending ? 'ascending' : 'descending'}
        columns={[
          {
            key: 'name',
            width: 6,
            title: tableStrings.name,
          },
          {
            key: 'createdDate',
            width: 2,
            title: tableStrings.created_date,
            sortable: true,
          },
          {
            key: 'status',
            width: 2,
            title: tableStrings.status,
            sortable: true,
          },
          {
            key: 'selected',
            width: 1,
            title: tableStrings.delete,
            alignText: 'center',
          },
        ]}
        dataTypesSynchronised={DATA_TYPES_SYNCHRONISED}
        database={database}
        selection={selection}
        {...genericTablePageStyles}
        topRoute={topRoute}
      >
        <BottomConfirmModal
          isOpen={selection.length > 0 && showCurrent}
          questionText={modalStrings.delete_these_stocktakes}
          onCancel={() => this.clearSelection(true)}
          onConfirm={() => this.onDeleteConfirm()}
          confirmText={modalStrings.delete}
        />
        <ByProgramModal
          isOpen={byProgramModalOpen}
          onConfirm={this.onConfirmProgramStocktake}
          onCancel={this.onCancelByProgram}
          database={database}
          type="stocktake"
          settings={settings}
        />
      </GenericPage>
    );
  }
}

export default StocktakesPage;

/* eslint-disable react/forbid-prop-types, react/require-default-props */
StocktakesPage.propTypes = {
  database: PropTypes.object,
  genericTablePageStyles: PropTypes.object,
  topRoute: PropTypes.bool,
  navigateTo: PropTypes.func.isRequired,
  settings: PropTypes.object.isRequired,
  currentUser: PropTypes.object.isRequired,
  runWithLoadingIndicator: PropTypes.func.isRequired,
};
<commit_msg>Fix #962 condition for showing program dialog in stocktakes<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';

import { GenericPage } from './GenericPage';

import { buttonStrings, modalStrings, navStrings, tableStrings } from '../localization';
import { formatStatus, getAllPrograms } from '../utilities';
import { PageButton, BottomConfirmModal, ToggleBar } from '../widgets';
import { ByProgramModal } from '../widgets/modals/index';

import globalStyles from '../globalStyles';
import { createRecord } from '../database/utilities/index';

const DATA_TYPES_SYNCHRONISED = ['Stocktake'];

/**
 * Renders the page for displaying stocktakes.
 *
 * @prop  {Realm}         database    App wide database.
 * @prop  {func}          navigateTo  CallBack for navigation stack.
 * @state {Realm.Results} stocktakes  Result object containing all items.
 */

export class StocktakesPage extends React.Component {
  constructor(props) {
    super(props);
    this.dataFilters = {
      searchTerm: '',
      sortBy: 'createdDate',
      isAscending: false,
    };
    this.state = {
      showCurrent: true,
      selection: [],
      usesPrograms: false,
      byProgramModalOpen: false,
    };
    this.stocktakes = props.database.objects('Stocktake');
  }

  componentDidMount() {
    const { settings, database } = this.props;
    this.setState({ usesPrograms: getAllPrograms(settings, database).length > 0 });
  }

  createNewStocktake = properties => {
    const { currentUser, database } = this.props;
    let stocktake;
    database.write(() => {
      stocktake = createRecord(database, 'Stocktake', { ...properties, createdBy: currentUser });
      stocktake.addItemsFromProgram(database);
    });
    return stocktake;
  };

  onConfirmProgramStocktake = programValues => {
    const { runWithLoadingIndicator, navigateTo } = this.props;
    const { program, name: stocktakeName } = programValues;
    runWithLoadingIndicator(() => {
      if (program && program.name) {
        const stocktake = this.createNewStocktake(programValues);
        navigateTo('stocktakeEditor', navStrings.stocktake, { stocktake });
      } else {
        navigateTo('stocktakeManager', navStrings.new_stocktake, { stocktakeName });
      }
    });
  };

  onRowPress = stocktake => {
    const { navigateTo } = this.props;

    this.clearSelection();
    navigateTo('stocktakeEditor', navStrings.stocktake, {
      stocktake,
    });
  };

  onNewStockTake = () => {
    const { navigateTo } = this.props;
    const { usesPrograms } = this.state;
    this.clearSelection();

    if (!usesPrograms) {
      navigateTo('stocktakeManager', navStrings.new_stocktake);
    } else {
      this.setState({ byProgramModalOpen: true });
    }
  };

  onDeleteConfirm = () => {
    const { selection } = this.state;
    const { database } = this.props;
    database.write(() => {
      const stocktakesToDelete = [];
      for (let i = 0; i < selection.length; i += 1) {
        const stocktake = this.stocktakes.find(s => s.id === selection[i]);
        if (stocktake.isValid() && !stocktake.isFinalised) {
          stocktakesToDelete.push(stocktake);
        }
      }
      database.delete('Stocktake', stocktakesToDelete);
    });
    this.clearSelection(true);
  };

  onToggleStatusFilter = isCurrent => {
    this.setState({ showCurrent: isCurrent }, this.refreshData);
  };

  onSelectionChange = newSelection => {
    this.setState({ selection: newSelection });
  };

  clearSelection = shouldRefreshData => {
    this.setState({ selection: [] }, () => shouldRefreshData && this.refreshData());
  };

  updateDataFilters = (newSearchTerm, newSortBy, newIsAscending) => {
    // (... != null) checks for null or undefined (implicitly type coerced to null).
    if (newSearchTerm != null) this.dataFilters.searchTerm = newSearchTerm;
    if (newSortBy != null) this.dataFilters.sortBy = newSortBy;
    if (newIsAscending != null) this.dataFilters.isAscending = newIsAscending;
  };

  /**
   * Returns updated data filtered by |searchTerm| and ordered by |sortBy| and |isAscending|.
   */
  refreshData = (newSearchTerm, newSortBy, newIsAscending) => {
    const { showCurrent } = this.state;

    this.updateDataFilters(newSearchTerm, newSortBy, newIsAscending);
    const { searchTerm, sortBy, isAscending } = this.dataFilters;
    const toggleFilter = showCurrent ? 'status != "finalised"' : 'status == "finalised"';
    const data = this.stocktakes
      .filtered(toggleFilter)
      .filtered('name BEGINSWITH[c] $0 OR serialNumber BEGINSWITH[c] $0', searchTerm)
      .sorted(sortBy, !isAscending); // |!isAscending| reverses sort order if |isAscending| is true.
    this.setState({ data });
  };

  renderCell = (key, stocktake) => {
    switch (key) {
      default:
      case 'name':
        return stocktake.name;
      case 'createdDate':
        return stocktake.createdDate.toDateString();
      case 'status':
        return formatStatus(stocktake.status);
      case 'selected':
        return {
          type: 'checkable',
          icon: 'md-remove-circle',
          isDisabled: stocktake.isFinalised,
        };
    }
  };

  renderToggleBar = () => {
    const { showCurrent } = this.state;

    return (
      <ToggleBar
        style={globalStyles.toggleBar}
        textOffStyle={globalStyles.toggleText}
        textOnStyle={globalStyles.toggleTextSelected}
        toggleOffStyle={globalStyles.toggleOption}
        toggleOnStyle={globalStyles.toggleOptionSelected}
        toggles={[
          {
            text: buttonStrings.current,
            onPress: () => this.onToggleStatusFilter(true),
            isOn: showCurrent,
          },
          {
            text: buttonStrings.past,
            onPress: () => this.onToggleStatusFilter(false),
            isOn: !showCurrent,
          },
        ]}
      />
    );
  };

  onCancelByProgram = () => {
    this.setState({ byProgramModalOpen: false });
  };

  renderNewStocktakeButton = () => (
    <PageButton text={buttonStrings.new_stocktake} onPress={this.onNewStockTake} />
  );

  render() {
    const { database, genericTablePageStyles, topRoute, settings } = this.props;
    const { data, selection, showCurrent, byProgramModalOpen } = this.state;
    return (
      <GenericPage
        data={data}
        refreshData={this.refreshData}
        renderCell={this.renderCell}
        renderTopLeftComponent={this.renderToggleBar}
        renderTopRightComponent={this.renderNewStocktakeButton}
        onRowPress={this.onRowPress}
        onSelectionChange={this.onSelectionChange}
        defaultSortKey={this.dataFilters.sortBy}
        defaultSortDirection={this.dataFilters.isAscending ? 'ascending' : 'descending'}
        columns={[
          {
            key: 'name',
            width: 6,
            title: tableStrings.name,
          },
          {
            key: 'createdDate',
            width: 2,
            title: tableStrings.created_date,
            sortable: true,
          },
          {
            key: 'status',
            width: 2,
            title: tableStrings.status,
            sortable: true,
          },
          {
            key: 'selected',
            width: 1,
            title: tableStrings.delete,
            alignText: 'center',
          },
        ]}
        dataTypesSynchronised={DATA_TYPES_SYNCHRONISED}
        database={database}
        selection={selection}
        {...genericTablePageStyles}
        topRoute={topRoute}
      >
        <BottomConfirmModal
          isOpen={selection.length > 0 && showCurrent}
          questionText={modalStrings.delete_these_stocktakes}
          onCancel={() => this.clearSelection(true)}
          onConfirm={() => this.onDeleteConfirm()}
          confirmText={modalStrings.delete}
        />
        <ByProgramModal
          isOpen={byProgramModalOpen}
          onConfirm={this.onConfirmProgramStocktake}
          onCancel={this.onCancelByProgram}
          database={database}
          type="stocktake"
          settings={settings}
        />
      </GenericPage>
    );
  }
}

export default StocktakesPage;

/* eslint-disable react/forbid-prop-types, react/require-default-props */
StocktakesPage.propTypes = {
  database: PropTypes.object,
  genericTablePageStyles: PropTypes.object,
  topRoute: PropTypes.bool,
  navigateTo: PropTypes.func.isRequired,
  settings: PropTypes.object.isRequired,
  currentUser: PropTypes.object.isRequired,
  runWithLoadingIndicator: PropTypes.func.isRequired,
};
<|endoftext|>


<commit_before>/**
 * @fileoverview Bubble Sort In JavaScript
 * @author AceMood
 * @email zmike86@gmail.com
 *
 * @License
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 AceMood
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * Order an array.
 * @param {Array} arr An array to be sorted.
 * @param {boolean} orderUp If ordering up or not.
 * @return {Array} the result array.
 */
function bubbleSort (arr, orderUp) {
  var i, j;
  var temp;
  var cmpFn = orderUp ? function (a, b) {
    return a > b
  } : function (a, b) {
    return b > a
  };
  for (i = arr.length - 1 ; i >= 0; i--) {
    for (j = 0; j < i; j++) {
      if (cmpFn(arr[j], arr[j + 1])) {
        temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
  return arr
}

// Usage:
var arr = [3, 2, 4, 9, 1, 5, 7, 6, 8];
var arrSorted = bubbleSort(arr);

console.log(arrSorted);<commit_msg>use 2 for loops<commit_after>/**
 * @fileoverview Bubble Sort In JavaScript
 * @author AceMood
 * @email zmike86@gmail.com
 *
 * @License
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 AceMood
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * Order an array.
 * ʱ临Ӷ	O(pow(n, 2))
 * ʱ临Ӷ	O(n)
 * ƽʱ临Ӷ	O(pow(n, 2))
ռ临Ӷ	ܹO(n)ҪռO(1)
 * @param {Array} arr An array to be sorted.
 * @param {boolean} orderUp If ordering up or not.
 * @return {Array} the result array.
 */
function bubbleSort (arr, orderUp) {
  var i, j;
  var temp;
  var cmpFn = orderUp ? function (a, b) {
    return a > b
  } : function (a, b) {
    return b > a
  };
  for (i = arr.length - 1 ; i >= 0; i--) {
    for (j = 0; j < i; j++) {
      if (cmpFn(arr[j], arr[j + 1])) {
        temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
  return arr
}

// Usage:
var arr = [3, 2, 4, 9, 1, 5, 7, 6, 8];
var arrSorted = bubbleSort(arr);

console.log(arrSorted);<|endoftext|>


<commit_before>import React, { Component, Fragment } from 'react'
import Login from 'components/apply/Login'
import LoadingAnimation from 'components/LoadingAnimation'
import ErrorPage from 'components/admin/ErrorPage'
import Nav from 'components/apply/ApplyNav'
import LeaderForm from 'components/checkup/LeaderForm'
import api from 'api'
import search from 'search'
import {
  Container,
  Card,
  Link,
  Text,
  Heading,
  Section,
  Button,
  Flex,
  Field
} from '@hackclub/design-system'
import { Modal, Overlay, CloseButton } from 'components/Modal'

class SelfForm extends Component {
  state = {
    status: 'ready'
  }

  deletePosition() {
    const { positionId, callback } = this.props
    this.setState({ status: 'deleting' })
    api
      .delete(`v1/leadership_positions/${positionId}`)
      .then(callback)
      .catch(err => {
        console.error(err)
        this.setState({ status: 'error' })
      })
  }

  render() {
    const { status } = this.state
    const { user, club, positionId } = this.props
    switch (status) {
      case 'ready':
        return (
          <Fragment>
            <Text>Will you be a leader next semester?</Text>
            <Flex>
              <Button
                onClick={() => this.setState({ status: 'staying' })}
                bg="success"
                m={2}
                w={1}
              >
                Yes
              </Button>
              <Button
                onClick={() => this.setState({ status: 'confirmDelete' })}
                m={2}
                w={1}
                inverted
              >
                No
              </Button>
            </Flex>
          </Fragment>
        )
      case 'staying':
        return (
          <Fragment>
            <Heading>Complete!</Heading>
            <Text>
              You’re staying as a leader next semester. The checkup is over. You
              can close this window.
            </Text>
          </Fragment>
        )
      case 'confirmDelete':
        return (
          <Fragment>
            <Modal align="left" my={4} p={[3, 4]}>
              <CloseButton onClick={() => this.setState({ status: 'ready' })} />
              <Heading.h2>Are you sure?</Heading.h2>
              <Text my={3}>
                This action <Text.span bold>cannot</Text.span> be undone. This
                will permanently remove you (<Text.span bold>
                  {user.email}
                </Text.span>) from the club at {club.high_school_name}. Please
                type in your email to confirm.
              </Text>
              <Field
                my={3}
                value={this.state.typed}
                onChange={e => {
                  this.setState({ typed: e.target.value })
                }}
              />
              <Text my={3}>
                You will be immediatly logged out and won’t be able to log back
                in.
              </Text>
              <Button
                inverted={user.email !== this.state.typed}
                disabled={user.email !== this.state.typed}
                w={1}
                onClick={() => {
                  if (user.email === this.state.typed) {
                    this.deletePosition()
                  }
                }}
              >
                I understand the consequences, remove me
              </Button>
            </Modal>
            <Overlay onClick={() => this.setState({ status: 'ready' })} />
          </Fragment>
        )
      case 'loading':
        return <LoadingAnimation />
    }
  }
}

export default class extends Component {
  state = {
    status: 'loading'
  }

  componentDidMount() {
    const id = search.get('id')
    api
      .get(`v1/users/current`)
      .then(user =>
        api.get(`v1/new_clubs/${id}`).then(club => {
          const position = club.leadership_positions.find(
            position => (position.new_leader_id = user.new_leader.id)
          )
          if (position) {
            this.setState({
              user,
              club,
              positionId: position.id,
              status: 'success'
            })
          } else {
            this.setState({ user, club, status: 'deleted' })
          }
        })
      )
      .catch(err => {
        console.error(err)
        if (err.status === 401) {
          this.setState({ status: 'needsToAuth' })
        } else if (err.status === 403) {
          this.setState({ status: 'deleted' })
        } else {
          this.setState({ status: 'error' })
        }
      })
  }

  render() {
    const { status, user, club, positionId } = this.state
    switch (status) {
      case 'loading':
        return <LoadingAnimation />
      case 'success':
        return (
          <Fragment>
            <Nav breadcrumb={false} />
            <Container my={3} maxWidth={32}>
              <Card FlexShadowSize="sm" p={3}>
                <SelfForm
                  user={user}
                  club={club}
                  positionId={positionId}
                  callback={() => this.setState({ status: 'deleted' })}
                />
              </Card>
            </Container>
          </Fragment>
        )
      case 'deleted':
        return (
          <Fragment>
            <Nav breadcrumb={false} />
            <Container my={3} maxWidth={32}>
              <Heading>Removed</Heading>
              <Text>You are no longer marked as a club leader.</Text>
              <Text>
                If you removed yourself by accident, please contact me at{' '}
                <Link href="mailto:max@hackclub.com">max@hackclub.com</Link>.
              </Text>
            </Container>
          </Fragment>
        )
      case 'needsToAuth':
        return <Login heading="Login to view" />
      default:
        return <ErrorPage />
    }
  }
}
<commit_msg>Clarify finish checkup page<commit_after>import React, { Component, Fragment } from 'react'
import Login from 'components/apply/Login'
import LoadingAnimation from 'components/LoadingAnimation'
import ErrorPage from 'components/admin/ErrorPage'
import Nav from 'components/apply/ApplyNav'
import LeaderForm from 'components/checkup/LeaderForm'
import api from 'api'
import search from 'search'
import {
  Container,
  Card,
  Link,
  Text,
  Heading,
  Button,
  Flex
} from '@hackclub/design-system'

class SelfForm extends Component {
  state = {
    status: 'ready'
  }

  render() {
    const { status } = this.state
    const { user, club, position } = this.props
    switch (status) {
      case 'ready':
        return (
          <Fragment>
            <Heading>Will you lead the club next semester?</Heading>
            <Flex my={3}>
              <Button
                onClick={() => this.setState({ status: 'staying' })}
                bg="success"
                m={2}
                w={1}
              >
                Yes
              </Button>
              <Button
                onClick={() => this.setState({ status: 'leaving' })}
                m={2}
                w={1}
                inverted
              >
                No
              </Button>
            </Flex>
          </Fragment>
        )
        debugger
      case 'leaving':
        return (
          <Fragment>
            <Heading>Leaving?</Heading>
            <Text my={3}>
              Sorry to hear you’re leaving. Before you go, we would like to have
              an exit interview. It will be a short (~10 minutes) call to talk
              about your time leading your club.
            </Text>
            <Button href="https://exit-interview.hackclub.com" w={1}>
              Schedule the call
            </Button>
          </Fragment>
        )
      case 'staying':
        return (
          <Fragment>
            <Heading>Complete!</Heading>
            <Text my={3}>
              You’re leading your club next semester. The checkup is over. You
              can close this window.
            </Text>
          </Fragment>
        )
      case 'loading':
        return <LoadingAnimation />
    }
  }
}

export default class extends Component {
  state = {
    status: 'loading'
  }

  componentDidMount() {
    const id = search.get('id')
    api
      .get(`v1/users/current`)
      .then(user =>
        api.get(`v1/new_clubs/${id}`).then(club => {
          const position = club.leadership_positions.find(
            position => (position.new_leader_id = user.new_leader.id)
          )
          if (position) {
            this.setState({ user, club, position, status: 'success' })
          } else {
            this.setState({ user, club, status: 'deleted' })
          }
        })
      )
      .catch(err => {
        console.error(err)
        if (err.status === 401) {
          this.setState({ status: 'needsToAuth' })
        } else if (err.status === 403) {
          this.setState({ status: 'deleted' })
        } else {
          this.setState({ status: 'error' })
        }
      })
  }

  render() {
    const { status, user, club, position } = this.state
    switch (status) {
      case 'loading':
        return <LoadingAnimation />
      case 'success':
        return (
          <Fragment>
            <Nav breadcrumb={false} />
            <Container my={3} maxWidth={32}>
              <Card FlexShadowSize="sm" p={3}>
                <SelfForm
                  user={user}
                  club={club}
                  position={position}
                  callback={() => this.setState({ status: 'deleted' })}
                />
              </Card>
            </Container>
          </Fragment>
        )
      case 'deleted':
        return (
          <Fragment>
            <Nav breadcrumb={false} />
            <Container my={3} maxWidth={32}>
              <Heading>Removed</Heading>
              <Text>You are no longer marked as a club leader.</Text>
              <Text>
                If you removed yourself by accident, please contact me at{' '}
                <Link href="mailto:max@hackclub.com">max@hackclub.com</Link>.
              </Text>
            </Container>
          </Fragment>
        )
      case 'needsToAuth':
        return <Login heading="Login to view" />
      default:
        return <ErrorPage />
    }
  }
}
<|endoftext|>


<commit_before>import React from 'react'
import {blueA400, grey600, grey700, blueA100} from 'material-ui/styles/colors'
import { connect } from 'react-redux'
import sizeMe from 'react-sizeme'
import { routerActions } from 'react-router-redux'
import { editResource, removeResource, scaleResource } from '../state/actions/workloads'

import {Card, CardHeader} from 'material-ui/Card'
import ConfigurationPane from './configuration-pane/ConfigurationPane'
import PodTemplatePane from './configuration-pane/PodTemplatePane'
import EventViewer from './EventViewer'

import IconConfiguration from 'material-ui/svg-icons/action/list'
import IconPodTemplate from 'material-ui/svg-icons/action/flip-to-back'
import IconLogs from 'material-ui/svg-icons/action/receipt'
import IconTerminal from 'material-ui/svg-icons/hardware/computer'
import IconEvents from 'material-ui/svg-icons/action/event'
import IconExpand from 'material-ui/svg-icons/navigation/more-vert'
import IconEdit from 'material-ui/svg-icons/editor/mode-edit'
import IconDelete from 'material-ui/svg-icons/action/delete'
import IconScale from 'material-ui/svg-icons/communication/import-export'
import IconSuspend from 'material-ui/svg-icons/content/block'

import FilterChip from './FilterChip'

import { withRouter } from 'react-router-dom'

import {Tabs, Tab} from 'material-ui/Tabs'

import LoadingSpinner from './LoadingSpinner'

import RaisedButton from 'material-ui/RaisedButton'
import Popover from 'material-ui/Popover'
import Menu from 'material-ui/Menu'
import MenuItem from 'material-ui/MenuItem'
import KubeKinds from '../kube-kinds'
import KindAbbreviation from './KindAbbreviation'
import queryString from 'query-string'

import { resourceStatus as resourceStatusIcons } from './icons'

import ConfirmationDialog from './ConfirmationDialog'
import ScaleDialog from './ScaleDialog'
import './ResourceInfoPage.css'

import Loadable from 'react-loadable'
import LoadingComponentStub from '../components/LoadingComponentStub'

const AsyncTerminalViewer = Loadable({
  loader: () => import('./TerminalViewer'),
  loading: LoadingComponentStub
})

const AsyncLogViewer = Loadable({
  loader: () => import('./LogViewer'),
  loading: LoadingComponentStub
})

const mapStateToProps = function(store) {
  return {
    filterNames: store.workloads.filterNames,
    pods: store.workloads.pods,
  }
}

const mapDispatchToProps = function(dispatch, ownProps) {
  return {
    editResource: function(namespace, kind, name) {
      dispatch(editResource(namespace, kind, name))
      ownProps.selectView('edit')
    },
    removeResource: function(resource, filterNames) {
      dispatch(removeResource(resource))

      let search = queryString.stringify({filters: filterNames})
      if (!!search) {
        search = '?'+search
      }
      dispatch(routerActions.push({
        pathname: `/${ownProps.resourceGroup}`,
        search: search,
      }))
    },
    viewFilters: function(filters) {
      let search = `?${queryString.stringify({filters: filters})}`
      dispatch(routerActions.push({
        pathname: `/${ownProps.resourceGroup}`,
        search: search,
      }))
    },
    selectView: function(tab) {
      if (tab === 'edit') {
        let { params } = ownProps.match
        dispatch(editResource(params.namespace, params.kind, params.name))
      }

      let { location } = ownProps
      let newSearch = `?view=${tab}`
      console.log(`selectView: pushed new location...`)
      dispatch(routerActions.push({
        pathname: location.pathname,
        search: newSearch,
        hash: location.hash,
      }))
    },
    scaleResource: function(resource, replicas) {
      dispatch(scaleResource(
        resource.metadata.namespace,
        resource.kind,
        resource.metadata.name,
        replicas))
    }
  }
}

const styles = {
  tabs: {
    backgroundColor: grey600,
  },
  tabsInkBar: {
    backgroundColor: blueA400,
    height: 3,
    marginTop: -4,
    borderTop: `1px ${blueA100} solid`,
  },
  card: {
    margin: 5,
  },
  cardHeader: {
    background: 'rgb(66,66,66)',
    minHeight: '125px',
  },
  cardHeaderTitle: {
    fontWeight: 600,
    fontSize: '18px',
  },
  cardHeaderTitleStyle: {
    fontSize: '24px',
    fontWeight: 600,
    paddingLeft: 10,
    color: 'rgba(240,240,240,1)',
  }
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps) (
sizeMe({ monitorHeight: true, monitorWidth: true }) (
class ResourceInfoPage extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      actionsOpen: false,
      deleteOpen: false,
      scaleOpen: false,
      suspendOpen: false,
      editing: false,
    }

    this.kubeKind = !!props.resource && KubeKinds[props.resourceGroup][props.resource.kind]
    for (let fn of ['handleScale','handleSuspend','handleDelete']) {
      this[fn] = this[fn].bind(this)
    }
  }

  handleActionsTouchTap = (event) => {
    // This prevents ghost click.
    event.preventDefault();
    this.setState({
      actionsOpen: true,
      deleteOpen: false,
      scaleOpen: false,
      suspendOpen: false,
      actionsAnchor: event.currentTarget,
    })
  }

  handleActionsRequestClose = () => {
    this.setState({
      actionsOpen: false,
    })
  }

  handleFilterLabelTouchTap = (filters) => {
    this.props.viewFilters(filters)
  }

  handleRequestCloseDelete = () => {
    this.setState({
      deleteOpen: false,
    })
  }

  handleDelete = () => {
    this.setState({
      deleteOpen: true,
      actionsOpen: false,
    })
  }

  handleConfirmDelete = () => {
    this.setState({
      deleteOpen: false,
    })
    this.props.removeResource(this.props.resource)
  }

  handleScale = () => {
    this.setState({
      scaleOpen: true,
      actionsOpen: false,
    })
  }

  handleRequestCloseScale = () => {
    this.setState({
      scaleOpen: false,
    })
  }

  handleConfirmScale = (replicas) => {
    this.setState({
      scaleOpen: false,
    })
    if (replicas !== undefined) {
      this.props.scaleResource(this.props.resource, replicas)
    }
  }

  handleSuspend = () => {
    this.setState({
      suspendOpen: true,
      actionsOpen: false,
    })
  }

  handleRequestCloseSuspend = () => {
    this.setState({
      suspendOpen: false,
    })
  }

  handleConfirmSuspend = () => {
    this.setState({
      suspendOpen: false,
    })
    this.props.scaleResource(this.props.resource, 0)
  }

  componentWillReceiveProps = (nextProps) => {
    let { props } = this
    if ((props.activeTab === 'logs' && !props.enableLogsTab)
      || (props.activeTab === 'term' && !props.enableTerminalTab)) {

      props.selectView('config')
    }

  }

  componentDidUpdate = () => {
    this.kubeKind = !!this.props.resource && KubeKinds[this.props.resourceGroup][this.props.resource.kind]
  }

  render() {

    let { resourceGroup, resource, logs, activeTab, enableLogsTab, enableTerminalTab } = this.props

    let tabs = [
      {
        name: 'config',
        component: ConfigurationPane,
        icon: <IconConfiguration/>,
        props: {resource: resource, resourceGroup: resourceGroup},
      }
    ]

    if (resource.spec && resource.spec.template) {
      tabs.push({
        name: 'pod template',
        component: PodTemplatePane,
        icon: <IconPodTemplate/>,
        props: {resource: resource},
      })
    }

    tabs.push({
      name: 'events',
      component: EventViewer,
      icon: <IconEvents/>,
    })

    if (enableLogsTab) {
      tabs.push({
        name: 'logs',
        component: AsyncLogViewer,
        icon: <IconLogs/>,
        props: {logs: logs},
      })
    }
    if (enableTerminalTab) {
      tabs.push({
        name: 'terminal',
        component: AsyncTerminalViewer,
        icon: <IconTerminal/>,
        props: {logs: logs},
      })
    }

    activeTab = (activeTab || tabs[0].name)

    return (

        <LoadingSpinner hidden={!this.props.resource}/>

        <Card className="resource-info" style={styles.card} >
          <CardHeader
            title={resource.metadata.name}
            titleStyle={styles.cardHeaderTitleStyle}
            subtitle={

                {resource.metadata.labels && Object.entries(resource.metadata.labels).map(([key, value]) =>
                  <FilterChip
                    onTouchTap={this.handleFilterLabelTouchTap.bind(this,[`namespace:${resource.metadata.namespace}`, `${key}:${value}`])}
                    key={key}
                    prefix={key}
                    suffix={value} />)
                }

                  <FilterChip
                    onTouchTap={this.handleFilterLabelTouchTap.bind(this,[`namespace:${resource.metadata.namespace}`])}
                    prefixStyle={{color: grey700}}
                    prefix={'namespace'} suffix={resource.metadata.namespace} />
                  <FilterChip
                    onTouchTap={this.handleFilterLabelTouchTap.bind(this,[`namespace:${resource.metadata.namespace}`,`kind:${resource.kind}`])}
                    prefixStyle={{color: grey700}}
                    prefix={'kind'} suffix={resource.kind} />

            }
            avatar={<KindAbbreviation text={this.kubeKind.abbrev} color={this.kubeKind.color}/>}
            style={styles.cardHeader}
          >

            <RaisedButton
              label="Actions"
              labelPosition="before"
              onTouchTap={this.handleActionsTouchTap}
              icon={<IconExpand/>}
              style={{position: 'absolute', right: 20, top: 20}}
              primary={true}
            />
            <Popover
              open={this.state.actionsOpen}
              anchorEl={this.state.actionsAnchor}
              onRequestClose={this.handleActionsRequestClose}
              anchorOrigin={{horizontal: 'right', vertical: 'bottom'}}
              targetOrigin={{horizontal: 'right', vertical: 'top'}}
            >
              <Menu desktop={true}>
                {(this.props.resource.spec && this.props.resource.spec.replicas > -1) &&
                  <MenuItem primaryText="Scale"
                    leftIcon={<IconScale/>}
                    onTouchTap={this.handleScale}
                    />
                }

                {(!('editable' in this.kubeKind) || !!this.kubeKind.editable) &&
                  <MenuItem primaryText="Edit"
                    onTouchTap={() => {
                      this.props.selectView('edit')
                      this.setState({actionsOpen: false})
                    }}
                    leftIcon={<IconEdit/>}
                    />
                }

                {(this.props.resource.spec && this.props.resource.spec.replicas > 0) &&
                  <MenuItem primaryText="Suspend"
                    leftIcon={<IconSuspend/>}
                    onTouchTap={this.handleSuspend}
                    />
                }

                <MenuItem primaryText="Delete"
                  leftIcon={<IconDelete/>}
                  onTouchTap={this.handleDelete}
                  />

              </Menu>
            </Popover>

{resourceStatusIcons[resource.statusSummary]}

          </CardHeader>

          <Tabs
            style={{background: 'white'}}
            tabItemContainerStyle={styles.tabs}
            contentContainerStyle={{overflow: 'hidden'}}
            inkBarStyle={styles.tabsInkBar}
            className={`resource-tabs ${activeTab.replace(" ","-")}`}
            value={activeTab}
            ref={ (ref) => {
                if (!!ref) {
                  // get absolute bottom of the tab button bar so our tab contents can fill available space
                  let tabButtons = document.getElementsByClassName('resource-tabs')[0].children[0]
                  this.contentTop = tabButtons.getBoundingClientRect().bottom
                }
              }
            }
            >
            {tabs.map(tab => {
                let Component = tab.component
                return <Tab key={tab.name} icon={tab.icon} label={tab.name} value={tab.name} onActive={this.props.selectView.bind(this, tab.name)}>
                  <Component {...tab.props} contentTop={this.contentTop}/>
                </Tab>
              }
            )}
          </Tabs>
        </Card>

        <ConfirmationDialog
          open={this.state.deleteOpen}
          title={'Delete Resource(s):'}
          message={`Are you sure you want to delete the following resource?`}
          resources={[this.props.resource]}
          onRequestClose={this.handleRequestCloseDelete}
          onConfirm={this.handleConfirmDelete}
          />

        <ConfirmationDialog
          open={this.state.suspendOpen}
          title={'Suspend Resource(s):'}
          message={`Are you sure you want to suspend the following resource (by scaling to 0 replicas)?`}
          resources={[this.props.resource]}
          onRequestClose={this.handleRequestCloseSuspend}
          onConfirm={this.handleConfirmSuspend}
          />

        <ScaleDialog
          open={this.state.scaleOpen}
          resource={this.props.resource}
          onRequestClose={this.handleRequestCloseScale}
          onConfirm={this.handleConfirmScale}
          />

    )
  }
})))
<commit_msg>use link for resource kind, don't show ns when it doesn't apply<commit_after>import React from 'react'
import {blueA400, grey600, grey700, blueA100} from 'material-ui/styles/colors'
import { connect } from 'react-redux'
import sizeMe from 'react-sizeme'
import { routerActions } from 'react-router-redux'
import { editResource, removeResource, scaleResource } from '../state/actions/workloads'
import { linkForResourceKind } from '../routes'

import {Card, CardHeader} from 'material-ui/Card'
import ConfigurationPane from './configuration-pane/ConfigurationPane'
import PodTemplatePane from './configuration-pane/PodTemplatePane'
import EventViewer from './EventViewer'

import IconConfiguration from 'material-ui/svg-icons/action/list'
import IconPodTemplate from 'material-ui/svg-icons/action/flip-to-back'
import IconLogs from 'material-ui/svg-icons/action/receipt'
import IconTerminal from 'material-ui/svg-icons/hardware/computer'
import IconEvents from 'material-ui/svg-icons/action/event'
import IconExpand from 'material-ui/svg-icons/navigation/more-vert'
import IconEdit from 'material-ui/svg-icons/editor/mode-edit'
import IconDelete from 'material-ui/svg-icons/action/delete'
import IconScale from 'material-ui/svg-icons/communication/import-export'
import IconSuspend from 'material-ui/svg-icons/content/block'

import FilterChip from './FilterChip'

import { withRouter } from 'react-router-dom'

import {Tabs, Tab} from 'material-ui/Tabs'

import LoadingSpinner from './LoadingSpinner'

import RaisedButton from 'material-ui/RaisedButton'
import Popover from 'material-ui/Popover'
import Menu from 'material-ui/Menu'
import MenuItem from 'material-ui/MenuItem'
import KubeKinds from '../kube-kinds'
import KindAbbreviation from './KindAbbreviation'
import queryString from 'query-string'

import { resourceStatus as resourceStatusIcons } from './icons'

import ConfirmationDialog from './ConfirmationDialog'
import ScaleDialog from './ScaleDialog'
import './ResourceInfoPage.css'

import Loadable from 'react-loadable'
import LoadingComponentStub from '../components/LoadingComponentStub'

const AsyncTerminalViewer = Loadable({
  loader: () => import('./TerminalViewer'),
  loading: LoadingComponentStub
})

const AsyncLogViewer = Loadable({
  loader: () => import('./LogViewer'),
  loading: LoadingComponentStub
})

const mapStateToProps = function(store) {
  return {
    filterNames: store.workloads.filterNames,
    pods: store.workloads.pods,
  }
}

const mapDispatchToProps = function(dispatch, ownProps) {
  return {
    editResource: function(namespace, kind, name) {
      dispatch(editResource(namespace, kind, name))
      ownProps.selectView('edit')
    },
    removeResource: function(resource, filterNames) {
      dispatch(removeResource(resource))

      let search = queryString.stringify({filters: filterNames})
      if (!!search) {
        search = '?'+search
      }
      dispatch(routerActions.push({
        pathname: `/${ownProps.resourceGroup}`,
        search: search,
      }))
    },
    viewKind: function(kind, namespace) {
      let ns = {}
      if (!!namespace) {
        ns[namespace] = true
      }
      dispatch(routerActions.push(linkForResourceKind(kind, ns)))
    },
    viewFilters: function(filters) {
      let search = `?${queryString.stringify({filters: filters})}`
      dispatch(routerActions.push({
        pathname: `/${ownProps.resourceGroup}`,
        search: search,
      }))
    },
    selectView: function(tab) {
      if (tab === 'edit') {
        let { params } = ownProps.match
        dispatch(editResource(params.namespace, params.kind, params.name))
      }

      let { location } = ownProps
      let newSearch = `?view=${tab}`
      console.log(`selectView: pushed new location...`)
      dispatch(routerActions.push({
        pathname: location.pathname,
        search: newSearch,
        hash: location.hash,
      }))
    },
    scaleResource: function(resource, replicas) {
      dispatch(scaleResource(
        resource.metadata.namespace,
        resource.kind,
        resource.metadata.name,
        replicas))
    }
  }
}

const styles = {
  tabs: {
    backgroundColor: grey600,
  },
  tabsInkBar: {
    backgroundColor: blueA400,
    height: 3,
    marginTop: -4,
    borderTop: `1px ${blueA100} solid`,
  },
  card: {
    margin: 5,
  },
  cardHeader: {
    background: 'rgb(66,66,66)',
    minHeight: '125px',
  },
  cardHeaderTitle: {
    fontWeight: 600,
    fontSize: '18px',
  },
  cardHeaderTitleStyle: {
    fontSize: '24px',
    fontWeight: 600,
    paddingLeft: 10,
    color: 'rgba(240,240,240,1)',
  }
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps) (
sizeMe({ monitorHeight: true, monitorWidth: true }) (
class ResourceInfoPage extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      actionsOpen: false,
      deleteOpen: false,
      scaleOpen: false,
      suspendOpen: false,
      editing: false,
    }

    this.kubeKind = !!props.resource && KubeKinds[props.resourceGroup][props.resource.kind]
    for (let fn of ['handleScale','handleSuspend','handleDelete']) {
      this[fn] = this[fn].bind(this)
    }
  }

  handleActionsTouchTap = (event) => {
    // This prevents ghost click.
    event.preventDefault();
    this.setState({
      actionsOpen: true,
      deleteOpen: false,
      scaleOpen: false,
      suspendOpen: false,
      actionsAnchor: event.currentTarget,
    })
  }

  handleActionsRequestClose = () => {
    this.setState({
      actionsOpen: false,
    })
  }

  handleFilterLabelTouchTap = (filters) => {
    this.props.viewFilters(filters)
  }

  handleKindTouchTap = (kind, namespace) => {
    this.props.viewKind(kind, namespace)
  }

  handleRequestCloseDelete = () => {
    this.setState({
      deleteOpen: false,
    })
  }

  handleDelete = () => {
    this.setState({
      deleteOpen: true,
      actionsOpen: false,
    })
  }

  handleConfirmDelete = () => {
    this.setState({
      deleteOpen: false,
    })
    this.props.removeResource(this.props.resource)
  }

  handleScale = () => {
    this.setState({
      scaleOpen: true,
      actionsOpen: false,
    })
  }

  handleRequestCloseScale = () => {
    this.setState({
      scaleOpen: false,
    })
  }

  handleConfirmScale = (replicas) => {
    this.setState({
      scaleOpen: false,
    })
    if (replicas !== undefined) {
      this.props.scaleResource(this.props.resource, replicas)
    }
  }

  handleSuspend = () => {
    this.setState({
      suspendOpen: true,
      actionsOpen: false,
    })
  }

  handleRequestCloseSuspend = () => {
    this.setState({
      suspendOpen: false,
    })
  }

  handleConfirmSuspend = () => {
    this.setState({
      suspendOpen: false,
    })
    this.props.scaleResource(this.props.resource, 0)
  }

  componentWillReceiveProps = (nextProps) => {
    let { props } = this
    if ((props.activeTab === 'logs' && !props.enableLogsTab)
      || (props.activeTab === 'term' && !props.enableTerminalTab)) {

      props.selectView('config')
    }

  }

  componentDidUpdate = () => {
    this.kubeKind = !!this.props.resource && KubeKinds[this.props.resourceGroup][this.props.resource.kind]
  }

  render() {

    let { resourceGroup, resource, logs, activeTab, enableLogsTab, enableTerminalTab } = this.props

    let tabs = [
      {
        name: 'config',
        component: ConfigurationPane,
        icon: <IconConfiguration/>,
        props: {resource: resource, resourceGroup: resourceGroup},
      }
    ]

    if (resource.spec && resource.spec.template) {
      tabs.push({
        name: 'pod template',
        component: PodTemplatePane,
        icon: <IconPodTemplate/>,
        props: {resource: resource},
      })
    }

    tabs.push({
      name: 'events',
      component: EventViewer,
      icon: <IconEvents/>,
    })

    if (enableLogsTab) {
      tabs.push({
        name: 'logs',
        component: AsyncLogViewer,
        icon: <IconLogs/>,
        props: {logs: logs},
      })
    }
    if (enableTerminalTab) {
      tabs.push({
        name: 'terminal',
        component: AsyncTerminalViewer,
        icon: <IconTerminal/>,
        props: {logs: logs},
      })
    }

    activeTab = (activeTab || tabs[0].name)

    return (

        <LoadingSpinner hidden={!this.props.resource}/>

        <Card className="resource-info" style={styles.card} >
          <CardHeader
            title={resource.metadata.name}
            titleStyle={styles.cardHeaderTitleStyle}
            subtitle={

                {resource.metadata.labels && Object.entries(resource.metadata.labels).map(([key, value]) =>
                  <FilterChip
                    onTouchTap={this.handleFilterLabelTouchTap.bind(this,[`namespace:${resource.metadata.namespace}`, `${key}:${value}`])}
                    key={key}
                    prefix={key}
                    suffix={value} />)
                }

                  {'namespace' in resource.metadata &&
                    <FilterChip
                      onTouchTap={this.handleFilterLabelTouchTap.bind(this,[`namespace:${resource.metadata.namespace}`])}
                      prefixStyle={{color: grey700}}
                      prefix={'namespace'} suffix={resource.metadata.namespace} />
                  }
                  <FilterChip
                    onTouchTap={this.handleKindTouchTap.bind(this,resource.kind, resource.metadata.namespace)}
                    prefixStyle={{color: grey700}}
                    prefix={'kind'} suffix={resource.kind} />

            }
            avatar={<KindAbbreviation text={this.kubeKind.abbrev} color={this.kubeKind.color}/>}
            style={styles.cardHeader}
          >

            <RaisedButton
              label="Actions"
              labelPosition="before"
              onTouchTap={this.handleActionsTouchTap}
              icon={<IconExpand/>}
              style={{position: 'absolute', right: 20, top: 20}}
              primary={true}
            />
            <Popover
              open={this.state.actionsOpen}
              anchorEl={this.state.actionsAnchor}
              onRequestClose={this.handleActionsRequestClose}
              anchorOrigin={{horizontal: 'right', vertical: 'bottom'}}
              targetOrigin={{horizontal: 'right', vertical: 'top'}}
            >
              <Menu desktop={true}>
                {(this.props.resource.spec && this.props.resource.spec.replicas > -1) &&
                  <MenuItem primaryText="Scale"
                    leftIcon={<IconScale/>}
                    onTouchTap={this.handleScale}
                    />
                }

                {(!('editable' in this.kubeKind) || !!this.kubeKind.editable) &&
                  <MenuItem primaryText="Edit"
                    onTouchTap={() => {
                      this.props.selectView('edit')
                      this.setState({actionsOpen: false})
                    }}
                    leftIcon={<IconEdit/>}
                    />
                }

                {(this.props.resource.spec && this.props.resource.spec.replicas > 0) &&
                  <MenuItem primaryText="Suspend"
                    leftIcon={<IconSuspend/>}
                    onTouchTap={this.handleSuspend}
                    />
                }

                <MenuItem primaryText="Delete"
                  leftIcon={<IconDelete/>}
                  onTouchTap={this.handleDelete}
                  />

              </Menu>
            </Popover>

{resourceStatusIcons[resource.statusSummary]}

          </CardHeader>

          <Tabs
            style={{background: 'white'}}
            tabItemContainerStyle={styles.tabs}
            contentContainerStyle={{overflow: 'hidden'}}
            inkBarStyle={styles.tabsInkBar}
            className={`resource-tabs ${activeTab.replace(" ","-")}`}
            value={activeTab}
            ref={ (ref) => {
                if (!!ref) {
                  // get absolute bottom of the tab button bar so our tab contents can fill available space
                  let tabButtons = document.getElementsByClassName('resource-tabs')[0].children[0]
                  this.contentTop = tabButtons.getBoundingClientRect().bottom
                }
              }
            }
            >
            {tabs.map(tab => {
                let Component = tab.component
                return <Tab key={tab.name} icon={tab.icon} label={tab.name} value={tab.name} onActive={this.props.selectView.bind(this, tab.name)}>
                  <Component {...tab.props} contentTop={this.contentTop}/>
                </Tab>
              }
            )}
          </Tabs>
        </Card>

        <ConfirmationDialog
          open={this.state.deleteOpen}
          title={'Delete Resource(s):'}
          message={`Are you sure you want to delete the following resource?`}
          resources={[this.props.resource]}
          onRequestClose={this.handleRequestCloseDelete}
          onConfirm={this.handleConfirmDelete}
          />

        <ConfirmationDialog
          open={this.state.suspendOpen}
          title={'Suspend Resource(s):'}
          message={`Are you sure you want to suspend the following resource (by scaling to 0 replicas)?`}
          resources={[this.props.resource]}
          onRequestClose={this.handleRequestCloseSuspend}
          onConfirm={this.handleConfirmSuspend}
          />

        <ScaleDialog
          open={this.state.scaleOpen}
          resource={this.props.resource}
          onRequestClose={this.handleRequestCloseScale}
          onConfirm={this.handleConfirmScale}
          />

    )
  }
})))
<|endoftext|>