import React, { Component, Fragment } from "react" import PropTypes from "prop-types" import autobind from "autobind-decorator" import { Row, Column, Box } from "ui" import { YesNoMessageModal, MessageModal, WaitModal } from "../Modal" import { sizeInfo, colorInfo } from "ui/style" import { DetailPlaceholder, MasterList } from "." import pluralize from "pluralize" export class MasterDetail extends Component { static propTypes = { history: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), name: PropTypes.string, form: PropTypes.func.isRequired, listItems: PropTypes.func.isRequired, updateItem: PropTypes.func.isRequired, createItem: PropTypes.func.isRequired, deleteItem: PropTypes.func.isRequired, listSort: PropTypes.func.isRequired, detailCallbacks: PropTypes.object, listData: PropTypes.func, children: PropTypes.element, } constructor(props) { super(props) this.state = { modified: false, selectedItem: null, items: [], yesNoModal: null, messageModal: null, waitModal: null, changeEmailModal: null, } const { name } = this.props this.capitalizedName = name.charAt(0).toUpperCase() + name.substr(1) this.pluralizedName = pluralize(name) } componentDidMount() { this.props .listItems() .then((list) => { this.setState({ items: list.items.sort(this.props.listSort) }) const { history } = this.props const search = new URLSearchParams(history.location.search) const id = search.get("id") if (id) { const item = list.items.find((item) => item._id === id) if (item) { this.setState({ selectedItem: item }) } else { history.replace(history.pathname) } } }) .catch((error) => { this.showErrorMessage( `Unable to get the list of ${this.pluralizedName}.`, error.message ) }) } @autobind showWait(message) { this.setState({ waitModal: { message, }, }) } @autobind hideWait() { this.setState({ waitModal: null, }) } @autobind showMessage(message, detail) { this.setState({ messageModal: { icon: "thumb", message, detail, }, }) } @autobind showErrorMessage(message, detail) { this.setState({ messageModal: { icon: "hand", message, detail, }, }) } @autobind showYesNo(message, onDismiss) { this.setState({ yesNoModal: { question: message, onDismiss, }, }) } @autobind hideYesNo() { this.setState({ yesNoModal: null, }) } @autobind getSelectedItem() { return this.state.selectedItem } @autobind removeUnfinishedNewItem() { let items = this.state.items if (items.length > 0 && !items[0]._id) { this.setState({ items: this.state.items.slice(1) }) } } @autobind handleItemListClick(e, index) { let item = this.state.items[index] const { history } = this.props if (this.state.modified) { this.nextSelectedItem = item this.showYesNo( `This ${ this.props.name } has been modified. Are you sure you would like to navigate away?`, this.handleModifiedModalDismiss ) } else { this.setState({ selectedItem: item }) this.removeUnfinishedNewItem() history.replace(`${history.location.pathname}?id=${item._id}`) } } @autobind handleSave(item) { if (item._id) { this.showWait(`Updating ${this.capitalizedName}`) this.props .updateItem(item) .then((updatedItem) => { this.hideWait() this.setState({ items: this.state.items.map( (item) => (item._id === updatedItem._id ? updatedItem : item) ), modified: false, selectedItem: updatedItem, }) }) .catch((error) => { this.hideWait() this.showErrorMessage( "Unable to save the item changes", error.message ) }) } else { this.showWait(`Creating ${this.capitalizedName}`) this.props .createItem(item) .then((createdItem) => { this.hideWait() this.setState({ items: this.state.items .map((item) => (!item._id ? createdItem : item)) .sort(this.props.listSort), modified: false, selectedItem: createdItem, }) }) .catch((error) => { this.hideWait() this.showErrorMessage("Unable to create the item.", error.message) }) } } @autobind handleRemove() { this.showYesNo( `Are you sure you want to remove this ${this.props.name}?`, this.handleRemoveModalDismiss ) } @autobind handleRemoveModalDismiss(yes) { if (yes) { const selectedItemId = this.state.selectedItem._id const selectedIndex = this.state.items.findIndex( (item) => item._id === selectedItemId ) if (selectedIndex >= 0) { this.showWait(`Removing ${this.capitalizedName}`) this.props .deleteItem(selectedItemId) .then(() => { this.hideWait() this.setState({ items: [ ...this.state.items.slice(0, selectedIndex), ...this.state.items.slice(selectedIndex + 1), ], selectedItem: null, }) }) .catch((error) => { this.hideWait() this.showErrorMessage( `Unable to remove the ${this.props.name}.`, error.message ) }) } } this.hideYesNo() } @autobind handleModifiedModalDismiss(yes) { if (yes) { this.setState({ selectedItem: this.nextSelectedItem, modified: false, }) this.removeUnfinishedNewItem() delete this.nextSelectedItem } this.hideYesNo() } @autobind handleMessageModalDismiss() { this.setState({ messageModal: null }) } @autobind handleModifiedChanged(modified) { this.setState({ modified: modified }) } @autobind handleAddNewItem() { let items = this.state.items if (items.length > 0 && !items[0]._id) { // Already adding a new item return } let newItem = {} let newItems = [newItem].concat(this.state.items) this.setState({ items: newItems, selectedItem: newItem }) } render() { const { messageModal, yesNoModal, waitModal, items, selectedItem, modified, } = this.state const { name } = this.props return ( {selectedItem ? ( React.createElement(this.props.form, { item: selectedItem, onSave: this.handleSave, onRemove: this.handleRemove, onModifiedChanged: this.handleModifiedChanged, ...this.props.detailCallbacks, }) ) : ( )} {this.props.children} ) } }