import React, {PureComponent} from 'react'; import PropTypes from "prop-types"; import {FilterOptions} from "./options/utils"; import {v4 as uuidv4} from "uuid"; import {DragDropContext} from "@hello-pangea/dnd"; import {copy, itemsUnfoldedList, nbrOccupiedSlotsInCrate, remove, reorder} from "./utils"; import {Layout} from "./Layout.jsx"; import {Backlog} from "./Backlog.jsx"; import {OrderPanel} from "./OrderPanel.jsx"; import {CrateMode} from "./CrateMode.jsx"; import {Crate} from "./Crate.jsx"; import {Cart} from "./Cart.jsx"; import {OrderSummary} from "./OrderSummary.jsx"; import {OrderForm} from "./OrderForm.jsx"; import {TriggerWarnings} from "./warnings"; import {FillResources} from "./count_resources"; /** * Component that render the entire shop */ export class Shop extends PureComponent { static get propTypes() { return { data: PropTypes.object.isRequired, }; } constructor(props) { super(props); this.state = this.props.data; this.handleCrateModeChange = this.handleCrateModeChange.bind(this); this.handleOnDragEnd = this.handleOnDragEnd.bind(this); this.handleDeleteItem = this.handleDeleteItem.bind(this); this.handleDeleteAllItems = this.handleDeleteAllItems.bind(this); this.handleMouseEnterItem = this.handleMouseEnterItem.bind(this); this.handleMouseLeaveItem = this.handleMouseLeaveItem.bind(this); this.handleClickAddItem = this.handleClickAddItem.bind(this); this.checkAlerts = this.checkAlerts.bind(this); this.handleClickSelectItem = this.handleClickSelectItem.bind(this); this.handleClickSubmit = this.handleClickSubmit.bind(this); this.handleToggleOverlayRemove = this.handleToggleOverlayRemove.bind(this); this.handleShowOverlayRemove = this.handleShowOverlayRemove.bind(this); this.handleClickToggleMobileSideMenu = this.handleClickToggleMobileSideMenu.bind(this); this.handleClickCloseRFQFeedback = this.handleClickCloseRFQFeedback.bind(this); this.handleClickShowOrder = this.handleClickShowOrder.bind(this); this.handleClickOpenImport = this.handleClickOpenImport.bind(this); this.handleLoadCustomConf = this.handleLoadCustomConf.bind(this); this.handleCardsUpdated = this.handleCardsUpdated.bind(this); this.timer = null; this.timer_remove = null; } componentDidMount() { const source = { droppableId: 'backlog', indexes: [ itemsUnfoldedList.findIndex(element => element === "eem_pwr_mod"), itemsUnfoldedList.findIndex(element => element === "kasli") ], }; const destination = { droppableId: 'cart', index: 0, }; this.handleOnDragEnd({ source, destination }); } componentDidUpdate(prevProps, prevState) { /** * We check alerts (reminder + warning) only when items inside crate or * crate mode change. * * In the function checkAlerts, we DO NOT want to change items as we will * trigger again this function (componentDidUpdate) and thus, * making an infinite loop. */ if ( (prevState.columns.cart.items !== this.state.columns.cart.items) || (prevState.currentMode !== this.state.currentMode) ) { this.checkAlerts(this.state.columns.cart.items); } if (this.state.newCardJustAdded) { this.timer = setTimeout(() => { this.setState({ newCardJustAdded: false, }); }, 2000); } } componentWillUnmount() { clearTimeout(this.timer); } handleCardsUpdated() { this.checkAlerts(this.state.columns.cart.items); } handleCrateModeChange(mode) { this.setState({ currentMode: mode, }); } handleDeleteItem(index) { let cloned = Array.from(this.state.columns.cart.items); let cloned_data = Array.from(this.state.columns.cart.itemsData); cloned.splice(index, 1); cloned_data.splice(index, 1); this.setState({ ...this.state, columns: { ...this.state.columns, cart: { ...this.state.columns.cart, items: cloned, itemsData: cloned_data, }, }, }); } handleDeleteAllItems() { this.setState({ ...this.state, columns: { ...this.state.columns, cart: { ...this.state.columns.cart, items: [], itemsData: [] }, }, }); } handleMouseEnterItem(id) { this.setState({ ...this.state, currentItemHovered: id, }); } handleMouseLeaveItem() { this.setState({ ...this.state, currentItemHovered: null, }); } handleClickAddItem(index, tap) { const source = { droppableId: 'backlog', index: index, }; const destination = { droppableId: 'cart', index: this.state.columns.cart.items.length, }; this.handleOnDragEnd({ source, destination }, tap); } handleClickSelectItem(index) { const itemsCloned = Array.from(this.state.columns.cart.items); this.setState({ ...this.state, columns: { ...this.state.columns, cart: { ...this.state.columns.cart, items: itemsCloned.map((item, id) => { return {...item, selected: id === index ? true : false}; }), } }, }); } handleToggleOverlayRemove(index, show) { const itemsCloned = Array.from(this.state.columns.cart.items); this.setState({ ...this.state, columns: { ...this.state.columns, cart: { ...this.state.columns.cart, items: itemsCloned.map((item, id) => { return { ...item, showOverlayRemove: id === index ? show : false }; }), } }, }); } handleShowOverlayRemove(index) { if (this.timer_remove) clearTimeout(this.timer_remove); this.handleToggleOverlayRemove(index, true); this.timer_remove = setTimeout(() => { this.handleToggleOverlayRemove(index, false); }, 2000); } handleClickShowOrder() { const crate = { items: [], type: this.state.currentMode, }; const clonedCart = Array.from(this.state.columns.cart.items); const clonedCartData = Array.from(this.state.columns.cart.itemsData); for (const i in clonedCart) { const item = clonedCart[i]; const item_data = clonedCartData[i]; crate.items.push({ 'pn': item.name_number, 'options': (item_data.options_data && item_data.options) ? FilterOptions(item_data.options, item_data.options_data) : null, }); } this.setState({ isProcessing: false, shouldShowRFQFeedback: true, RFQBodyType: 'show', RFQBodyOrder: JSON.stringify(crate, null, 2), }); } handleClickOpenImport() { this.setState({ isProcessing: false, shouldShowRFQFeedback: true, RFQBodyType: 'import', }); } handleLoadCustomConf(customconf) { if (!customconf) {return; } const items = this.props.data.items; let new_items = []; let new_items_data = []; this.setState({ ...this.state, columns: { ...this.state.columns, cart: { ...this.state.columns.cart, items: [], }, }, }, function () { customconf.items.map(function (item) { Object.keys(items).map(key => { if (item.pn && item.pn === items[key].name_number) { new_items.push(Object.assign({ ...items[key], }, { id: uuidv4(), options_data: item.options ? item.options : null, })); new_items_data.push({options_data: item.options ? item.options : null}); } }); return item; }); this.setState({ ...this.state, columns: { ...this.state.columns, cart: { ...this.state.columns.cart, items: new_items, itemsData: new_items_data, }, }, currentMode: customconf.type, }); }); } handleClickSubmit(note, email) { const crate = { items: [], type: this.state.currentMode, }; const clonedCart = Array.from(this.state.columns.cart.items); const clonedCartData = Array.from(this.state.columns.cart.itemsData); for (const i in clonedCart) { const item = clonedCart[i]; const item_data = clonedCartData[i]; crate.items.push({ 'pn': item.name_number, 'options': (item_data.options_data && item_data.options) ? FilterOptions(item_data.options, item_data.options_data) : null, }); } const {data} = this.props; this.setState({isProcessing: true}); fetch(data.API_RFQ, { method: "POST", headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ email, note, configuration: JSON.stringify(crate) }) }).then(response => { if (response.status !== 200) { throw Error("Response status is not OK: " + response.status + ".\n" + response); } this.setState({ isProcessing: false, shouldShowRFQFeedback: true, RFQBodyType: 'email', isProcessingComplete: true, }); }).catch(err => { console.error("Request failed, reason:", err) this.setState({isProcessing: false}, () => { alert("We cannot receive your request. Try using the export by coping the configuration and send it to us at sales@m-labs.hk"); }); }) } handleOnDragEnd(result, newAdded) { const { source, destination, } = result; let dragged_items = []; if (source.indexes) { source.indexes.forEach((card_index, _) => { dragged_items.push(itemsUnfoldedList[card_index]); }) } else if (source.index >= 0) { dragged_items.push(itemsUnfoldedList[source.index]); } if (!destination) { if (source.droppableId === 'cart') { this.setState({ ...this.state, newCardJustAdded: false, columns: { ...this.state.columns, [source.droppableId]: { ...this.state.columns[source.droppableId], items: remove( this.state.columns[source.droppableId].items, source.index, ), itemsData: remove( this.state.columns[source.droppableId].itemsData, source.index, ) }, }, }); } return; } switch(source.droppableId) { case 'backlog': if (source.droppableId !== destination.droppableId) { this.setState({ ...this.state, newCardJustAdded: newAdded ? true : false, columns: { ...this.state.columns, [destination.droppableId]: { ...this.state.columns[destination.droppableId], items: copy( this.state.items, this.state.columns[source.droppableId], this.state.columns[destination.droppableId], dragged_items, destination, ), }, }, }); } break; case destination.droppableId: this.setState({ ...this.state, newCardJustAdded: false, columns: { ...this.state.columns, [destination.droppableId]: { ...this.state.columns[destination.droppableId], items: reorder( this.state.columns[destination.droppableId].items, source.index, destination.index, ), itemsData: reorder( this.state.columns[destination.droppableId].itemsData, source.index, destination.index, ), }, }, }); break; default: break; } } handleClickToggleMobileSideMenu() { this.setState({ ...this.state, mobileSideMenuShouldOpen: !this.state.mobileSideMenuShouldOpen, }); } handleClickCloseRFQFeedback() { this.setState({ shouldShowRFQFeedback: false, }); } checkAlerts(newItems) { console.log('--- START CHECKING CRATE WARNING ---'); const { currentMode, crateModeSlots, crateRules, } = this.state; let itemsCloned = Array.from(newItems); const rules = {}; itemsCloned.forEach((elem, idx) => { if (!(idx in itemsCloned)) itemsCloned[idx] = elem; if (idx in this.state.columns.cart.itemsData && this.state.columns.cart.itemsData[idx].options_data) { itemsCloned[idx].options_data = this.state.columns.cart.itemsData[idx].options_data; } }); itemsCloned = FillResources(itemsCloned); itemsCloned = TriggerWarnings(itemsCloned); // check number of slot in crate const nbrOccupied = nbrOccupiedSlotsInCrate(newItems); if (nbrOccupied > crateModeSlots[currentMode]) { rules[crateRules.maxSlot.type] = {...crateRules.maxSlot}; } else if (crateModeSlots[currentMode] === 21 && nbrOccupied <= 10) { rules[crateRules.compactSlot.type] = {...crateRules.compactSlot}; } // update state with rules this.setState({ ...this.state, columns: { ...this.state.columns, cart: { ...this.state.columns.cart, itemsData: itemsCloned, } }, rules: { ...rules, }, }); } render() { const { currency, currentItemHovered, currentMode, crateModeSlots, crateModeItems, items, columns, rules, mobileSideMenuShouldOpen, newCardJustAdded, isProcessing, shouldShowRFQFeedback, RFQBodyType, RFQBodyOrder, isProcessingComplete, } = this.state; const isMobile = window.deviceIsMobile(); const isTouch = window.isTouchEnabled(); return ( } main={( Drag and drop the cards you want into the crate below to see how the combination would look like. Setup card's configuration by tapping at the top of the card, most of the options can be modified after shipment. If you have any issues with this ordering system, or if you need other configurations, email us directly anytime at sales@m-labs.hk. The price is estimated and must be confirmed by a quote.

)} crateMode={ } crate={ } rules={Object.values(rules).filter(rule => rule)}> } summaryPrice={ } form={ }>
)}>
); } }