<file_sep>import React from 'react';
import '../modal-shared/modalShared.css';
import './editModal.css';
import PropTypes from 'prop-types';
import ModalShared from '../modal-shared/modalShared';

export default function EditModal(props) {
    return (
        <ModalShared
            show={props.show}
            modalBody={
                [
                    <div className="modal-edit-buttons-container">
                        <div className="modal-buttons modal-edit-button" onClick={props.deleteClicked}>
                            Edit
                        </div>
                        <div className="modal-buttons modal-cancel-button" onClick={props.cancelClicked}>
                            Cancel
                        </div>
                    </div>
                ]
            }
            modalTitle="Edit Brakes"
            stylingId="edit"
        />
    );
}

EditModal.propTypes = {
    show: PropTypes.bool.isRequired,
    deleteClicked: PropTypes.func.isRequired,
    cancelClicked: PropTypes.func.isRequired,
    stylingId: PropTypes.string.isRequired
};
<file_sep>export default class ComponentsData {
    constructor(dbid, productname, manufacturer, contact, failrate) {
        this.dbid = dbid;
        this.productname = productname;
        this.manufacturer = manufacturer;
        this.contact = contact;
        this.failrate = failrate;
    }

    static get describe() {
        return ['Component Name', 'Manufacturer', 'Contact', 'Fail Rate'];
    }

    static get getIds() {
        return ['productname', 'manufacturer', 'contact', 'failrate'];
    }

    static get types() {
        return ['string', 'string', 'string', 'number'];
    }

    static get constraints() {
        return [{ canBeEmpty: false }, { canBeEmpty: false }, { canBeEmpty: false }, {
            moreThan: 0,
            lessThanOrEqualTo: 100,
            canBeEmpty: false,
        }];
    }

    getData() {
        return [this.productname, this.manufacturer, this.contact, this.failrate];
    }
}
<file_sep>import React from 'react';
import PropTypes from 'prop-types';
import { getDisplayName, getIcon } from '../utils';
import './TopMenu.css';
import './TopMenuColors.css';

function SearchField(props) {
    return (
        <div id="mainSearchField">
            <img id="searchIcon" src={getIcon('search')} alt="Search Icon" />
            <input
                type="text"
                className="beautiful-textfield"
                placeholder={`Search ${getDisplayName(props.currentSelectedMenuItem)}`}
                onBlur={() => {
                    props.setSearchActive(false);
                }}
                onFocus={(e) => {
                    props.setSearchActive(true);
                    props.setSearchContents(e.target.value);
                }}
                onChange={(e) => {
                    props.setSearchContents(e.target.value);
                }}
                value={props.searchContents}
            />
        </div>
    );
}

SearchField.propTypes = {
    currentSelectedMenuItem: PropTypes.string.isRequired,
    setSearchActive: PropTypes.func.isRequired,
    searchContents: PropTypes.string.isRequired,
    setSearchContents: PropTypes.func.isRequired,
};

function TopMenuButton(props) {
    return (
        <div className={`topMenuButtonCircle ${props.disabled ? 'topMenuButtonCircleDisabled' : 'topMenuButtonCircleEnabled'}`} onClick={props.onClick}>
            <div className="topMenuButtonInnerCircle">
                <img className={`topMenuButtonIcon${props.disabled ? ' topMenuButtonIconDisabled' : ''}`} id={props.imgId} src={getIcon(props.iconName)} alt={props.alt} />
            </div>
        </div>
    );
}

TopMenuButton.propTypes = {
    disabled: PropTypes.bool.isRequired,
    onClick: PropTypes.func.isRequired,
    imgId: PropTypes.string.isRequired,
    iconName: PropTypes.string.isRequired,
    alt: PropTypes.string.isRequired,
};

function MenuItem(props) {
    const myItemSelected = props.myMenuItem === props.currentSelectedMenuItem;
    return (
        <div className={`segmentedControlOption segmentedControlOption${myItemSelected ? 'Selected' : 'Deselected'}`} onClick={props.onClick}>
            <div className={`segmentedControlImageWrapper ${myItemSelected ? 'segmentedItemSelected' : 'segmentedItemDeselected'}`}>
                <img className="segmentedControlIcon" src={getIcon(props.myMenuItem)} alt={getDisplayName(props.myMenuItem)} />
            </div>
        </div>
    );
}

MenuItem.propTypes = {
    myMenuItem: PropTypes.string.isRequired,
    currentSelectedMenuItem: PropTypes.string.isRequired,
    onClick: PropTypes.func.isRequired,
};

export default class TopMenu extends React.Component {
    handleMenuPress(menuItem) {
        this.props.setSelectedMenuItem(menuItem);
    }

    render() {
        return (
            <div id="topMenu">
                <div id="topMenuMainSection">
                    <div className="topSegEqual" id="buttonsBar">
                        <div
                            id="selectOrDeselectAll"
                            className="topMenuButtonCircleEnabled"
                            onClick={this.props.performDeSelectAll}
                        >
                            {this.props.selectionToggleButtonIsSelectAll ? 'Select All' : 'Deselect All'}
                        </div>
                        <TopMenuButton
                            iconName="edit"
                            alt="Edit"
                            imgId="editIcon"
                            disabled={!this.props.editEnabled}
                            onClick={() => {
                                this.props.performEdit();
                            }}
                        />
                        <TopMenuButton
                            iconName="delete"
                            alt="Delete"
                            imgId="deleteIcon"
                            disabled={!this.props.deleteEnabled}
                            onClick={() => {
                                this.props.performDelete();
                            }}
                        />
                        <TopMenuButton
                            iconName="plus"
                            alt="Add"
                            imgId="plusIcon"
                            disabled={false}
                            onClick={() => {
                                this.props.performAdd();
                            }}
                        />
                    </div>
                    <div id="mainSegmentedControl">
                        <MenuItem
                            currentSelectedMenuItem={this.props.currentSelectedMenuItem}
                            myMenuItem="components"
                            onClick={() => this.handleMenuPress('components')}
                        />
                        <MenuItem
                            currentSelectedMenuItem={this.props.currentSelectedMenuItem}
                            myMenuItem="failures"
                            onClick={() => this.handleMenuPress('failures')}
                        />
                        <MenuItem
                            currentSelectedMenuItem={this.props.currentSelectedMenuItem}
                            myMenuItem="modes"
                            onClick={() => this.handleMenuPress('modes')}
                        />
                    </div>
                    <div className="topSegEqual">
                        <SearchField currentSelectedMenuItem={this.props.currentSelectedMenuItem} setSearchActive={this.props.setSearchActive} searchContents={this.props.searchContents} setSearchContents={this.props.setSearchContents} />
                    </div>
                </div>
                <div id="topMenuSelectedDisplay">
                    { this.props.searchTip !== '' ? this.props.searchTip : getDisplayName(this.props.currentSelectedMenuItem) }
                </div>
            </div>
        );
    }
}

TopMenu.propTypes = {
    setSelectedMenuItem: PropTypes.func.isRequired,
    editEnabled: PropTypes.bool.isRequired,
    deleteEnabled: PropTypes.bool.isRequired,
    performDelete: PropTypes.func.isRequired,
    performAdd: PropTypes.func.isRequired,
    performEdit: PropTypes.func.isRequired,
    performDeSelectAll: PropTypes.func.isRequired,
    currentSelectedMenuItem: PropTypes.string.isRequired,
    selectionToggleButtonIsSelectAll: PropTypes.bool.isRequired,
    setSearchActive: PropTypes.func.isRequired,
    searchContents: PropTypes.string.isRequired,
    setSearchContents: PropTypes.func.isRequired,
    searchTip: PropTypes.string.isRequired,
};
<file_sep>export default function cleanseInput(x) {
    if (x.length === 0) {
        return x;
    }
    let rturn = x;
    if (rturn[0] === ' ') {
        rturn = rturn.substring(1);
    }
    if (rturn.length === 0) {
        return rturn;
    }
    if (rturn[rturn.length - 1] === ' ') {
        rturn = rturn.substring(0, rturn.length - 1);
    }
    return rturn;
}
<file_sep>import React from 'react';
import PropTypes from 'prop-types';
import { getIcon } from '../utils';
import './pager.css';

export default class Pager extends React.Component {
    constructor(props) {
        super(props);
        this.state={
            textFieldPage: this.props.currentPage,
        };
    }

    componentDidUpdate(prevProps) {
        if (prevProps.currentPage !== this.props.currentPage) {
            // eslint-disable-next-line react/no-did-update-set-state
            this.setState({
                textFieldPage: this.props.currentPage,
            });
        }
    }

    render() {
        if (this.props.pages === 0) {
            return (<div />);
        }
        return (
            <div id="pager-master">
                <div id="pager-currentPageDisplay">
                    <span>Page</span>
                    <input
                        id="pager-input"
                        min="1"
                        max={this.props.pages}
                        value={this.state.textFieldPage}
                        onChange={(val) => {
                            this.setState({
                                textFieldPage: val.target.value,
                            });
                        }}
                        onKeyDown={(event) => {
                            if (event.key!=='Enter') {
                                return;
                            }
                            let val=this.state.textFieldPage;
                            if (isNaN(val)) {
                                this.setState({
                                    textFieldPage: this.props.currentPage,
                                });
                                return;
                            }
                            let num=Number.parseInt(val, 10);
                            if (num>=1 && num<=this.props.pages) {
                                this.props.setPage(num);
                            } else {
                                this.setState({
                                    textFieldPage: this.props.currentPage,
                                });
                            }
                        }}
                        onBlur={() => {
                            this.setState({
                                textFieldPage: this.props.currentPage,
                            });
                        }}
                    />
                    <span>{`of ${this.props.pages}`}</span>
                </div>
                <div id="pager-adjust">
                    <div
                        className={`pager-adjust-buttons ${this.props.currentPage>1 ? 'pager-adjust-buttons-enabled' : 'pager-adjust-buttons-disabled'}`}
                        onClick={() => {
                            if (this.props.currentPage>1) {
                                this.props.setPage(this.props.currentPage-1);
                            }
                        }}
                    >
                        <img className={`pager-adjust-icon${this.props.currentPage>1 ? '' : ' pager-adjust-icon-disabled'}`} alt="Last Page" src={getIcon("LeftArrow")} />
                    </div>
                    <div
                        className={`pager-adjust-buttons ${this.props.currentPage<this.props.pages ? 'pager-adjust-buttons-enabled' : 'pager-adjust-buttons-disabled'}`}
                        onClick={() => {
                            if (this.props.currentPage<this.props.pages) {
                                this.props.setPage(this.props.currentPage+1);
                            }
                        }}
                    >
                        <img className={`pager-adjust-icon${this.props.currentPage<this.props.pages ? '' : ' pager-adjust-icon-disabled'}`} alt="Next Page" src={getIcon("RightArrow")} />
                    </div>
                </div>
            </div>
        );
    }
}

Pager.propTypes = {
    currentPage: PropTypes.number.isRequired,
    pages: PropTypes.number.isRequired,
    setPage: PropTypes.func.isRequired,
};
<file_sep>import ComponentIcon from './icons/components.svg';
import FailureIcon from './icons/failures.svg';
import ModeIcon from './icons/modes.svg';
import SearchIcon from './icons/magnifying.svg';
import PlusIcon from './icons/plus.svg';
import ArrowUp from './icons/arrowup.svg';
import ArrowDown from './icons/arrowdown.svg';
import Delete from './icons/delete.svg';
import Edit from './icons/edit.svg';
import Checkmark from './icons/checkmark.svg';
import Xmark from './icons/xmark.svg';
import LeftArrow from './icons/arrowleft.svg';
import RightArrow from './icons/arrowright.svg';

import ComponentIconDark from './icons/components-dark.svg';
import FailureIconDark from './icons/failures-dark.svg';
import ModeIconDark from './icons/modes-dark.svg';
import SearchIconDark from './icons/magnifying-dark.svg';
import PlusIconDark from './icons/plus-dark.svg';
import ArrowUpDark from './icons/arrowup-dark.svg';
import ArrowDownDark from './icons/arrowdown-dark.svg';
import DeleteDark from './icons/delete-dark.svg';
import EditDark from './icons/edit-dark.svg';
import CheckmarkDark from './icons/checkmark-dark.svg';
import XmarkDark from './icons/xmark-dark.svg';
import LeftArrowDark from './icons/arrowleft-dark.svg';
import RightArrowDark from './icons/arrowright-dark.svg';

export function getDisplayName(menuItem) {
    switch (menuItem) {
    case 'components':
        return 'Components';
    case 'failures':
        return 'Mapping';
    case 'modes':
        return 'Fail Modes';
    default:
        console.log(`Get display name called on unrecognized menu item ${menuItem}`);
    }
    return undefined;
}

export function getSingularDisplayName(menuItem) {
    switch (menuItem) {
    case 'components':
        return 'Component';
    case 'failures':
        return 'Mapping';
    case 'modes':
        return 'Fail Mode';
    default:
        console.log(`Get singular display name called on unrecognized menu item ${menuItem}`);
    }
    return undefined;
}

export function getIcon(name) {
    const isDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
    switch (name) {
    case 'components':
        if (isDarkTheme) {
            return ComponentIconDark;
        }
        return ComponentIcon;
    case 'failures':
        if (isDarkTheme) {
            return FailureIconDark;
        }
        return FailureIcon;
    case 'modes':
        if (isDarkTheme) {
            return ModeIconDark;
        }
        return ModeIcon;
    case 'search':
        if (isDarkTheme) {
            return SearchIconDark;
        }
        return SearchIcon;
    case 'plus':
        if (isDarkTheme) {
            return PlusIconDark;
        }
        return PlusIcon;
    case 'UpArrow':
        if (isDarkTheme) {
            return ArrowUpDark;
        }
        return ArrowUp;
    case 'DownArrow':
        if (isDarkTheme) {
            return ArrowDownDark;
        }
        return ArrowDown;
    case 'delete':
        if (isDarkTheme) {
            return DeleteDark;
        }
        return Delete;
    case 'edit':
        if (isDarkTheme) {
            return EditDark;
        }
        return Edit;
    case 'checkmark':
        if (isDarkTheme) {
            return CheckmarkDark;
        }
        return Checkmark;
    case 'xmark':
        if (isDarkTheme) {
            return XmarkDark;
        }
        return Xmark;
    case 'LeftArrow':
        if (isDarkTheme) {
            return LeftArrowDark;
        }
        return LeftArrow;
    case 'RightArrow':
        if (isDarkTheme) {
            return RightArrowDark;
        }
        return RightArrow;
    case '':
        return undefined;
    default:
        console.log(`Requested icon ${name} not found.`);
    }
    return undefined;
}
<file_sep>export default function dataSearch(rawData, column, key) {
    if (rawData.length===0) {
        return [];
    }
    let curPrototypeIds = rawData[0].constructor.getIds;
    let searchIndex = -1;
    for (let i=0;i<curPrototypeIds.length;i++) {
        if (curPrototypeIds[i] === column) {
            searchIndex = i;
        }
    }
    if (searchIndex === -1) {
        console.log(`Search error: Couldn't find search column ${column}`);
        return [];
    }
    let searchResult=[];
    for (let i=0;i<rawData.length;i++) {
        const tryMatch = String(rawData[i].getData()[searchIndex]);
        if (tryMatch.indexOf(key) !== -1) {
            searchResult.push({
                index: i,
                data: rawData[i],
            });
        }
    }
    return searchResult;
}
<file_sep>import './App.css';
import './Table/Table.css';
import React from 'react';
import serverRequestDelete from './services/serverRequestDelete';
import getPrototype from './GetPrototypes';
import ComponentsData from './DataStructs/ComponentsData';
import ModesData from './DataStructs/ModesData';
import FailsData from './DataStructs/FailsData';
import MainTable from './Table/Table';

import TopMenu from './TopMenu/TopMenu';
import httpCall from './services/httpCall';
import DeleteModal from './Modals/DeleteModal/deleteModal';
import AddEditModal from './Modals/AddEditModal/addEditModal';
import { getDisplayName, getSingularDisplayName } from './utils';
import serverRequestAdd from './services/serverRequestAdd';
import serverRequestUpdate from './services/serverRequestUpdate';
import * as globals from './globals';
import cleanseInput from './DataStructs/cleanseInput';
import dataSearch from './dataSearch';
import rootTypeToState from './DataStructs/rootTypeToState';
import getCorrespondingDisplayNameOfColumnId from './DataStructs/getCorrespondingDisplayNameOfColumnId';
import autocompleteSearch from './autocompleteSearch';
import Pager from './Pager/pager';

class App extends React.Component {
    async fetchComponents() {
        const callUrl=`${globals.rootURL}/components/fetchAll`;
        const rawData=this.rawComponentsData;
        const relevantSelections=this.componentSelections;
        const relevantSelectionCache=this.componentsSelectedCache;
        await httpCall(callUrl).then((reqsReturn) => {
            relevantSelectionCache.length=0;
            const gottenData = reqsReturn[0];
            let newRawData = Array(gottenData.length);
            for (let i = 0; i < gottenData.length; i++) {
                const current = gottenData[i];
                newRawData[i] = new ComponentsData(current.pkid,
                    current.productname,
                    current.manufacturer,
                    current.contact,
                    current.failrate);
            }

            this.componentSelections=this.realignSelections(rawData, gottenData, relevantSelectionCache, relevantSelections, newRawData);
            this.rawComponentsData=newRawData;
        });
    }

    async fetchModes() {
        const callUrl=`${globals.rootURL}/fail_mode/fetchAll`;
        const rawData=this.rawModesData;
        const relevantSelections=this.modesSelections;
        const relevantSelectionCache=this.modesSelectedCache;
        await httpCall(callUrl).then((reqsReturn) => {
            relevantSelectionCache.length=0;
            const gottenData = reqsReturn[0];
            let newRawData = Array(gottenData.length);
            for (let i = 0; i < gottenData.length; i++) {
                const current = gottenData[i];
                newRawData[i] = new ModesData(current.pkid,
                    current.failname,
                    current.code,
                    current.description);
            }

            this.modesSelections=this.realignSelections(rawData, gottenData, relevantSelectionCache, relevantSelections, newRawData);
            this.rawModesData=newRawData;
        });
    }

    // eslint-disable-next-line class-methods-use-this
    realignSelections(rawData, gottenData, relevantSelectionCache, relevantSelections, newRawData) {
        let pkidToOldIndexMap = new Map();
        for (let i=0;i<rawData.length;i++) {
            pkidToOldIndexMap.set(rawData[i].dbid, i);
        }
        let newSelectionData=Array(gottenData.length);
        for (let i=0;i<newRawData.length;i++) {
            let oldIndex=pkidToOldIndexMap.get(newRawData[i].dbid);
            if (oldIndex===undefined) {
                newSelectionData[i]=false;
            } else {
                newSelectionData[i]=relevantSelections[oldIndex];
                if (newSelectionData[i]) {
                    relevantSelectionCache.push(i);
                }
            }
        }
        return newSelectionData;
    }

    async fetchFailures() {
        const callUrl=`${globals.rootURL}/mapping/fetchAll`;
        const rawData=this.rawFailsData;
        const relevantSelections=this.failuresSelections;
        const relevantSelectionCache=this.failuresSelectedCache;
        await httpCall(callUrl).then((reqsReturn) => {
            relevantSelectionCache.length=0;
            const gottenData = reqsReturn[0];
            let newRawData = Array(gottenData.length);
            for (let i = 0; i < gottenData.length; i++) {
                const current = gottenData[i];
                newRawData[i] = new FailsData(current.pkid,
                    current.failComponentId,
                    current.failComponentName,
                    current.failModeId,
                    current.failModeName,
                    current.failCode,
                    current.failDescription);
            }

            this.failuresSelections=this.realignSelections(rawData, gottenData, relevantSelectionCache, relevantSelections, newRawData);
            this.rawFailsData=newRawData;
        });
    }

    constructor(props) {
        super(props);
        this.selectionAnchor = -1;
        this.selectionLastAction = '';
        this.componentsSelectedCache = []; // selection cache is in database sorting
        this.modesSelectedCache = [];
        this.failuresSelectedCache = [];
        this.currentSortToDbMapping = [];
        this.currentDbToSortMapping = [];
        this.rawComponentsData = [];
        this.rawModesData = [];
        this.rawFailsData = [];
        this.state = {
            currentSelectedMenuItem: 'failures',
            sortedColumn: {
                components: 'num',
                failures: 'num',
                modes: 'num',
            },
            sortMethodAscending: {
                components: true,
                failures: true,
                modes: true,
            },
            pages: {
                components: 0,
                failures: 0,
                modes: 0,
            },
            numberOfPages: 0,
            displayLRange: 0,
            displayRRange: 0,
            displayData: [],
            deleteModalShown: false,
            deleteModalText: '',
            addModalShown: false,
            dataLength: -1,
            isInSearch: false,
            searchColumn: {
                components: 'productname',
                failures: 'failedcomponent',
                modes: 'name',
            },
            searchContents: "",
            editModalShown: false,
            additionalEditInfo: {},
        };
    }

    componentDidMount() {
        this.fetchFailures().then(() => {
            this.recomputeData('failures', 'num', true);
        });
    }

    changeCurrentSelectedMenuItem(val) {
        this.recomputeData(val);
        this.setState({
            currentSelectedMenuItem: val,
            searchContents: "",
        });
        switch (val) {
        case "components":
            this.fetchComponents()
                .then(() => {
                    this.recomputeData('components', this.state.sortedColumn.components, this.state.sortMethodAscending.components, "", false, this.state.searchColumn.components);
                });
            break;
        case "failures":
            this.fetchFailures()
                .then(() => {
                    this.recomputeData('failures', this.state.sortedColumn.failures, this.state.sortMethodAscending.failures, "", false, this.state.searchColumn.failures);
                });
            break;
        case "modes":
            this.fetchModes()
                .then(() => {
                    this.recomputeData('modes', this.state.sortedColumn.modes, this.state.sortMethodAscending.modes, "", false, this.state.searchColumn.modes);
                });
            break;
        default:
            console.log(`Invalid menu item change ${val}`);
        }
    }

    getRawData(selectedState = this.state.currentSelectedMenuItem) {
        switch (selectedState) {
        case 'components':
            return this.rawComponentsData;
        case 'failures':
            return this.rawFailsData;
        case 'modes':
            return this.rawModesData;
        default:
            console.log(`Get raw data called on invalid state ${selectedState}.`);
            return undefined;
        }
    }

    recomputeData(selectedState = this.state.currentSelectedMenuItem, sortedColumn = this.state.sortedColumn[selectedState], sortMethodAscending = this.state.sortMethodAscending[selectedState], searchContents = this.state.searchContents, isInSearch = this.state.isInSearch, searchColumn = this.state.searchColumn[this.state.currentSelectedMenuItem], currentPage = null) {
        this.selectionAnchor = -1;
        this.selectionLastAction = '';
        console.log('Recomputing display data');
        let accompanyingSelectionData = [];
        let accompanyingLRange;
        let accompanyingRRange;
        switch (selectedState) {
        case 'components':
            accompanyingSelectionData = this.componentSelections;
            accompanyingLRange=this.state.pages.components;
            break;
        case 'failures':
            accompanyingSelectionData = this.failuresSelections;
            accompanyingLRange=this.state.pages.failures;
            break;
        case 'modes':
            accompanyingSelectionData = this.modesSelections;
            accompanyingLRange=this.state.pages.modes;
            break;
        default:
            console.log(`Unknown selected state (${selectedState})`);
        }
        if (currentPage !== null) {
            accompanyingLRange=currentPage;
        }
        let displayData = this.getRawData(selectedState).slice();
        let totalPages = Math.floor(displayData.length / globals.eachPage);
        accompanyingLRange = Math.min(accompanyingLRange, totalPages);
        accompanyingLRange*=globals.eachPage;
        accompanyingRRange=Math.min(accompanyingLRange+globals.eachPage, displayData.length);
        for (let i = 0; i < displayData.length; i++) {
            displayData[i].num = i + 1;
            displayData[i].selected = accompanyingSelectionData[i];
        }
        if (isInSearch) {
            displayData=dataSearch(displayData, searchColumn, searchContents);
            for (let i=0;i<displayData.length;i++) {
                displayData[i]=displayData[i].data;
            }
        }
        this.currentSortToDbMapping = Array(displayData.length);
        this.currentDbToSortMapping = Array(displayData.length);
        if (displayData.length > 0) {
            if (sortedColumn === 'num') {
                if (!sortMethodAscending) {
                    displayData.reverse();
                }
            } else {
                const elementsDescription = displayData[0].constructor.getIds;
                const elementsType = displayData[0].constructor.types;
                let sortBy = -1;
                for (let i = 0; i < elementsDescription.length; i++) {
                    if (elementsDescription[i] === sortedColumn) {
                        sortBy = i;
                    }
                }
                if (sortBy === -1) {
                    console.log(`Cannot find sort column ID ${sortedColumn}`);
                    return displayData;
                }
                const floatSort = elementsType[sortBy] === 'number';
                displayData.sort((a, b) => {
                    const aData = a.getData();
                    const bData = b.getData();
                    let aComp = aData[sortBy];
                    let bComp = bData[sortBy];
                    if (floatSort) {
                        aComp = parseFloat(aComp);
                        bComp = parseFloat(bComp);
                    }
                    if (sortMethodAscending) {
                        return (aComp > bComp ? 1 : -1);
                    }
                    return (aComp < bComp ? 1 : -1);
                });
            }
            for (let i = 0; i < displayData.length; i++) {
                this.currentSortToDbMapping[i] = displayData[i].num - 1;
            }
            for (let i = 0; i < displayData.length; i++) {
                this.currentDbToSortMapping[this.currentSortToDbMapping[i]] = i;
            }
        }
        this.setState({
            displayData: displayData,
            dataLength: displayData.length,
            displayLRange: accompanyingLRange,
            displayRRange: accompanyingRRange,
            numberOfPages: displayData.length === 0 ? 0 : Math.floor(displayData.length/globals.eachPage)+1,
        });
        return null;
    }

    getRelSelectionArrayAndCache(currentMenuState = this.state.currentSelectedMenuItem) {
        let relSelectionArray;
        let relSelectionCache;
        switch (currentMenuState) {
        case 'components':
            relSelectionArray = this.componentSelections;
            relSelectionCache = this.componentsSelectedCache;
            break;
        case 'failures':
            relSelectionArray = this.failuresSelections;
            relSelectionCache = this.failuresSelectedCache;
            break;
        case 'modes':
            relSelectionArray = this.modesSelections;
            relSelectionCache = this.modesSelectedCache;
            break;
        default:
            console.log(`Getting relevant selection and cache for unknown table type ${currentMenuState}`);
        }
        return [relSelectionArray, relSelectionCache];
    }

    handleDeSelectAllButtonPressed() {
        const shouldSelectAll = this.selectionToggleButtonIsSelectAll();
        const [relSelectionArray, relSelectionCache] = this.getRelSelectionArrayAndCache();
        // clear the cache
        relSelectionCache.length = 0;
        for (let i = 0; i < this.state.displayData.length; i++) {
            relSelectionArray[this.currentSortToDbMapping[i]] = shouldSelectAll;
        }
        for (let i=0;i<relSelectionArray.length;i++) {
            if (relSelectionArray[i]) {
                relSelectionCache.push(i);
            }
        }
        const newDisplayData = this.state.displayData.slice();
        for (let i = 0; i < newDisplayData.length; i++) {
            newDisplayData[i].selected = shouldSelectAll;
        }
        this.setState({
            displayData: newDisplayData,
        });
    }

    processClick(tableRow, action) {
    // single select -> deselect everything and only select one. set this to the anchor.
    // multi select -> toggle the selection on that one. if it is now selected, set it to the anchor.
    // if it isn't selected. set the anchor to no anchor.
    // range select -> look at the anchor. if there is no anchor, simply treat this as a multi-select (but without setting another anchor)
    // if there is an anchor, use it and LEAVE IT THERE.
    // if the last action was a multi-select, completely erase its selection and do the selection.
    // remember to set the anchor to none every time the table is resorted

        // required data for each state: anchor, lastAction
        // IMPORTANT: Anchor is in terms of current coordinates
        // eslint-disable-next-line prefer-const
        let [relSelectionArray, relSelectionCache] = this.getRelSelectionArrayAndCache(tableRow.tableType);
        const selectIndex = tableRow.tableIndex;
        // eslint-disable-next-line react/no-access-state-in-setstate
        const newDisplayData = this.state.displayData.slice();

        if (action === 'single') {
            for (let i = 0; i < relSelectionCache.length; i++) {
                relSelectionArray[relSelectionCache[i]] = false;
            }
            const dbIndex = this.currentSortToDbMapping[selectIndex];
            relSelectionArray[dbIndex] = true;
            for (let i = 0; i < relSelectionCache.length; i++) {
                if (this.currentDbToSortMapping[relSelectionCache[i]] !== undefined) {
                    newDisplayData[this.currentDbToSortMapping[relSelectionCache[i]]].selected = false;
                }
            }
            newDisplayData[selectIndex].selected = true;
            relSelectionCache = [dbIndex];
            this.selectionLastAction = {
                action: 'single',
                params: selectIndex,
            };
            this.selectionAnchor = selectIndex;
        } else if (action === 'multi' || (action === 'range' && this.selectionAnchor === -1)) {
            const dbIndex = this.currentSortToDbMapping[selectIndex];
            if (relSelectionArray[dbIndex]) {
                // remove the selection and set anchor to none
                relSelectionArray[dbIndex] = false;
                newDisplayData[selectIndex].selected = false;
                for (let i = 0; i < relSelectionCache.length; i++) {
                    if (relSelectionCache[i] === dbIndex) {
                        relSelectionCache.splice(i, 1);
                    }
                }
                this.selectionAnchor = -1;
            } else {
                // add the selection and set anchor to the selection
                relSelectionArray[dbIndex] = true;
                newDisplayData[selectIndex].selected = true;
                relSelectionCache.push(dbIndex);
                if (action !== 'range') {
                    this.selectionAnchor = selectIndex;
                }
            }
            if (action === 'range') {
                this.selectionLastAction = {
                    action: 'range_multi_fallback',
                    params: selectIndex,
                };
            } else {
                this.selectionLastAction = {
                    action: 'multi',
                    params: selectIndex,
                };
            }
        } else if (action === 'range') {
            if (this.selectionLastAction.action === 'range') {
                const eraseL = Math.min(this.selectionAnchor, this.selectionLastAction.params);
                const eraseR = Math.max(this.selectionAnchor, this.selectionLastAction.params);
                for (let i = eraseL; i <= eraseR; i++) {
                    relSelectionArray[this.currentSortToDbMapping[i]] = false;
                    newDisplayData[i].selected = false;
                }
                for (let i = 0; i < relSelectionCache.length; i++) {
                    if (this.currentDbToSortMapping[relSelectionCache[i]] >= eraseL
                        && this.currentDbToSortMapping[relSelectionCache[i]] <= eraseR) {
                        relSelectionCache.splice(i, 1);
                        i--;
                    }
                }
            }
            const l = Math.min(selectIndex, this.selectionAnchor);
            const r = Math.max(selectIndex, this.selectionAnchor);
            for (let i = l; i <= r; i++) {
                if (!relSelectionArray[this.currentSortToDbMapping[i]]) {
                    relSelectionCache.push(this.currentSortToDbMapping[i]);
                }
                relSelectionArray[this.currentSortToDbMapping[i]] = true;
                newDisplayData[i].selected = true;
            }
            this.selectionLastAction = {
                action: 'range',
                params: selectIndex,
            };
        }
        switch (tableRow.tableType) {
        case 'components':
            this.componentSelections = relSelectionArray;
            this.componentsSelectedCache = relSelectionCache;
            break;
        case 'failures':
            this.failuresSelections = relSelectionArray;
            this.failuresSelectedCache = relSelectionCache;
            break;
        case 'modes':
            this.modesSelections = relSelectionArray;
            this.modesSelectedCache = relSelectionCache;
            break;
        default:
            console.log(`Processing click for unknown table type ${tableRow.tableType}`);
        }
        this.setState({
            displayData: newDisplayData,
        });
    }

    async getSelectedDetails() {
    // gets details about the current selection for deletion.
    // selectionCache: a list of indexes are selected in the current selected page
    // rawData: just the raw list of data of the current selected page
    // selectionRawData: an array of booleans that provides the source of truth for whether or not every element is selected on the page.
    // relatedFailureDeletions: the indexes of the dependencies in Failures that would be deleted
        const currentState = this.state.currentSelectedMenuItem;
        let selectionCache; let rawData; let selectionRawData;
        const relatedFailureDeletions = [];
        switch (currentState) {
        case 'components':
            selectionCache = this.componentsSelectedCache;
            rawData = this.rawComponentsData;
            selectionRawData = this.componentSelections;
            break;
        case 'failures':
            selectionCache = this.failuresSelectedCache;
            rawData = this.rawFailsData;
            selectionRawData = this.failuresSelections;
            break;
        case 'modes':
            selectionCache = this.modesSelectedCache;
            rawData = this.rawModesData;
            selectionRawData = this.modesSelections;
            break;
        default:
            console.log(`Delete selection called on invalid menu item ${this.state.currentSelectedMenuItem}`);
        }
        if (currentState !== 'failures') {
            await this.fetchFailures();
            const dbidGettingDeletedMap = {};
            for (let i = 0; i < selectionCache.length; i++) {
                dbidGettingDeletedMap[rawData[selectionCache[i]].dbid] = true;
            }
            for (let i = 0; i < this.rawFailsData.length; i++) {
                if (currentState === 'components') {
                    if (dbidGettingDeletedMap[this.rawFailsData[i].failComponentId] === true) {
                        relatedFailureDeletions.push(i);
                    }
                } else if (dbidGettingDeletedMap[this.rawFailsData[i].failModeId] === true) {
                    relatedFailureDeletions.push(i);
                }
            }
        }
        return [selectionCache, rawData, selectionRawData, relatedFailureDeletions];
    }

    async deleteSelection() {
    // delete the current selection, taking into consideration that dependencies (such as failures) also have to be deleted.
        const deleteSelection = [];
        const selectionDetails = await this.getSelectedDetails();
        const selectionCache = selectionDetails[0]; const rawData = selectionDetails[1]; const selectionRawData = selectionDetails[2]; const
            relatedFailureDeletions = selectionDetails[3];
        if (selectionCache.length > 0) {
            for (let i = 0; i < selectionCache.length; i++) {
                deleteSelection.push(rawData[selectionCache[i]].dbid);
            }
            // send list of deletions to server and wait for server response
            await serverRequestDelete(deleteSelection, this.state.currentSelectedMenuItem);
            // delete the elements from the local copy according to the cache, which holds a list of items which are selected.
            selectionCache.sort();
            for (let i = selectionCache.length - 1; i >= 0; i--) {
                rawData.splice(selectionCache[i], 1);
                selectionRawData.splice(selectionCache[i], 1);
            }
            // clear the cache
            selectionCache.length = 0;
            if (relatedFailureDeletions.length > 0) {
                // erase related deletions
                relatedFailureDeletions.sort();
                // remove all the related deletions from failures
                // again, go through the list of things that have to be deleted and delete them.
                for (let i = relatedFailureDeletions.length - 1; i >= 0; i--) {
                    this.rawFailsData.splice(relatedFailureDeletions[i], 1);
                    this.failuresSelections.splice(relatedFailureDeletions[i], 1);
                }
                // now remove these things from the cache.
                this.failuresSelectedCache.sort();
                let currentRelatedFailureDeletionsAt = relatedFailureDeletions.length - 1;
                for (let i = this.failuresSelectedCache.length - 1; i >= 0; i--) {
                    if (currentRelatedFailureDeletionsAt !== -1) {
                        if (this.failuresSelectedCache[i] === relatedFailureDeletions[currentRelatedFailureDeletionsAt]) {
                            this.rawFailsData.splice(i, 1);
                            this.failuresSelections.splice(i, 1);
                            currentRelatedFailureDeletionsAt--;
                        }
                    }
                }
            }
            this.recomputeData();
            this.selectionAnchor=-1;
        }
    }

    async confirmAndDelete() {
    // generate a confirmation message and show the modal
        let nextConfirmMessage;
        const selectionDetails = await this.getSelectedDetails();
        const selectionCache = selectionDetails[0];
        const willDelete = selectionDetails[3].length;
        if (selectionCache.length === 0) {
            return;
        }
        const currentState = this.state.currentSelectedMenuItem;
        if (currentState === 'failures') {
            nextConfirmMessage = `${selectionCache.length} ${getSingularDisplayName('failures')}${selectionCache.length > 1 ? 's' : ''} will be deleted permanently`;
            this.setState({
                deleteModalShown: true,
                deleteModalText: nextConfirmMessage,
            });
        } else {
            nextConfirmMessage = `${selectionCache.length} ${getSingularDisplayName(currentState === 'components' ? 'components' : 'modes')}${selectionCache.length > 1 ? 's' : ''}`;
            if (willDelete > 0) nextConfirmMessage += ` and ${willDelete} related ${getSingularDisplayName('failures')}${willDelete > 1 ? 's' : ''}`;
            nextConfirmMessage += ' will be deleted permanently';
            this.setState({
                deleteModalShown: true,
                deleteModalText: nextConfirmMessage,
            });
        }
    }

    getCurrentNumberOfSelections() {
    // get the number of items currently selected
        switch (this.state.currentSelectedMenuItem) {
        case 'components':
            return this.componentsSelectedCache.length;
        case 'failures':
            return this.failuresSelectedCache.length;
        case 'modes':
            return this.modesSelectedCache.length;
        default:
            console.log(`Getting current nuumber ot selections called on invalid menu item ${this.state.currentSelectedMenuItem}`);
        }
        return undefined;
    }

    checkDeleteEnabled() {
    // determines whether or not the delete button is greyed out.
        return this.getCurrentNumberOfSelections() > 0;
    }

    checkEditEnabled() {
    // determines whether or not the edit button is greyed out
        return this.getCurrentNumberOfSelections() === 1;
    }

    selectionToggleButtonIsSelectAll() {
        // determines whether or not the selection button is select all
        if (this.state.dataLength === 0) {
            return true;
        }
        return this.getCurrentNumberOfSelections() !== this.state.dataLength;
    }

    generateNewObject(currentState = this.state.currentSelectedMenuItem, args) {
        const curPrototype = getPrototype(currentState).constructor;
        const isRootType = curPrototype.rootDescribe !== undefined;
        const identifiers = isRootType ? curPrototype.rootTypes : curPrototype.getIds;
        const serverObj = {};
        if (args.length === identifiers.length) {
            for (let i = 0; i < identifiers.length; i++) {
                serverObj[identifiers[i]] = isRootType ? this.fetchInfoByIndex(rootTypeToState(curPrototype.rootTypes[i]), args[i]).dbid : cleanseInput(args[i]);
            }
        } else {
            console.log(`Generate new object: argument length mismatch`);
            return {};
        }

        let dataStructObj;
        let linkedComponentIndex=-1;
        let linkedModeIndex=-1;
        switch (currentState) {
        case 'components':
            dataStructObj=new ComponentsData(-1, serverObj.productname, serverObj.manufacturer, serverObj.contact, serverObj.failrate);
            break;
        case 'failures':
            for (let i=0;i<this.rawComponentsData.length;i++) {
                if (this.rawComponentsData[i].dbid===serverObj.component_pkid) {
                    linkedComponentIndex=i;
                }
            }
            for (let i=0;i<this.rawModesData.length;i++) {
                if (this.rawModesData[i].dbid===serverObj.mode_pkid) {
                    linkedModeIndex=i;
                }
            }
            if (linkedComponentIndex===-1||linkedModeIndex===-1) {
                console.log(`Generate new object: linked component or linked mode not found.`);
                return {};
            }
            dataStructObj=new FailsData(-1, serverObj.component_pkid, this.rawComponentsData[linkedComponentIndex].productname, serverObj.mode_pkid, this.rawModesData[linkedModeIndex].failname, this.rawModesData[linkedModeIndex].code, this.rawModesData[linkedModeIndex].description);
            break;
        case 'modes':
            dataStructObj=new ModesData(-1, serverObj.name, serverObj.code, serverObj.description);
            break;
        default:
            console.log(`Creating object called on invalid menu item ${currentState}`);
        }
        return { serverObj, dataStructObj };
    }

    createAndAddObject(dataStructObj, currentState) {
        const [relSelectionArray, relSelectionCache] = this.getRelSelectionArrayAndCache(currentState);
        switch (currentState) {
        case 'components':
            this.rawComponentsData.unshift(dataStructObj);
            break;
        case 'failures':
            this.rawFailsData.unshift(dataStructObj);
            break;
        case 'modes':
            this.rawModesData.unshift(dataStructObj);
            break;
        default:
            console.log(`Creating object called on invalid menu item ${currentState}`);
        }
        relSelectionArray.unshift(false);
        for (let i=0;i<relSelectionCache.length;i++) {
            relSelectionCache[i]++;
        }
        this.selectionAnchor=-1;
        this.recomputeData();
    }

    updateObject(dataStructObj, index, currentState) {
        switch (currentState) {
        case 'components':
            this.rawComponentsData[index]=dataStructObj;
            for (let i=0;i<this.rawFailsData.length;i++) {
                if (this.rawFailsData[i].failComponentId === this.rawComponentsData[index].dbid) {
                    this.rawFailsData[i].failComponentName = this.rawComponentsData[index].productname;
                }
            }
            break;
        case 'failures':
            this.rawFailsData[index]=dataStructObj;
            break;
        case 'modes':
            this.rawModesData[index]=dataStructObj;
            for (let i=0;i<this.rawFailsData.length;i++) {
                if (this.rawFailsData[i].failModeId === this.rawModesData[index].dbid) {
                    this.rawFailsData[i].failModeName = this.rawModesData[index].failname;
                    this.rawFailsData[i].failCode = this.rawModesData[index].code;
                    this.rawFailsData[i].failDescription = this.rawModesData[index].description;
                }
            }
            break;
        default:
            console.log(`Updating object called on invalid menu item ${currentState}`);
        }
        this.recomputeData();
    }

    fetchInfoByIndex(state, index) {
        switch (state) {
        case 'components':
            return this.rawComponentsData[index];
        case 'failures':
            return this.rawFailsData[index];
        case 'modes':
            return this.rawModesData[index];
        default:
            console.log(`Fetch info on index called on invalid menu item ${state}`);
        }
        return null;
    }

    async fetchRootDataByDbid(type, dbid, performFetch) {
        let match = -1;
        let rawMatchData=(type==='component_pkid' ? this.rawComponentsData : this.rawModesData);
        for (let j=0;j<rawMatchData.length;j++) {
            if (rawMatchData[j].dbid === dbid) {
                match=j;
                break;
            }
        }
        let didGoFetch = false;
        if (match === -1 && performFetch) {
            didGoFetch=true;
            if (type==='component_pkid') {
                await this.fetchComponents();
                [match] = await this.fetchRootDataByDbid(type, dbid, false);
            } else {
                await this.fetchModes();
                [match] = await this.fetchRootDataByDbid(type, dbid, false);
            }
        }
        return [match, didGoFetch];
    }

    render() {
        return (
            <div id="masterContainer" className={window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark-theme' : ''}>
                <DeleteModal
                    show={this.state.deleteModalShown}
                    text={this.state.deleteModalText}
                    deleteClicked={() => {
                        this.deleteSelection();
                        this.setState({
                            deleteModalShown: false,
                        });
                    }}
                    cancelClicked={() => {
                        this.setState({
                            deleteModalShown: false,
                        });
                    }}
                />
                <AddEditModal
                    modalType="add"
                    show={this.state.addModalShown}
                    submitClicked={(args) => {
                        let { serverObj, dataStructObj } = this.generateNewObject(this.state.currentSelectedMenuItem, args);
                        if (serverObj === undefined || dataStructObj === undefined) {
                            return;
                        }
                        serverRequestAdd(serverObj, this.state.currentSelectedMenuItem).then((res) => {
                            // eslint-disable-next-line prefer-destructuring
                            dataStructObj.dbid=res.data[0];
                            this.createAndAddObject(dataStructObj, this.state.currentSelectedMenuItem);
                        });
                        this.setState({
                            addModalShown: false,
                        });
                    }}
                    cancelClicked={() => {
                        this.setState({
                            addModalShown: false,
                        });
                    }}
                    addItemDisplayTitle={getSingularDisplayName(this.state.currentSelectedMenuItem)}
                    objectPrototype={getPrototype(this.state.currentSelectedMenuItem)}
                    autocompleteSearch={autocompleteSearch.bind(this)}
                    fetchInfoByIndex={this.fetchInfoByIndex.bind(this)}
                    additionalEditInfo={{}}
                />
                <AddEditModal
                    modalType="edit"
                    show={this.state.editModalShown}
                    submitClicked={(args) => {
                        let { serverObj, dataStructObj }= this.generateNewObject(this.state.currentSelectedMenuItem, args);
                        if (serverObj === undefined || dataStructObj === undefined) {
                            return;
                        }
                        let { index } = this.state.additionalEditInfo;
                        switch (this.state.currentSelectedMenuItem) {
                        case "components":
                            serverObj.pkid=this.rawComponentsData[index].dbid;
                            dataStructObj.dbid=this.rawComponentsData[index].dbid;
                            break;
                        case "failures":
                            serverObj.pkid=this.rawFailsData[index].dbid;
                            dataStructObj.dbid=this.rawFailsData[index].dbid;
                            break;
                        case "modes":
                            serverObj.pkid=this.rawModesData[index].dbid;
                            dataStructObj.dbid=this.rawModesData[index].dbid;
                            break;
                        default:
                            console.log(`Tried to assign pkid with invalid state ${this.state.currentSelectedMenuItem}`);
                        }
                        serverRequestUpdate(serverObj, this.state.currentSelectedMenuItem).then(() => {
                            this.updateObject(dataStructObj, index, this.state.currentSelectedMenuItem);
                        });
                        this.setState({
                            editModalShown: false,
                        });
                    }}
                    cancelClicked={() => {
                        this.setState({
                            editModalShown: false,
                        });
                    }}
                    addItemDisplayTitle={getSingularDisplayName(this.state.currentSelectedMenuItem)}
                    objectPrototype={getPrototype(this.state.currentSelectedMenuItem)}
                    autocompleteSearch={autocompleteSearch.bind(this)}
                    fetchInfoByIndex={this.fetchInfoByIndex.bind(this)}
                    additionalEditInfo={this.state.additionalEditInfo}
                />
                <div id="masterInnerContainer">
                    <div id="topMenuContainer">
                        <TopMenu
                            currentSelectedMenuItem={this.state.currentSelectedMenuItem}
                            setSelectedMenuItem={this.changeCurrentSelectedMenuItem.bind(this)}
                            deleteEnabled={this.checkDeleteEnabled()}
                            editEnabled={this.checkEditEnabled()}
                            performDelete={this.confirmAndDelete.bind(this)}
                            performAdd={async () => {
                                this.setState({
                                    addModalShown: true,
                                });
                                let isRootType = getPrototype(this.state.currentSelectedMenuItem).constructor.rootDescribe !== undefined;
                                if (isRootType) {
                                    let types=getPrototype(this.state.currentSelectedMenuItem).constructor.rootTypes;
                                    for (let i=0;i<types.length;i++) {
                                        if (types[i]==='component_pkid' || types[i]==='mode_pkid') {
                                            if (types[i]==='component_pkid') {
                                                this.fetchComponents();
                                            } else {
                                                this.fetchModes();
                                            }
                                        } else {
                                            console.log(`Unrecognized root type ${types[i]}`);
                                        }
                                    }
                                }
                            }}
                            performEdit={async () => {
                                let [selectionCache, rawData, , relatedFailureDeletions] = await this.getSelectedDetails();
                                if (selectionCache.length !== 1) {
                                    return;
                                }
                                let editIndex=selectionCache[0];
                                let name;
                                switch (this.state.currentSelectedMenuItem) {
                                case 'components':
                                    name=rawData[editIndex].productname;
                                    break;
                                case 'failures':
                                    name='Failure';
                                    break;
                                case 'mode':
                                    name=rawData[editIndex].failname;
                                    break;
                                default:
                                    console.log(`Perform edit called on unexpected state ${this.state.currentSelectedMenuItem}`);
                                }
                                let sub=`Database ID ${rawData[editIndex].dbid}`;
                                if (relatedFailureDeletions.length>0) {
                                    sub=`${sub} • ${getSingularDisplayName(this.state.currentSelectedMenuItem)} associated with ${relatedFailureDeletions.length} Mapping${relatedFailureDeletions.length>1 ? "s" : ""}`;
                                }
                                let isRootType = rawData[editIndex].constructor.rootDescribe !== undefined;
                                let rootData=[];
                                if (isRootType) {
                                    let types=rawData[editIndex].constructor.rootTypes;
                                    let rawRootData=rawData[editIndex].getRootData();

                                    let fetchJobs=[];
                                    for (let i=0;i<types.length;i++) {
                                        if (types[i]==='component_pkid' || types[i]==='mode_pkid') {
                                            fetchJobs.push(this.fetchRootDataByDbid(types[i], rawRootData[i], true));
                                        } else {
                                            console.log(`Unrecognized root type ${types[i]}`);
                                        }
                                    }
                                    let results = await Promise.all(fetchJobs);
                                    for (let i=0;i<types.length;i++) {
                                        // eslint-disable-next-line no-await-in-loop
                                        let [match, fetched] = results[i];
                                        if (!fetched) {
                                            if (types[i] === 'component_pkid') {
                                                this.fetchComponents();
                                            } else {
                                                this.fetchModes();
                                            }
                                        }
                                        if (match === -1) {
                                            console.log(`Failed to load edit view, couldn't find ${types[i]==='component_pkid' ? 'Component' : 'Mode'} with database ID ${rawRootData[i]}`);
                                            return;
                                        }
                                        rootData.push(match);
                                    }
                                }
                                this.setState({
                                    editModalShown: true,
                                    additionalEditInfo: {
                                        index: editIndex,
                                        name: name,
                                        sub: sub,
                                        data: isRootType ? rootData : rawData[editIndex].getData(),
                                    },
                                });
                            }}
                            performDeSelectAll={this.handleDeSelectAllButtonPressed.bind(this)}
                            selectionToggleButtonIsSelectAll={this.selectionToggleButtonIsSelectAll()}
                            setSearchActive={(val) => {
                                if (!val && this.state.searchContents !== '') {
                                    return;
                                }
                                this.setState({
                                    isInSearch: val,
                                });
                            }}
                            searchContents={this.state.searchContents}
                            setSearchContents={(val) => {
                                this.setState({
                                    searchContents: val,
                                });
                                this.recomputeData(undefined, undefined, undefined, val, true);
                                // actually do the search now
                            }}
                            searchTip={this.state.isInSearch ? `${getDisplayName(this.state.currentSelectedMenuItem)}: Searching ${getCorrespondingDisplayNameOfColumnId(getPrototype(this.state.currentSelectedMenuItem), this.state.searchColumn[this.state.currentSelectedMenuItem])}` : ''}
                        />
                    </div>
                    <MainTable
                        displayData={this.state.displayData}
                        displayLRange={this.state.displayLRange}
                        displayRRange={this.state.displayRRange}
                        sortedColumn={this.state.sortedColumn[this.state.currentSelectedMenuItem]}
                        sortMethodAscending={this.state.sortMethodAscending[this.state.currentSelectedMenuItem]}
                        tableType={this.state.currentSelectedMenuItem}
                        processClick={this.processClick.bind(this)}
                        highlightData={this.state.highlightData}
                        tableDataPrototype={getPrototype(this.state.currentSelectedMenuItem)}
                        handleHeaderClick={(id, cmdOrOptionHeld) => {
                            if (!this.state.isInSearch || (this.state.isInSearch && cmdOrOptionHeld)) {
                                let newSortedColumn = { ...this.state.sortedColumn };
                                let newSortMethodAscending = { ...this.state.sortMethodAscending };

                                if (id === newSortedColumn[this.state.currentSelectedMenuItem]) {
                                    newSortMethodAscending[this.state.currentSelectedMenuItem] = !newSortMethodAscending[this.state.currentSelectedMenuItem];
                                } else {
                                    newSortedColumn[this.state.currentSelectedMenuItem] = id;
                                    newSortMethodAscending[this.state.currentSelectedMenuItem] = true;
                                }
                                this.recomputeData(this.state.currentSelectedMenuItem, newSortedColumn[this.state.currentSelectedMenuItem], newSortMethodAscending[this.state.currentSelectedMenuItem]);
                                this.setState(() => ({
                                    sortedColumn: newSortedColumn,
                                    sortMethodAscending: newSortMethodAscending,
                                }));
                            } else {
                                if (id === 'num') {
                                    return;
                                }
                                let newSearchColumn = { ...this.state.searchColumn };
                                newSearchColumn[this.state.currentSelectedMenuItem] = id;
                                this.setState({
                                    searchColumn: newSearchColumn,
                                });
                                this.recomputeData(undefined, undefined, undefined, undefined, undefined, id);
                            }
                        }}
                        isInSearch={this.state.isInSearch}
                        searchColumn={this.state.searchColumn[this.state.currentSelectedMenuItem]}
                    />
                    <Pager
                        pages={this.state.numberOfPages}
                        currentPage={this.state.pages[this.state.currentSelectedMenuItem]+1}
                        setPage={(page) => {
                            let newPages={ ...this.state.pages };
                            newPages[this.state.currentSelectedMenuItem]=page-1;
                            this.setState({
                                pages: newPages,
                            });
                            this.recomputeData(undefined, undefined, undefined, undefined, undefined, undefined, page-1);
                        }}
                    />
                </div>
            </div>
        );
    }
}

export default App;
<file_sep>import FailsData from './DataStructs/FailsData';
import ComponentsData from './DataStructs/ComponentsData';
import ModesData from './DataStructs/ModesData';

export default function getPrototype(curState) {
    switch (curState) {
    case 'components':
        return new ComponentsData('', '', '', '', '', '');
    case 'failures':
        return new FailsData('', '', '', '', '', '');
    case 'modes':
        return new ModesData('', '', '', '');
    default:
        console.log(`Unknown state for prototype ${curState}`);
    }
    return undefined;
}
<file_sep>import React from 'react';
import PropTypes from 'prop-types';
import ModalShared from '../modal-shared/modalShared';
import '../modal-shared/modalShared.css';
import './addEditModal.css';
import './addEditModalColors.css';
import '../../Shared Components/Bordered Input Box Styles/borderedInputBox.css';
import '../../Shared Components/Bordered Input Box Styles/borderedInputBoxColors.css';
import validateInput from '../../DataStructs/validateInput';
import AutocompleteBox from '../../Shared Components/Autocomplete Box/AutocompleteBox';

export default class AddEditModal extends React.Component {
    initialize(firstTime) {
        const isRootDescribe = (this.props.objectPrototype.constructor.rootDescribe !== undefined);
        let addModalFields;
        if (isRootDescribe) {
            addModalFields=this.props.objectPrototype.constructor.rootDescribe.length;
        } else {
            addModalFields=this.props.objectPrototype.constructor.describe.length;
        }
        const inputIsInvalidState = Array(addModalFields);
        let inputFieldValuesState = Array(addModalFields);
        for (let i = 0; i < addModalFields; i++) {
            inputIsInvalidState[i] = false;
            inputFieldValuesState[i] = isRootDescribe ? -1 : '';
        }
        if (this.props.modalType === 'edit' && this.props.additionalEditInfo.data !== undefined) {
            inputFieldValuesState = this.props.additionalEditInfo.data;
        }
        if (firstTime) {
            this.state = {
                inputIsInvalid: inputIsInvalidState,
                inputFieldValues: inputFieldValuesState,
                inputInvalidMessage: '',
                autocompleteSelectedInfo: [],
            };
        } else {
            this.setState({
                inputIsInvalid: inputIsInvalidState,
                inputFieldValues: inputFieldValuesState,
                inputInvalidMessage: '',
                autocompleteSelectedInfo: [],
            });
        }
    }

    constructor(props) {
        super(props);
        // state the addModal has to keep track of: whether or not an input box is red
        this.initialize(true);
    }

    componentDidUpdate(prevProps) {
        if (prevProps.objectPrototype !== this.props.objectPrototype || prevProps.show !== this.props.show || prevProps.additionalEditInfo !== this.props.additionalEditInfo) {
            this.initialize(false);
        }
    }

    render() {
        if (this.props.modalType !== 'add' && this.props.modalType !== 'edit') {
            console.log(`Addedit called on invalid modal type ${this.props.modalType}`);
        }
        const curPrototype = this.props.objectPrototype.constructor;
        const isRootDescribe = (curPrototype.rootDescribe !== undefined);
        const addChildren = [];
        let description = (isRootDescribe ? curPrototype.rootDescribe : curPrototype.describe);
        if (this.props.show && description.length === this.state.inputFieldValues.length) {
            for (let i = 0; i < description.length; i++) {
                if (isRootDescribe) {
                    addChildren.push(
                        <div className="modal-addedit-row" key={`modal-addedit-row-${description[i]}`}>
                            <div className="modal-addedit-row-label">
                                {description[i]}
                            </div>
                            <AutocompleteBox
                                autocompleteSearch={(val) => {
                                    let translatedName;
                                    switch (curPrototype.rootTypes[i]) {
                                    case "component_pkid":
                                        translatedName = 'components';
                                        break;
                                    case 'mode_pkid':
                                        translatedName = 'modes';
                                        break;
                                    default:
                                        console.log(`Translated name requested for unknown root type ${curPrototype.rootTypes[i]}`);
                                        return [];
                                    }
                                    let sortedSearchResults = this.props.autocompleteSearch(translatedName, val);
                                    let autocompleteList = [];
                                    switch (curPrototype.rootTypes[i]) {
                                    case "component_pkid":
                                        for (let j = 0; j < sortedSearchResults.length; j++) {
                                            autocompleteList.push({
                                                main: sortedSearchResults[j].data.productname,
                                                sub: sortedSearchResults[j].data.manufacturer,
                                                id: sortedSearchResults[j].index,
                                            });
                                        }
                                        break;
                                    case 'mode_pkid':
                                        for (let j = 0; j < sortedSearchResults.length; j++) {
                                            autocompleteList.push({
                                                main: sortedSearchResults[j].data.failname,
                                                sub: sortedSearchResults[j].data.code,
                                                id: sortedSearchResults[j].index,
                                            });
                                        }
                                        break;
                                    default:
                                    }
                                    return autocompleteList;
                                }}
                                boxRevIndex={description.length - i}
                                inputIsInvalid={this.state.inputIsInvalid[i]}
                                inputSelectedId={this.state.inputFieldValues[i]}
                                setInputInvalid={(val) => {
                                    let newInputIsInvalid = this.state.inputIsInvalid.slice();
                                    newInputIsInvalid[i] = val;
                                    this.setState({
                                        inputIsInvalid: newInputIsInvalid,
                                    });
                                }}
                                setInputSelectedId={(val) => {
                                    let newInputFieldValues = this.state.inputFieldValues.slice();
                                    newInputFieldValues[i] = val;
                                    let newAutocompleteInfo;
                                    let fetchedObject;
                                    switch (curPrototype.rootTypes[i]) {
                                    case "component_pkid":
                                        fetchedObject = this.props.fetchInfoByIndex('components', val);
                                        newAutocompleteInfo = {
                                            index: val,
                                            main: fetchedObject.productname,
                                            sub: fetchedObject.manufacturer,
                                        };
                                        break;
                                    case "mode_pkid":
                                        fetchedObject = this.props.fetchInfoByIndex('modes', val);
                                        newAutocompleteInfo = {
                                            index: val,
                                            main: fetchedObject.failname,
                                            sub: fetchedObject.code,
                                        };
                                        break;
                                    default:
                                        console.log(`Unrecognized root type ${curPrototype.rootTypes}`);
                                    }
                                    let newAutocompleteSelectedInfo = this.state.autocompleteSelectedInfo.slice();
                                    newAutocompleteSelectedInfo[i] = newAutocompleteInfo;
                                    this.setState({
                                        inputFieldValues: newInputFieldValues,
                                        autocompleteSelectedInfo: newAutocompleteSelectedInfo,
                                    });
                                }}
                                selectedInfo={this.state.autocompleteSelectedInfo[i]}
                            />
                        </div>,
                    );
                } else {
                    addChildren.push(
                        <div className="modal-addedit-row" key={`modal-addedit-row-${description[i]}`}>
                            <div className="modal-addedit-row-label">
                                {description[i]}
                            </div>
                            <input
                                className={`bordered-input-box${this.state.inputIsInvalid[i] ? ' bordered-input-box-invalid' : ''}`}
                                type="text"
                                value={this.state.inputFieldValues[i] === null ? '' : this.state.inputFieldValues[i]}
                                onChange={(val) => {
                                    const newState = this.state.inputFieldValues.slice();
                                    newState[i] = val.target.value;
                                    this.setState({
                                        inputFieldValues: newState,
                                    });
                                }}
                                onBlur={(e) => {
                                    const currentValue = e.target.value;
                                    const newInputIsInvalidState = this.state.inputIsInvalid.slice();
                                    newInputIsInvalidState[i] = validateInput(currentValue, curPrototype.types[i], curPrototype.constraints[i]) !== '';
                                    this.setState({
                                        inputIsInvalid: newInputIsInvalidState,
                                    });
                                }}
                            />
                        </div>,
                    );
                }
            }
        }
        let modalTitle;
        if (this.props.modalType === 'add') {
            modalTitle=`Add ${this.props.addItemDisplayTitle}`;
        } else {
            modalTitle=(
                <div>
                    {`Edit ${this.props.additionalEditInfo.name}`}
                    <div className="modal-edit-sub">
                        {this.props.additionalEditInfo.sub}
                    </div>
                </div>
            );
        }
        return (
            <ModalShared
                show={this.props.show}
                modalBody={
                    [
                        <div id="addedit-modal-create-list" key="addedit-modal-main-list">
                            {addChildren}
                        </div>,
                        <div id="addedit-modal-error-message" key="addedit-modal-error-message">{ this.state.inputInvalidMessage }</div>,
                        <div className="modal-buttons-container" key="modal-addedit-buttons-container">
                            <div
                                className={`modal-buttons modal-${this.props.modalType === 'add' ? 'add' : 'edit'}-button`}
                                onClick={() => {
                                    let encounteredError = false;
                                    let errorMessage = '';
                                    const newInputIsInvalidState = Array(isRootDescribe ? curPrototype.rootDescribe.length : curPrototype.describe.length);
                                    for (let i = 0; i < curPrototype.describe.length; i++) {
                                        let validationResult;
                                        if (isRootDescribe) {
                                            validationResult = (this.state.inputFieldValues[i] === -1 ? 'Cannot be empty' : '');
                                        } else {
                                            validationResult = validateInput(this.state.inputFieldValues[i], curPrototype.types[i], curPrototype.constraints[i]);
                                        }
                                        if (validationResult !== '' && !encounteredError) {
                                            encounteredError = true;
                                            errorMessage = `Invalid ${curPrototype.describe[i]}: ${validationResult}`;
                                        }
                                        newInputIsInvalidState[i] = (validationResult !== '');
                                    }
                                    if (!encounteredError) {
                                        this.props.submitClicked(this.state.inputFieldValues);
                                    }
                                    this.setState({
                                        inputIsInvalid: newInputIsInvalidState,
                                        inputInvalidMessage: errorMessage,
                                    });
                                }}
                            >
                                {this.props.modalType === 'add' ? 'Add' : 'Edit'}
                            </div>
                            <div className="modal-buttons modal-cancel-button" onClick={this.props.cancelClicked}>
                                Cancel
                            </div>
                        </div>,
                    ]
                }
                modalTitle={modalTitle}
                stylingId="addedit"
            />
        );
    }
}

AddEditModal.propTypes = {
    modalType: PropTypes.string.isRequired,
    show: PropTypes.bool.isRequired,
    objectPrototype: PropTypes.object.isRequired,
    addItemDisplayTitle: PropTypes.string.isRequired,
    submitClicked: PropTypes.func.isRequired,
    cancelClicked: PropTypes.func.isRequired,
    autocompleteSearch: PropTypes.func.isRequired,
    fetchInfoByIndex: PropTypes.func.isRequired,
    additionalEditInfo: PropTypes.object.isRequired,
};
<file_sep>import React from 'react';
import PropTypes from 'prop-types';
import ModalShared from '../modal-shared/modalShared';
import '../modal-shared/modalShared.css';
import './deleteModal.css';
import './deleteModalColors.css';

export default function DeleteModal(props) {
    return (
        <ModalShared
            show={props.show}
            modalBody={
                [
                    <div className="modal-text" key="modal-delete-main-text">{ props.text }</div>,
                    <div className="modal-buttons-container" key="modal-delete-buttons-container">
                        <div className="modal-buttons modal-delete-button" onClick={props.deleteClicked}>
                            Delete
                        </div>
                        <div className="modal-buttons modal-cancel-button" onClick={props.cancelClicked}>
                            Cancel
                        </div>
                    </div>,
                ]
            }
            modalTitle="Are you sure?"
            stylingId="delete"
        />
    );
}

DeleteModal.propTypes = {
    show: PropTypes.bool.isRequired,
    text: PropTypes.string.isRequired,
    deleteClicked: PropTypes.func.isRequired,
    cancelClicked: PropTypes.func.isRequired,
};
<file_sep>export default class FailsData {
    constructor(dbid, failComponentId, failComponentName, failModeId, failModeName, failCode, failDescription) {
        this.dbid = dbid;
        this.failComponentId = failComponentId;
        this.failComponentName = failComponentName;
        this.failModeId = failModeId;
        this.failModeName = failModeName;
        this.failCode = failCode;
        this.failDescription = failDescription;
    }

    static get describe() {
        return ['Component', 'Fail Mode', 'Fail Code', 'Description'];
    }

    static get rootTypes() {
        return ['component_pkid', 'mode_pkid']; // this is kinda basic for now, add as things go on. built to be extendable
    }

    static get rootDescribe() {
        return ["Failed Component", "Fail Name"];
    }

    getRootData() {
        return [this.failComponentId, this.failModeId];
    }

    static get getIds() {
        return ['failedcomponent', 'failname', 'code', 'reason'];
    }

    static get types() {
        return ['string', 'string', 'string', 'string'];
    }

    getData() {
        return [this.failComponentName, this.failModeName, this.failCode, this.failDescription];
    }
}
<file_sep># DataStructs
## .describe()
Provides the title for the table
## .getIds()
Provides identifiers for which things such as which column is currently being sorted is coded in and for deserializing data from the server
## .types()
Provides type information. The sorter uses this and the add and edit modals also uses this for input validation.
### Valid Types:
string, number
will add as needed
## .constraints()
Provides constraint information for the add and edit modals
## Valid arguments
moreThan: number, lessThanOrEqualTo: number for numbers
canBeEmpty: bool for any
will add as needed
<file_sep>import React from 'react';
import PropTypes from 'prop-types';
import TableSeparator from './TableSeparator';
import { getIcon } from '../utils';
import './TableColors.css';

function TableHeaderItem(props) {
    return (
        // parameters: what to display, my id, the currently being sorted id, which way its being sorted
        <th
            onMouseDown={(e) => {
                e.preventDefault();
                props.handleHeaderClick(e.metaKey || e.altKey);
            }}
            className="TableHeaderTh"
        >
            <div className={`tableHeaderItem${props.highlight ? 'Highlighted' : 'Unhighlighted'}`}>
                <div className={props.greyOut ? 'tableHeaderItem-greyOut' : ''}>{ props.displayItem }</div>
                <div className={`tableHeaderSort${props.sortArrow!=='' ? '' : ' tableHeaderHidden'}`}>
                    <img src={getIcon(props.sortArrow)} className="tableHeaderSort-icon" alt="Sorting Icon" />
                </div>
            </div>
        </th>
    );
}

TableHeaderItem.propTypes = {
    displayItem: PropTypes.string.isRequired,
    highlight: PropTypes.bool.isRequired,
    greyOut: PropTypes.bool.isRequired,
    sortArrow: PropTypes.string.isRequired,
    handleHeaderClick: PropTypes.func.isRequired,
};

function TableHeader(props) {
    const headerData = props.headerData.constructor.describe.slice();
    headerData.unshift('#');
    const headerDataIds = props.headerData.constructor.getIds.slice();
    headerDataIds.unshift('num');
    const children = Array(headerData.length + 3);
    for (let i = 0; i < headerData.length; i++) {
        children[i] = React.createElement(TableHeaderItem, {
            displayItem: headerData[i],
            highlight: headerDataIds[i] === props.sortedColumn,
            greyOut: props.isInSearch && props.searchColumn !== headerDataIds[i],
            sortArrow: headerDataIds[i] === props.sortedColumn ? (props.sortMethodAscending ? 'DownArrow' : 'UpArrow') : '',
            handleHeaderClick: (cmdOrOptionHeld) => {
                props.handleHeaderClick(headerDataIds[i], cmdOrOptionHeld);
            },
            key: `HeaderItem${headerDataIds[i]}`,
        }, null);
    }
    return React.createElement('tr', { id: 'TableHeader' }, children);
}

TableHeader.propTypes = {
    headerData: PropTypes.object.isRequired,
    sortedColumn: PropTypes.string.isRequired,
    sortMethodAscending: PropTypes.bool.isRequired,
    handleHeaderClick: PropTypes.func.isRequired,
    isInSearch: PropTypes.bool.isRequired,
    searchColumn: PropTypes.string.isRequired,
};

function TableRow(props) {
    const { rowData } = props;
    const children = Array(rowData.length + 3);
    children[0] = <td key="num" className={(props.rowHighlighted ? 'TableRowHighlighted' : '')}>{props.rowNumber}</td>;
    for (let i = 1; i <= rowData.length; i++) {
        children[i] = (
            <td key={props.rowIds[i - 1]} className={(props.rowHighlighted ? 'TableRowHighlighted' : '')}>
                {rowData[i - 1]}
            </td>
        );
    }
    return (
        <tr
            className="TableRow"
            onClick={(e) => {
                let typeOfAction = 'single';
                if (e.shiftKey) {
                    typeOfAction = 'range';
                } else if (e.metaKey || e.altKey) {
                    typeOfAction = 'multi';
                }
                props.processClick(props.rowSelectIdentifier, typeOfAction);
            }}
        >
            {children}
        </tr>
    );
    /*
    There are two parts to a rowSelectIdentifier. The type of table (Components, Failures, Modes, etc.) and the dbid (primary key)
    */
}

TableRow.propTypes = {
    rowData: PropTypes.arrayOf(PropTypes.string).isRequired,
    rowHighlighted: PropTypes.bool.isRequired,
    rowNumber: PropTypes.number.isRequired,
    rowIds: PropTypes.arrayOf(PropTypes.string).isRequired,
    processClick: PropTypes.func.isRequired,
    rowSelectIdentifier: PropTypes.shape({
        tableType: PropTypes.string,
        tableIndex: PropTypes.number,
    }).isRequired,
};

export default function MainTable(props) {
    const tableData = props.displayData;
    const { displayLRange } = props;
    const { displayRRange } = props;
    const dataPrototype = props.tableDataPrototype;
    let tableElements = Array(Math.max((displayRRange-displayLRange) * 2 - 1, 0));
    if (tableData.length > 0) {
        const tableMultiples = dataPrototype.getData().length;
        const tableIds = dataPrototype.constructor.getIds;
        for (let i = displayLRange; i < Math.min(displayRRange, tableData.length); i++) {
            tableElements[2 * i] = (
                <TableRow
                    rowIds={tableIds}
                    rowData={tableData[i].getData()}
                    rowNumber={tableData[i].num}
                    key={`tableRow${props.tableType}${tableData[i].dbid}`}
                    rowHighlighted={tableData[i].selected}
                    rowSelectIdentifier={{
                        tableType: props.tableType,
                        tableIndex: i,
                    }}
                    processClick={props.processClick}
                />
            );
            if (i !== Math.min(displayRRange, tableData.length) - 1) {
                tableElements[2 * i + 1] = <TableSeparator multiples={tableMultiples + 3} key={`tableSeparator${tableData[i].dbid}`} />;
            }
        }
    } else {
        tableElements = [
            <tr key="NoDataTableTr">
                <td id="NoData" colSpan={dataPrototype.getData().length + 1}>
                    {props.isInSearch ? "No Results" : "No Data"}
                </td>
            </tr>,
        ];
    }
    return (
        <div id="tableContainer">
            <div id="TableShadow" />
            <table id="MainTable">
                <thead>
                    <TableHeader headerData={dataPrototype} sortedColumn={props.sortedColumn} sortMethodAscending={props.sortMethodAscending} handleHeaderClick={props.handleHeaderClick} isInSearch={props.isInSearch} searchColumn={props.searchColumn} />
                </thead>
                <tbody>
                    {tableElements}
                </tbody>
            </table>
        </div>
    );
}

MainTable.propTypes = {
    displayData: PropTypes.arrayOf(PropTypes.object).isRequired,
    displayLRange: PropTypes.number.isRequired,
    displayRRange: PropTypes.number.isRequired,
    tableDataPrototype: PropTypes.object.isRequired,
    tableType: PropTypes.string.isRequired,
    processClick: PropTypes.func.isRequired,
    sortedColumn: PropTypes.string.isRequired,
    sortMethodAscending: PropTypes.bool.isRequired,
    handleHeaderClick: PropTypes.func.isRequired,
    isInSearch: PropTypes.bool.isRequired,
    searchColumn: PropTypes.string.isRequired,
};
<file_sep>export default class ModesData {
    constructor(dbid, failname, code, description) {
        this.dbid = dbid;
        this.failname = failname;
        this.code = code;
        this.description = description;
    }

    static get describe() {
        return ['Fail Name', 'Fail Code', 'Description'];
    }

    static get getIds() {
        return ['name', 'code', 'description'];
    }

    static get types() {
        return ['string', 'string', 'string'];
    }

    static get constraints() {
        return [{ canBeEmpty: false }, { canBeEmpty: false }, { canBeEmpty: false }];
    }

    getData() {
        return [this.failname, this.code, this.description];
    }
}
<file_sep>// eslint-disable-next-line import/prefer-default-export
export const rootURL = 'http://127.0.0.1:5000';
// eslint-disable-next-line import/prefer-default-export
export const eachPage = 15;
