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 {copyFromBacklog, itemsUnfoldedList, nbrOccupiedSlotsInCrate, remove, reorder} from "./utils";
import {Layout} from "./Layout.jsx";
import {Backlog} from "./Backlog.jsx";
import {OrderPanel} from "./OrderPanel.jsx";
import {OrderSummary} from "./OrderSummary.jsx";
import {OrderForm} from "./OrderForm.jsx";
import {TriggerCrateWarnings, TriggerWarnings} from "./warnings";
import {FillResources} from "./count_resources";
import {CrateList} from "./CrateList.jsx";
/**
* 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.onAddCrate = this.onAddCrate.bind(this);
this.onDeleteCrate = this.onDeleteCrate.bind(this);
this.onCrateModeChange = this.onCrateModeChange.bind(this);
this.onCrateSelected = this.onCrateSelected.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: 'crate0',
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.
*/
console.log("componentDidUpdate")
return;
if (
(prevState.columns.crates !== this.state.columns.crates.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);
}
onCrateChanged(crate_id) {
// kinda silly that hover over cards triggers checkAlerts
this.checkAlerts(crate_id)
}
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) {
/**
* 4 cases:
* - from backlog to one of the crate - add to the correct crate in correct order
* - from one crate to another - delete from one crate and put to another in correct order
* - within one crate - reorder
* - from crate to backlog - delete
* */
const {
source,
destination,
} = result;
/* better move to another function
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]);
}*/
console.log('==> result', result);
// dropped outside the list
if (!destination) {
return;
}
switch (source.droppableId) {
// TODO add delete functionality
case destination.droppableId:
this.setState({
...this.state,
columns: {
...this.state.columns,
crates: {
...this.state.columns.crates,
[destination.droppableId]: {
...this.state.columns.crates[destination.droppableId],
items: reorder(
this.state.columns.crates[source.droppableId].items,
source.index,
destination.index
)}}
}
});
this.onCrateChanged(destination.droppableId);
break;
case 'backlog':
this.setState({
...this.state,
columns: {
...this.state.columns,
crates: {
...this.state.columns.crates,
[destination.droppableId]: {
...this.state.columns.crates[destination.droppableId],
items: copyFromBacklog(
this.state.items,
this.state.columns.crates[destination.droppableId],
source,
destination
)}}
}
});
this.onCrateChanged(destination.droppableId);
break;
default:
this.setState({
columns: move(
this.state.columns[source.droppableId],
this.state.columns[destination.droppableId],
source,
destination
)
});
break;
}
}
handleClickToggleMobileSideMenu() {
this.setState({
...this.state,
mobileSideMenuShouldOpen: !this.state.mobileSideMenuShouldOpen,
});
}
handleClickCloseRFQFeedback() {
this.setState({
shouldShowRFQFeedback: false,
});
}
checkAlerts(crate_id) {
console.log('--- START CHECKING CRATE WARNING ---');
let itemsCloned = Array.from(this.state.columns.crates[crate_id].items);
const crate_warnings = TriggerCrateWarnings(this.state.columns.crates[crate_id]);
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);
// update state with rules
this.setState({
...this.state,
columns: {
...this.state.columns,
crates: {
...this.state.columns.crates,
[crate_id]: {
...this.state.columns.crates[crate_id],
items: itemsCloned,
warnings: crate_warnings
},
}
}
});
}
onAddCrate(id) {
this.setState({
...this.state,
columns: {
...this.state.columns,
crates: {
...this.state.columns.crates,
[id]: {
crate_type: "rack",
items: []
}}
}
});
}
onDeleteCrate(id) {
let new_state = {
...this.state,
columns: {
...this.state.columns,
crates: {
...this.state.columns.crates,
[id]: undefined
}
}};
delete new_state.columns.crates[id];
this.setState(new_state);
}
onCrateSelected(id) {
console.log(id)
this.setState({
active_crate: id
})
}
onCrateModeChange(crate_id, new_mode) {
let new_state = {
...this.state,
columns: {
...this.state.columns,
crates: {
...this.state.columns.crates,
[crate_id]: {
...this.state.columns.crates[crate_id],
crate_type: new_mode
}
}
}};
this.setState(new_state);
}
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 (