forked from M-Labs/web2019
Start refactor to state manager zustand
Signed-off-by: Egor Savkin <es@m-labs.hk>
This commit is contained in:
parent
9bdaca2ca9
commit
9edf410e4d
|
@ -24,7 +24,8 @@
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"webpack": "^5.89.0",
|
"webpack": "^5.89.0",
|
||||||
"webpack-cli": "^5.1.4"
|
"webpack-cli": "^5.1.4",
|
||||||
|
"zustand": "^4.4.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
|
@ -4915,6 +4916,34 @@
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zustand": {
|
||||||
|
"version": "4.4.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.7.tgz",
|
||||||
|
"integrity": "sha512-QFJWJMdlETcI69paJwhSMJz7PPWjVP8Sjhclxmxmxv/RYI7ZOvR5BHX+ktH0we9gTWQMxcne8q1OY8xxz604gw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"use-sync-external-store": "1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.7.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": ">=16.8",
|
||||||
|
"immer": ">=9.0",
|
||||||
|
"react": ">=16.8"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"immer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,8 @@
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"webpack": "^5.89.0",
|
"webpack": "^5.89.0",
|
||||||
"webpack-cli": "^5.1.4",
|
"webpack-cli": "^5.1.4",
|
||||||
"json-logic-js": "^2.0.2"
|
"json-logic-js": "^2.0.2",
|
||||||
|
"zustand": "^4.4.7"
|
||||||
},
|
},
|
||||||
"babel": {
|
"babel": {
|
||||||
"presets": [
|
"presets": [
|
||||||
|
|
|
@ -1,118 +1,106 @@
|
||||||
import React, {PureComponent} from 'react';
|
import React from 'react';
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import {v4 as uuidv4} from "uuid";
|
import {v4 as uuidv4} from "uuid";
|
||||||
import {Droppable} from "@hello-pangea/dnd";
|
import {Droppable} from "@hello-pangea/dnd";
|
||||||
import {ProductItem} from "./ProductItem.jsx";
|
import {ProductItem} from "./ProductItem.jsx";
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that renders the backlog in the aside
|
* Component that renders the backlog in the aside
|
||||||
*/
|
*/
|
||||||
export class Backlog extends PureComponent {
|
export function Backlog() {
|
||||||
|
const {
|
||||||
static get propTypes() {
|
currency,
|
||||||
return {
|
data,
|
||||||
currency: PropTypes.string,
|
items,
|
||||||
data: PropTypes.object.isRequired,
|
onClickAddItem,
|
||||||
items: PropTypes.object,
|
onClickToggleMobileSideMenu,
|
||||||
isMobile: PropTypes.bool,
|
isMobile,
|
||||||
onClickAddItem: PropTypes.func,
|
} = useShopStore(state=> ({
|
||||||
onClickToggleMobileSideMenu: PropTypes.func,
|
currency: state.currency,
|
||||||
};
|
data: state.groups,
|
||||||
}
|
items: state.cards,
|
||||||
|
onClickAddItem: state.addCardFromBacklog,
|
||||||
static get defaultProps() {
|
onClickToggleMobileSideMenu: state.switchSideMenu,
|
||||||
return {
|
isMobile: state.isMobile
|
||||||
items: {},
|
}));
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
currency,
|
|
||||||
data,
|
|
||||||
items,
|
|
||||||
onClickAddItem,
|
|
||||||
onClickToggleMobileSideMenu,
|
|
||||||
isMobile,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
|
|
||||||
const ordered_groups = data.categories.map(groupItem => ({
|
const ordered_groups = data.categories.map(groupItem => ({
|
||||||
name: groupItem.name,
|
name: groupItem.name,
|
||||||
items: groupItem.itemIds.map(itemId => items[itemId])
|
items: groupItem.itemIds.map(itemId => items[itemId])
|
||||||
}));
|
}));
|
||||||
let item_index = -1;
|
let item_index = -1;
|
||||||
const groups = ordered_groups.map((group, g_index) => {
|
const groups = ordered_groups.map((group, g_index) => {
|
||||||
return (
|
return (
|
||||||
<div className="accordion-item" key={`${group.name}`}>
|
<div className="accordion-item" key={`${group.name}`}>
|
||||||
<h2 className="accordion-header">
|
<h2 className="accordion-header">
|
||||||
<button className="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
<button className="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||||
data-bs-target={`#collapse${g_index}`} aria-expanded="true"
|
data-bs-target={`#collapse${g_index}`} aria-expanded="true"
|
||||||
aria-controls={`collapse${g_index}`}>
|
aria-controls={`collapse${g_index}`}>
|
||||||
{group.name}
|
{group.name}
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id={`collapse${g_index}`} className="accordion-collapse collapse" aria-labelledby="headingOne"
|
||||||
|
data-bs-parent="#accordion_categories">
|
||||||
|
<div className="accordion-body">
|
||||||
|
{group.items.map(item => {
|
||||||
|
item_index++;
|
||||||
|
return (
|
||||||
|
<ProductItem
|
||||||
|
key={item.id}
|
||||||
|
id={uuidv4()}
|
||||||
|
index={item_index}
|
||||||
|
name={`${item.name_number} ${item.name}`}
|
||||||
|
name_codename={item.name_codename}
|
||||||
|
price={item.price}
|
||||||
|
currency={currency}
|
||||||
|
image={`/images/${item.image}`}
|
||||||
|
specs={item.specs}
|
||||||
|
datasheet_file={item.datasheet_file}
|
||||||
|
datasheet_name={item.datasheet_name}
|
||||||
|
onClickAddItem={onClickAddItem}
|
||||||
|
></ProductItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Droppable
|
||||||
|
droppableId={data.id}
|
||||||
|
isDropDisabled={true}>
|
||||||
|
|
||||||
|
{(provided) => (
|
||||||
|
<div
|
||||||
|
className="backlog-container"
|
||||||
|
ref={provided.innerRef}
|
||||||
|
{...provided.droppableProps}>
|
||||||
|
|
||||||
|
{isMobile ? (
|
||||||
|
<div className="mobileCloseMenu">
|
||||||
|
<button onClick={onClickToggleMobileSideMenu}>
|
||||||
|
<img src="/images/shop/icon-close-white.svg" alt="add"/>
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
|
||||||
<div id={`collapse${g_index}`} className="accordion-collapse collapse" aria-labelledby="headingOne"
|
|
||||||
data-bs-parent="#accordion_categories">
|
|
||||||
<div className="accordion-body">
|
|
||||||
{group.items.map(item => {
|
|
||||||
item_index++;
|
|
||||||
return (
|
|
||||||
<ProductItem
|
|
||||||
key={item.id}
|
|
||||||
id={uuidv4()}
|
|
||||||
index={item_index}
|
|
||||||
name={`${item.name_number} ${item.name}`}
|
|
||||||
name_codename={item.name_codename}
|
|
||||||
price={item.price}
|
|
||||||
currency={currency}
|
|
||||||
image={`/images/${item.image}`}
|
|
||||||
specs={item.specs}
|
|
||||||
datasheet_file={item.datasheet_file}
|
|
||||||
datasheet_name={item.datasheet_name}
|
|
||||||
onClickAddItem={onClickAddItem}
|
|
||||||
></ProductItem>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="accordion accordion-flush" id="accordion_categories">
|
||||||
|
{groups}
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
{provided.placeholder && (
|
||||||
<Droppable
|
<div style={{display: 'none'}}>
|
||||||
droppableId={data.id}
|
{provided.placeholder}
|
||||||
isDropDisabled={true}>
|
|
||||||
|
|
||||||
{(provided) => (
|
|
||||||
<div
|
|
||||||
className="backlog-container"
|
|
||||||
ref={provided.innerRef}
|
|
||||||
{...provided.droppableProps}>
|
|
||||||
|
|
||||||
{isMobile ? (
|
|
||||||
<div className="mobileCloseMenu">
|
|
||||||
<button onClick={onClickToggleMobileSideMenu}>
|
|
||||||
<img src="/images/shop/icon-close-white.svg" alt="add"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className="accordion accordion-flush" id="accordion_categories">
|
|
||||||
{groups}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{provided.placeholder && (
|
</Droppable>
|
||||||
<div style={{display: 'none'}}>
|
);
|
||||||
{provided.placeholder}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</Droppable>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -4,45 +4,42 @@ import {cartStyle} from "./utils";
|
||||||
import {ProductCartItem} from "./ProductCartItem.jsx";
|
import {ProductCartItem} from "./ProductCartItem.jsx";
|
||||||
import {FakePlaceholder} from "./FakePlaceholder.jsx";
|
import {FakePlaceholder} from "./FakePlaceholder.jsx";
|
||||||
import {FillExtData} from "./options/utils";
|
import {FillExtData} from "./options/utils";
|
||||||
import {crate_type_to_hp, hp_to_slots, resource_counters} from "./count_resources";
|
import {CountResources, crate_type_to_hp, hp_to_slots, resource_counters} from "./count_resources";
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
import {TriggerCardWarnings} from "./warnings";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays a list of <ProductCartItem>
|
* Component that displays a list of <ProductCartItem>
|
||||||
*/
|
*/
|
||||||
export function Cart({isMobile, isTouch, data, onToggleOverlayRemove, onClickRemoveItem, onCardUpdate, onClickItem}) {
|
export function Cart({crate_index}) {
|
||||||
const nbrOccupied = hp_to_slots(resource_counters.hp(data.items, -1));
|
// isMobile, isTouch, crate, onToggleOverlayRemove, onClickRemoveItem, onCardUpdate, onClickItem
|
||||||
const nbrSlots = hp_to_slots(crate_type_to_hp(data.crate_type));
|
const {crate} = useShopStore(state => ({
|
||||||
|
crate: state.crates[crate_index]
|
||||||
|
}));
|
||||||
|
|
||||||
|
const nbrOccupied = hp_to_slots(resource_counters.hp(crate.items, -1));
|
||||||
|
const nbrSlots = hp_to_slots(crate_type_to_hp(crate.crate_mode));
|
||||||
console.log(nbrOccupied, nbrSlots);
|
console.log(nbrOccupied, nbrSlots);
|
||||||
|
|
||||||
const products = data.items.map((item, index) => {
|
const products = crate.items.map((item, index) => {
|
||||||
let itemData;
|
const ext_data = FillExtData(crate.items, index);
|
||||||
let ext_data = FillExtData(data.items, index);
|
const resources = CountResources(crate.items, index);
|
||||||
if (data.items && index in data.items) {
|
const warnings = TriggerCardWarnings(crate.items, index, resources);
|
||||||
itemData = data.items[index];
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<ProductCartItem
|
<ProductCartItem
|
||||||
isMobile={isMobile}
|
card_index={index}
|
||||||
isTouch={isTouch}
|
crate_index={crate_index}
|
||||||
hovered={item.hovered}
|
|
||||||
key={item.id}
|
|
||||||
id={item.id}
|
|
||||||
index={index}
|
|
||||||
first={index === 0}
|
|
||||||
last={index === data.items.length - 1 && nbrOccupied >= nbrSlots}
|
|
||||||
data={itemData}
|
|
||||||
ext_data={ext_data}
|
ext_data={ext_data}
|
||||||
onToggleOverlayRemove={onToggleOverlayRemove}
|
first={index === 0}
|
||||||
onClickRemoveItem={onClickRemoveItem}
|
last={index === crate.items.length - 1 && nbrOccupied >= nbrSlots}
|
||||||
onCardUpdate={onCardUpdate}
|
resources={resources}
|
||||||
onClickItem={onClickItem}
|
warnings={warnings}
|
||||||
model={item}>
|
key={item.id}/>
|
||||||
</ProductCartItem>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Droppable droppableId={data.id} direction="horizontal">
|
<Droppable droppableId={crate.id} direction="horizontal">
|
||||||
|
|
||||||
{(provided, snapshot) => (
|
{(provided, snapshot) => (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -2,51 +2,42 @@ import React from 'react';
|
||||||
import {Cart} from "./Cart.jsx";
|
import {Cart} from "./Cart.jsx";
|
||||||
import {CrateMode} from "./CrateMode.jsx";
|
import {CrateMode} from "./CrateMode.jsx";
|
||||||
import {CrateWarnings} from "./CrateWarnings.jsx";
|
import {CrateWarnings} from "./CrateWarnings.jsx";
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
import {TriggerCrateWarnings} from "./warnings";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays the main crate with reminder rules.
|
* Component that displays the main crate with reminder rules.
|
||||||
* It includes <Cart> and rules
|
* It includes <Cart> and rules
|
||||||
*/
|
*/
|
||||||
export function Crate({
|
export function Crate({crate_index}) {
|
||||||
data,
|
const {
|
||||||
handleToggleOverlayRemove,
|
onDeleteCrate,
|
||||||
handleDeleteItem,
|
crate
|
||||||
handleShowOverlayRemove,
|
} = useShopStore(state => ({
|
||||||
handleCardsUpdated,
|
onDeleteCrate: state.delCrate,
|
||||||
isMobile,
|
crate: state.crates[crate_index]
|
||||||
isTouch,
|
}))
|
||||||
onDelete,
|
|
||||||
onModeChange
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<div className="crate">
|
<div className="crate">
|
||||||
|
|
||||||
<CrateMode current={data.mode} onChange={onModeChange}/>
|
<CrateMode crate_index={crate_index}/>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
Delete crate
|
Delete crate
|
||||||
<button style={{width: "32px"}} onClick={() => onDelete(data.id)}>
|
<button style={{width: "32px"}} onClick={() => onDeleteCrate(crate.id)}>
|
||||||
<img src="/images/shop/icon-remove.svg" alt="remove"/>
|
<img src="/images/shop/icon-remove.svg" alt="remove"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="crate-products">
|
<div className="crate-products">
|
||||||
|
|
||||||
<Cart
|
<Cart crate_index={crate_index}/>
|
||||||
data={data}
|
|
||||||
isMobile={isMobile}
|
|
||||||
isTouch={isTouch}
|
|
||||||
onToggleOverlayRemove={handleToggleOverlayRemove}
|
|
||||||
onClickRemoveItem={handleDeleteItem}
|
|
||||||
onClickItem={handleShowOverlayRemove}
|
|
||||||
onCardUpdate={handleCardsUpdated}>
|
|
||||||
</Cart>
|
|
||||||
|
|
||||||
{1 || (rules && rules.length > 0) && (
|
{1 || (rules && rules.length > 0) && (
|
||||||
<CrateWarnings/>
|
<CrateWarnings crate_index={crate_index} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,34 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Accordion} from "react-bootstrap";
|
import {Accordion} from "react-bootstrap";
|
||||||
import {Crate} from "./Crate.jsx";
|
import {Crate} from "./Crate.jsx";
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
|
||||||
export function CrateList({crates, active_crate, isMobile, isTouch, onAddCrate, onDeleteCrate, onModeChange, onCrateSelect}) {
|
export function CrateList() {
|
||||||
const onClickAdd = (_) => {
|
const {
|
||||||
onAddCrate("crate" + Object.entries(crates).length);
|
crates,
|
||||||
}
|
active_crate,
|
||||||
|
onAddCrate,
|
||||||
|
setActiveCrate,
|
||||||
|
} = useShopStore(state=> ({
|
||||||
|
crates: state.crates,
|
||||||
|
active_crate: state.active_crate,
|
||||||
|
onAddCrate: state.newCrate,
|
||||||
|
setActiveCrate: state.setActiveCrate,
|
||||||
|
}));
|
||||||
return (
|
return (
|
||||||
<Accordion defaultActiveKey={active_crate}>
|
<Accordion defaultActiveKey={active_crate}>
|
||||||
{Object.entries(crates).map(([crate_id, crate], index) =>
|
{crates.map((crate, index) =>
|
||||||
<Accordion.Item eventKey={crate_id} key={`crate${index}`} >
|
<Accordion.Item eventKey={crate.id} key={"accordion"+crate.id} >
|
||||||
<Accordion.Header onClick={() => onCrateSelect(crate_id)}>Crate #{`${index}`}</Accordion.Header>
|
<Accordion.Header onClick={() => setActiveCrate(crate.id)}>Crate #{`${index}`}</Accordion.Header>
|
||||||
<Accordion.Body>
|
<Accordion.Body>
|
||||||
<Crate
|
<Crate crate_index={index}/>
|
||||||
data={{id: crate_id, ...crate}}
|
|
||||||
isTouch={isTouch}
|
|
||||||
isMobile={isMobile}
|
|
||||||
onDelete={onDeleteCrate}
|
|
||||||
onModeChange={(new_mode) => onModeChange(crate_id, new_mode)}
|
|
||||||
/>
|
|
||||||
</Accordion.Body>
|
</Accordion.Body>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
)}
|
)}
|
||||||
<Accordion.Item eventKey="last">
|
<Accordion.Item eventKey="last">
|
||||||
<Accordion.Header>
|
<Accordion.Header>
|
||||||
Add new crate
|
Add new crate
|
||||||
<button style={{width: "32px"}} onClick={onClickAdd}>
|
<button style={{width: "32px"}} onClick={onAddCrate}>
|
||||||
<img src="/images/shop/icon-add.svg" alt="add" />
|
<img src="/images/shop/icon-add.svg" alt="add" />
|
||||||
</button>
|
</button>
|
||||||
</Accordion.Header>
|
</Accordion.Header>
|
||||||
|
|
|
@ -1,19 +1,25 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {data as shared_data} from "./utils";
|
import {useShopStore} from "./shop_store";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays crate modes
|
* Component that displays crate modes
|
||||||
*/
|
*/
|
||||||
export function CrateMode({current, onChange}) {
|
export function CrateMode({crate_index}) {
|
||||||
|
const {modes_order, crate_modes, crate, setMode} = useShopStore(state => ({
|
||||||
|
modes_order: state.modes_order,
|
||||||
|
crate_modes: state.crate_modes,
|
||||||
|
crate: state.crates[crate_index].crate_mode,
|
||||||
|
setMode: state.setCrateMode
|
||||||
|
}))
|
||||||
return (
|
return (
|
||||||
<div className="crate-mode">
|
<div className="crate-mode">
|
||||||
{shared_data.crateModeOrder.map(item => (
|
{modes_order.map((mode_name, _) => (
|
||||||
<a
|
<a
|
||||||
key={item}
|
key={mode_name}
|
||||||
className={current === item ? 'active' : ''}
|
className={crate.crate_mode === mode_name ? 'active' : ''}
|
||||||
onClick={() => onChange(item)}
|
onClick={() => setMode(crate.id, mode_name)}
|
||||||
href="#"
|
href="#"
|
||||||
role="button">{shared_data.crateModes[item].name}</a>
|
role="button">{crate_modes[mode_name].name}</a>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import {TriggerCrateWarnings} from "./warnings";
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
|
||||||
export function CrateWarnings() {
|
export function CrateWarnings({crate_index}) {
|
||||||
|
const crate = useShopStore(state => (state.crates[crate_index]))
|
||||||
|
const crate_warnings = TriggerCrateWarnings(crate);
|
||||||
|
// TODO UI/colors
|
||||||
return (
|
return (
|
||||||
<div className="crate-info">
|
<div className="crate-info">
|
||||||
{rules.map((rule, index) => (
|
{crate_warnings.map((rule, index) => (
|
||||||
<p key={index} className="rule" style={{'color': rule.color ? rule.color : 'inherit'}}>
|
<p key={index} className="rule" style={{'color': "red"}}>
|
||||||
<img src={`/images${rule.icon}`} /> <i><strong>{rule.name}:</strong> {rule.message}</i>
|
<img src={`/images${rule.icon}`} /> <i><strong>{rule.name}:</strong> {rule.message}</i>
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -1,27 +1,12 @@
|
||||||
import React, {PureComponent} from 'react';
|
import React from 'react';
|
||||||
import PropTypes from "prop-types";
|
import {useShopStore} from "./shop_store";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that provides a base layout (aside/main) for the page.
|
* Component that provides a base layout (aside/main) for the page.
|
||||||
*/
|
*/
|
||||||
export class Layout extends PureComponent {
|
|
||||||
|
|
||||||
static get propTypes() {
|
|
||||||
return {
|
|
||||||
aside: PropTypes.any,
|
|
||||||
main: PropTypes.any,
|
|
||||||
mobileSideMenuShouldOpen: PropTypes.bool,
|
|
||||||
isMobile: PropTypes.bool,
|
|
||||||
newCardJustAdded: PropTypes.bool,
|
|
||||||
onClickToggleMobileSideMenu: PropTypes.func,
|
|
||||||
onClickCloseRFQFeedback: PropTypes.func,
|
|
||||||
RFQBodyType: PropTypes.string,
|
|
||||||
RFQBodyOrder: PropTypes.string,
|
|
||||||
onClickLoadCustomConf: PropTypes.func,
|
|
||||||
items: PropTypes.object,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
export function Layout({aside, main}) {
|
||||||
static get defaultProps() {
|
static get defaultProps() {
|
||||||
return {
|
return {
|
||||||
mobileSideMenuShouldOpen: false,
|
mobileSideMenuShouldOpen: false,
|
||||||
|
@ -270,4 +255,32 @@ export class Layout extends PureComponent {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
export function Layout({aside, main}) {
|
||||||
|
const mobileSideMenuShouldOpen = useShopStore((state) => state.sideMenuOpen);
|
||||||
|
const onClickToggleMobileSideMenu = useShopStore((state) => state.switchSideMenu);
|
||||||
|
const newCardJustAdded = useShopStore((state) => state.newCardJustAdded);
|
||||||
|
const isMobile = useShopStore((state) => state.isMobile);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="layout">
|
||||||
|
|
||||||
|
<aside className={'aside ' + (mobileSideMenuShouldOpen ? 'menu-opened' : '')}>{aside}</aside>
|
||||||
|
|
||||||
|
{mobileSideMenuShouldOpen ? (
|
||||||
|
<section className="main" onClick={onClickToggleMobileSideMenu}>{main}</section>
|
||||||
|
) : (
|
||||||
|
<section className="main">{main}</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isMobile && newCardJustAdded ? (
|
||||||
|
<div className="feedback-add-success">
|
||||||
|
✓ added
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,71 +1,49 @@
|
||||||
import React, {PureComponent} from 'react'
|
import React from 'react'
|
||||||
import PropTypes from "prop-types";
|
import {OrderSummary} from "./OrderSummary";
|
||||||
|
import {OrderForm} from "./OrderForm";
|
||||||
|
import {CrateList} from "./CrateList";
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that renders all things for order.
|
* Component that renders all things for order.
|
||||||
* It acts like-a layout, this component do nothing more.
|
* It acts like-a layout, this component do nothing more.
|
||||||
*/
|
*/
|
||||||
export class OrderPanel extends PureComponent {
|
export function OrderPanel({title, description}) {
|
||||||
|
const isMobile = useShopStore((state) => state.isMobile);
|
||||||
|
const onClickToggleMobileSideMenu = useShopStore((state) => state.switchSideMenu);
|
||||||
|
const onClickOpenImport = useShopStore((state) => state.openImport);
|
||||||
|
|
||||||
static get propTypes() {
|
return (<section className="panel">
|
||||||
return {
|
|
||||||
title: PropTypes.string,
|
|
||||||
description: PropTypes.element,
|
|
||||||
cratesList: PropTypes.element,
|
|
||||||
summaryPrice: PropTypes.element,
|
|
||||||
form: PropTypes.element,
|
|
||||||
isMobile: PropTypes.bool,
|
|
||||||
onClickToggleMobileSideMenu: PropTypes.func,
|
|
||||||
onClickOpenImport: PropTypes.func,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
<h2>{title}</h2>
|
||||||
const {
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
cratesList,
|
|
||||||
summaryPrice,
|
|
||||||
form,
|
|
||||||
isMobile,
|
|
||||||
onClickToggleMobileSideMenu,
|
|
||||||
onClickOpenImport,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
<div className="control">
|
||||||
<section className="panel">
|
{description}
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2>{title}</h2>
|
<div>
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-outline-primary m-0 mb-2"
|
||||||
|
style={{'cursor': 'pointer'}}
|
||||||
|
onClick={onClickOpenImport}>Import JSON
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="control">
|
{isMobile ? (
|
||||||
{description}
|
<div className="mobileBtnDisplaySideMenu">
|
||||||
</div>
|
<button onClick={onClickToggleMobileSideMenu}>
|
||||||
|
<img src="/images/shop/icon-add.svg" alt="add"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div>
|
<CrateList/>
|
||||||
<button
|
|
||||||
className="btn btn-sm btn-outline-primary m-0 mb-2"
|
|
||||||
style={{'cursor': 'pointer'}}
|
|
||||||
onClick={onClickOpenImport}>Import JSON
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isMobile ? (
|
<section className="summary">
|
||||||
<div className="mobileBtnDisplaySideMenu">
|
<OrderSummary/>
|
||||||
<button onClick={onClickToggleMobileSideMenu}>
|
|
||||||
<img src="/images/shop/icon-add.svg" alt="add"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{cratesList}
|
<OrderForm/>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section className="summary">
|
</section>);
|
||||||
{summaryPrice}
|
|
||||||
|
|
||||||
{form}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,186 +1,122 @@
|
||||||
import React, {PureComponent} from 'react'
|
import React from 'react'
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import {Draggable} from "@hello-pangea/dnd";
|
import {Draggable} from "@hello-pangea/dnd";
|
||||||
import {DialogPopup} from "./options/DialogPopup.jsx";
|
import {DialogPopup} from "./options/DialogPopup.jsx";
|
||||||
import {productStyle} from "./utils";
|
import {productStyle} from "./utils";
|
||||||
import {Resources} from "./Resources.jsx";
|
import {Resources} from "./Resources.jsx";
|
||||||
import {CardWarnings} from "./CardWarnings.jsx";
|
import {CardWarnings} from "./CardWarnings.jsx";
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that renders a product.
|
* Component that renders a product.
|
||||||
* Used in the crate
|
* Used in the crate
|
||||||
*/
|
*/
|
||||||
export class ProductCartItem extends PureComponent {
|
export function ProductCartItem({card_index, crate_index, ext_data, first, last, resources, warnings}) {
|
||||||
|
const {card, crate, highlighted, setHighlight, removeHighlight, onCardUpdate, onCardRemove} = useShopStore(state => ({
|
||||||
|
card: state.crates[crate_index].items[card_index],
|
||||||
|
highlighted: state.crates[crate_index].id === state.highlighted.crate && card_index === state.highlighted.card,
|
||||||
|
crate: state.crates[crate_index],
|
||||||
|
setHighlight: state.highlightCard,
|
||||||
|
removeHighlight: state.highlightReset,
|
||||||
|
onCardUpdate: state.updateOptions,
|
||||||
|
onCardRemove: state.deleteCard
|
||||||
|
}))
|
||||||
|
|
||||||
static get propTypes() {
|
let options, options_data;
|
||||||
return {
|
//const warnings = data && data.show_warnings;
|
||||||
isMobile: PropTypes.bool,
|
|
||||||
isTouch: PropTypes.bool,
|
if (data && data.options) {
|
||||||
hovered: PropTypes.bool,
|
options = data.options;
|
||||||
first: PropTypes.bool,
|
if (!data.options_data) crate.options_data = {};
|
||||||
last: PropTypes.bool,
|
options_data = crate.options_data;
|
||||||
index: PropTypes.number.isRequired,
|
options_data.ext_data = ext_data;
|
||||||
model: PropTypes.object.isRequired,
|
|
||||||
data: PropTypes.object,
|
|
||||||
ext_data: PropTypes.object,
|
|
||||||
resources: PropTypes.object,
|
|
||||||
onToggleOverlayRemove: PropTypes.func,
|
|
||||||
onClickRemoveItem: PropTypes.func,
|
|
||||||
onClickItem: PropTypes.func,
|
|
||||||
onCardUpdate: PropTypes.func,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static get defaultProps() {
|
return (
|
||||||
return {
|
<Draggable draggableId={card.id} index={card_index}>
|
||||||
hovered: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
{(provided, snapshot) => (
|
||||||
super(props);
|
<div
|
||||||
this.handleOnMouseEnterRemoveItem = this.handleOnMouseEnterRemoveItem.bind(this);
|
ref={provided.innerRef}
|
||||||
this.handleOnMouseLeaveRemoveItem = this.handleOnMouseLeaveRemoveItem.bind(this);
|
{...provided.draggableProps}
|
||||||
this.handleOnClickRemoveItem = this.handleOnClickRemoveItem.bind(this);
|
{...provided.dragHandleProps}
|
||||||
this.handleOnClickItem = this.handleOnClickItem.bind(this);
|
style={{
|
||||||
}
|
...productStyle(
|
||||||
|
provided.draggableProps.style,
|
||||||
|
snapshot,
|
||||||
|
true,
|
||||||
|
!!highlighted,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setHighlight(crate.id, card_index)}
|
||||||
|
onMouseLeave={removeHighlight}
|
||||||
|
>
|
||||||
|
|
||||||
handleOnMouseEnterRemoveItem(index, e) {
|
{/* warning container */}
|
||||||
if (this.props.onToggleOverlayRemove && !this.props.isMobile) {
|
|
||||||
this.props.onToggleOverlayRemove(index, true);
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOnMouseLeaveRemoveItem(index, e) {
|
<div className="progress-container warning d-flex justify-content-evenly">
|
||||||
if (this.props.onToggleOverlayRemove && !this.props.isMobile) {
|
{warnings && warnings.length > 0 &&
|
||||||
this.props.onToggleOverlayRemove(index, false);
|
(<CardWarnings warnings={warnings} prefix={card_index}/>)
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOnClickItem(index, e) {
|
{options && (
|
||||||
if (this.props.onClickItem && this.props.isTouch) {
|
<DialogPopup
|
||||||
this.props.onClickItem(index);
|
options={options}
|
||||||
}
|
data={options_data}
|
||||||
e.preventDefault();
|
options_class={card.options_class}
|
||||||
}
|
key={"popover" + card_index}
|
||||||
|
id={"popover" + card_index}
|
||||||
|
big={card.size === "big"}
|
||||||
|
first={first}
|
||||||
|
last={last}
|
||||||
|
target={{
|
||||||
|
construct: ((outvar, value) => {
|
||||||
|
// console.log("construct", outvar, value, options_data);
|
||||||
|
options_data[outvar] = value;
|
||||||
|
}),
|
||||||
|
update: ((outvar, value) => {
|
||||||
|
// console.log("update", outvar, value, options_data);
|
||||||
|
if (outvar in options_data) options_data[outvar] = value;
|
||||||
|
onCardUpdate(crate.id, card_index, {[outvar]: value});
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
handleOnClickRemoveItem(index, e) {
|
<h6>{card.name_number}</h6>
|
||||||
if (this.props.onClickRemoveItem) {
|
|
||||||
this.props.onClickRemoveItem(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
hovered,
|
|
||||||
model,
|
|
||||||
data,
|
|
||||||
index,
|
|
||||||
first,
|
|
||||||
last,
|
|
||||||
ext_data,
|
|
||||||
onCardUpdate,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
let options, options_data;
|
|
||||||
const warnings = data && data.show_warnings;
|
|
||||||
const resources = data && data.counted_resources;
|
|
||||||
|
|
||||||
if (data && data.options) {
|
|
||||||
options = data.options;
|
|
||||||
if (!data.options_data) data.options_data = {};
|
|
||||||
options_data = data.options_data;
|
|
||||||
options_data.ext_data = ext_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Draggable draggableId={model.id} index={index}>
|
|
||||||
|
|
||||||
{(provided, snapshot) => (
|
|
||||||
<div
|
<div
|
||||||
ref={provided.innerRef}
|
onMouseEnter={() => setHighlight(crate.id, card_index)}
|
||||||
{...provided.draggableProps}
|
onClick={() => setHighlight(crate.id, card_index)}
|
||||||
{...provided.dragHandleProps}
|
|
||||||
style={{ ...productStyle(
|
|
||||||
provided.draggableProps.style,
|
|
||||||
snapshot,
|
|
||||||
true,
|
|
||||||
!!hovered,
|
|
||||||
!!model.selected,
|
|
||||||
true
|
|
||||||
)}}
|
|
||||||
onMouseEnter={this.handleOnMouseEnterRemoveItem.bind(this, index)}
|
|
||||||
onMouseLeave={this.handleOnMouseLeaveRemoveItem.bind(this, index)}
|
|
||||||
>
|
>
|
||||||
|
|
||||||
{/* warning container */}
|
<img
|
||||||
|
className='item-cart'
|
||||||
<div className="progress-container warning d-flex justify-content-evenly">
|
src={`/images${card.image}`}/>
|
||||||
{warnings && warnings.length > 0 &&
|
|
||||||
(<CardWarnings warnings={warnings} prefix={index} />)
|
|
||||||
}
|
|
||||||
|
|
||||||
{options && (
|
|
||||||
<DialogPopup
|
|
||||||
options={options}
|
|
||||||
data={options_data}
|
|
||||||
options_class={model.options_class}
|
|
||||||
key={"popover" + index}
|
|
||||||
id={"popover" + index}
|
|
||||||
big={model.size === "big"}
|
|
||||||
first={first}
|
|
||||||
last={last}
|
|
||||||
target={{
|
|
||||||
construct: ((outvar, value) => {
|
|
||||||
// console.log("construct", outvar, value, options_data);
|
|
||||||
options_data[outvar] = value;
|
|
||||||
}),
|
|
||||||
update: ((outvar, value) => {
|
|
||||||
// console.log("update", outvar, value, options_data);
|
|
||||||
if (outvar in options_data) options_data[outvar] = value;
|
|
||||||
onCardUpdate();
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h6>{model.name_number}</h6>
|
|
||||||
|
|
||||||
<div
|
|
||||||
onMouseEnter={this.handleOnMouseEnterRemoveItem.bind(this, index)}
|
|
||||||
onClick={this.handleOnClickItem.bind(this, index)}
|
|
||||||
>
|
|
||||||
|
|
||||||
<img
|
|
||||||
className='item-cart'
|
|
||||||
src={`/images${model.image}`} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* remove container */}
|
|
||||||
<div
|
|
||||||
style={{'display': model.showOverlayRemove ? 'flex':'none'}}
|
|
||||||
className="overlayRemove"
|
|
||||||
onClick={this.handleOnClickRemoveItem.bind(this, index)}>
|
|
||||||
|
|
||||||
<img src="/images/shop/icon-remove.svg" alt="rm"/>
|
|
||||||
|
|
||||||
<p>Remove</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* progression container */}
|
|
||||||
{resources && (
|
|
||||||
<Resources resources={resources}/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
</Draggable>
|
{/* remove container */}
|
||||||
);
|
<div
|
||||||
}
|
style={{'display': highlighted ? 'flex' : 'none'}}
|
||||||
|
className="overlayRemove"
|
||||||
|
onClick={() => onCardRemove(crate.id, card_index)}>
|
||||||
|
|
||||||
|
<img src="/images/shop/icon-remove.svg" alt="rm"/>
|
||||||
|
|
||||||
|
<p>Remove</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* progression container */}
|
||||||
|
{resources && (
|
||||||
|
<Resources resources={resources}/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {CrateList} from "./CrateList.jsx";
|
||||||
* Component that render the entire shop
|
* Component that render the entire shop
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class Shop extends PureComponent {
|
export function Shop() {
|
||||||
|
|
||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
return {
|
return {
|
||||||
|
@ -37,7 +37,7 @@ export class Shop extends PureComponent {
|
||||||
this.handleMouseEnterItem = this.handleMouseEnterItem.bind(this);
|
this.handleMouseEnterItem = this.handleMouseEnterItem.bind(this);
|
||||||
this.handleMouseLeaveItem = this.handleMouseLeaveItem.bind(this);
|
this.handleMouseLeaveItem = this.handleMouseLeaveItem.bind(this);
|
||||||
this.handleClickAddItem = this.handleClickAddItem.bind(this);
|
this.handleClickAddItem = this.handleClickAddItem.bind(this);
|
||||||
this.checkAlerts = this.checkAlerts.bind(this);
|
this.checkAlertsInNewState = this.checkAlertsInNewState.bind(this);
|
||||||
this.handleClickSelectItem = this.handleClickSelectItem.bind(this);
|
this.handleClickSelectItem = this.handleClickSelectItem.bind(this);
|
||||||
this.handleClickSubmit = this.handleClickSubmit.bind(this);
|
this.handleClickSubmit = this.handleClickSubmit.bind(this);
|
||||||
this.handleToggleOverlayRemove = this.handleToggleOverlayRemove.bind(this);
|
this.handleToggleOverlayRemove = this.handleToggleOverlayRemove.bind(this);
|
||||||
|
@ -47,11 +47,11 @@ export class Shop extends PureComponent {
|
||||||
this.handleClickShowOrder = this.handleClickShowOrder.bind(this);
|
this.handleClickShowOrder = this.handleClickShowOrder.bind(this);
|
||||||
this.handleClickOpenImport = this.handleClickOpenImport.bind(this);
|
this.handleClickOpenImport = this.handleClickOpenImport.bind(this);
|
||||||
this.handleLoadCustomConf = this.handleLoadCustomConf.bind(this);
|
this.handleLoadCustomConf = this.handleLoadCustomConf.bind(this);
|
||||||
this.handleCardsUpdated = this.handleCardsUpdated.bind(this);
|
|
||||||
this.onAddCrate = this.onAddCrate.bind(this);
|
this.onAddCrate = this.onAddCrate.bind(this);
|
||||||
this.onDeleteCrate = this.onDeleteCrate.bind(this);
|
this.onDeleteCrate = this.onDeleteCrate.bind(this);
|
||||||
this.onCrateModeChange = this.onCrateModeChange.bind(this);
|
this.onCrateModeChange = this.onCrateModeChange.bind(this);
|
||||||
this.onCrateSelected = this.onCrateSelected.bind(this);
|
this.onCrateSelected = this.onCrateSelected.bind(this);
|
||||||
|
this.onCrateChanged = this.onCrateChanged.bind(this);
|
||||||
|
|
||||||
this.timer = null;
|
this.timer = null;
|
||||||
this.timer_remove = null;
|
this.timer_remove = null;
|
||||||
|
@ -111,14 +111,10 @@ export class Shop extends PureComponent {
|
||||||
onCrateChanged(crate_id) {
|
onCrateChanged(crate_id) {
|
||||||
// kinda silly that hover over cards triggers checkAlerts
|
// kinda silly that hover over cards triggers checkAlerts
|
||||||
|
|
||||||
this.checkAlerts(crate_id)
|
this.checkAlertsInNewState(crate_id, this.state);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCardsUpdated() {
|
|
||||||
this.checkAlerts(this.state.columns.cart.items);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCrateModeChange(mode) {
|
handleCrateModeChange(mode) {
|
||||||
this.setState({
|
this.setState({
|
||||||
currentMode: mode,
|
currentMode: mode,
|
||||||
|
@ -398,7 +394,22 @@ export class Shop extends PureComponent {
|
||||||
switch (source.droppableId) {
|
switch (source.droppableId) {
|
||||||
// TODO add delete functionality
|
// TODO add delete functionality
|
||||||
case destination.droppableId:
|
case destination.droppableId:
|
||||||
this.setState({
|
/*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.checkAlertsInNewState(destination.droppableId, {
|
||||||
...this.state,
|
...this.state,
|
||||||
columns: {
|
columns: {
|
||||||
...this.state.columns,
|
...this.state.columns,
|
||||||
|
@ -413,10 +424,9 @@ export class Shop extends PureComponent {
|
||||||
)}}
|
)}}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.onCrateChanged(destination.droppableId);
|
|
||||||
break;
|
break;
|
||||||
case 'backlog':
|
case 'backlog':
|
||||||
this.setState({
|
/*this.setState({
|
||||||
...this.state,
|
...this.state,
|
||||||
columns: {
|
columns: {
|
||||||
...this.state.columns,
|
...this.state.columns,
|
||||||
|
@ -431,8 +441,23 @@ export class Shop extends PureComponent {
|
||||||
destination
|
destination
|
||||||
)}}
|
)}}
|
||||||
}
|
}
|
||||||
|
});*/
|
||||||
|
this.checkAlertsInNewState(destination.droppableId, {
|
||||||
|
...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;
|
break;
|
||||||
default:
|
default:
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -460,17 +485,16 @@ export class Shop extends PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAlerts(crate_id) {
|
checkAlertsInNewState(crate_id, new_state) {
|
||||||
console.log('--- START CHECKING CRATE WARNING ---');
|
console.log('--- START CHECKING CRATE WARNING ---');
|
||||||
|
|
||||||
let itemsCloned = Array.from(this.state.columns.crates[crate_id].items);
|
let itemsCloned = Array.from(new_state.columns.crates[crate_id].items);
|
||||||
|
|
||||||
const crate_warnings = TriggerCrateWarnings(this.state.columns.crates[crate_id]);
|
const crate_warnings = TriggerCrateWarnings(new_state.columns.crates[crate_id]);
|
||||||
|
|
||||||
itemsCloned.forEach((elem, idx) => {
|
itemsCloned.forEach((elem, _idx) => {
|
||||||
if (!(idx in itemsCloned)) itemsCloned[idx] = elem;
|
if (!elem.options_data && !!elem.options) {
|
||||||
if (idx in this.state.columns.cart.itemsData && this.state.columns.cart.itemsData[idx].options_data) {
|
elem.options_data = {}
|
||||||
itemsCloned[idx].options_data = this.state.columns.cart.itemsData[idx].options_data;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -481,11 +505,11 @@ export class Shop extends PureComponent {
|
||||||
this.setState({
|
this.setState({
|
||||||
...this.state,
|
...this.state,
|
||||||
columns: {
|
columns: {
|
||||||
...this.state.columns,
|
...new_state.columns,
|
||||||
crates: {
|
crates: {
|
||||||
...this.state.columns.crates,
|
...new_state.columns.crates,
|
||||||
[crate_id]: {
|
[crate_id]: {
|
||||||
...this.state.columns.crates[crate_id],
|
...new_state.columns.crates[crate_id],
|
||||||
items: itemsCloned,
|
items: itemsCloned,
|
||||||
warnings: crate_warnings
|
warnings: crate_warnings
|
||||||
},
|
},
|
||||||
|
@ -545,98 +569,21 @@ export class Shop extends PureComponent {
|
||||||
this.setState(new_state);
|
this.setState(new_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
|
||||||
|
|
||||||
const {
|
return (
|
||||||
currency,
|
<DragDropContext onDragEnd={this.handleOnDragEnd}>
|
||||||
currentItemHovered,
|
<Layout
|
||||||
currentMode,
|
aside={
|
||||||
crateModeSlots,
|
<Backlog/>
|
||||||
crateModeItems,
|
}
|
||||||
items,
|
main={(
|
||||||
columns,
|
<OrderPanel
|
||||||
rules,
|
title="Order hardware"
|
||||||
mobileSideMenuShouldOpen,
|
description={(<p className="description">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 <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a>. The price is estimated and must be confirmed by a quote.</p>)}
|
||||||
newCardJustAdded,
|
/>
|
||||||
isProcessing,
|
)}>
|
||||||
shouldShowRFQFeedback,
|
</Layout>
|
||||||
RFQBodyType,
|
</DragDropContext>
|
||||||
RFQBodyOrder,
|
);
|
||||||
isProcessingComplete,
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const isMobile = window.deviceIsMobile();
|
|
||||||
const isTouch = window.isTouchEnabled();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DragDropContext onDragEnd={this.handleOnDragEnd}>
|
|
||||||
|
|
||||||
<Layout
|
|
||||||
showRFQFeedback={shouldShowRFQFeedback}
|
|
||||||
RFQBodyType={RFQBodyType}
|
|
||||||
RFQBodyOrder={RFQBodyOrder}
|
|
||||||
className="shop"
|
|
||||||
mobileSideMenuShouldOpen={mobileSideMenuShouldOpen}
|
|
||||||
isMobile={isMobile}
|
|
||||||
newCardJustAdded={newCardJustAdded}
|
|
||||||
onClickToggleMobileSideMenu={this.handleClickToggleMobileSideMenu}
|
|
||||||
onClickCloseRFQFeedback={this.handleClickCloseRFQFeedback}
|
|
||||||
onClickLoadCustomConf={this.handleLoadCustomConf}
|
|
||||||
items={items}
|
|
||||||
aside={
|
|
||||||
<Backlog
|
|
||||||
currency={currency}
|
|
||||||
items={items}
|
|
||||||
data={columns.backlog}
|
|
||||||
onClickAddItem={this.handleClickAddItem}
|
|
||||||
onClickToggleMobileSideMenu={this.handleClickToggleMobileSideMenu}
|
|
||||||
isMobile={isMobile}>
|
|
||||||
</Backlog>
|
|
||||||
}
|
|
||||||
main={(
|
|
||||||
<OrderPanel
|
|
||||||
onClickToggleMobileSideMenu={this.handleClickToggleMobileSideMenu}
|
|
||||||
onClickOpenImport={this.handleClickOpenImport}
|
|
||||||
isMobile={isMobile}
|
|
||||||
title="Order hardware"
|
|
||||||
description={(<p className="description">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 <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a>. The price is estimated and must be confirmed by a quote.</p>)}
|
|
||||||
cratesList={
|
|
||||||
<CrateList
|
|
||||||
crates={columns.crates}
|
|
||||||
isTouch={isTouch}
|
|
||||||
isMobile={isMobile}
|
|
||||||
onAddCrate={this.onAddCrate}
|
|
||||||
onDeleteCrate={this.onDeleteCrate}
|
|
||||||
onModeChange={this.onCrateModeChange}
|
|
||||||
onCrateSelect={this.onCrateSelected}
|
|
||||||
active_crate={this.state.active_crate}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
summaryPrice={
|
|
||||||
<OrderSummary
|
|
||||||
currency={currency}
|
|
||||||
crates={columns.crates}
|
|
||||||
onMouseEnterItem={this.handleMouseEnterItem}
|
|
||||||
onMouseLeaveItem={this.handleMouseLeaveItem}
|
|
||||||
onDeleteItem={this.handleDeleteItem}
|
|
||||||
onDeleteAllItems={this.handleDeleteAllItems}
|
|
||||||
onClickSelectItem={this.handleClickSelectItem}>
|
|
||||||
</OrderSummary>
|
|
||||||
}
|
|
||||||
form={
|
|
||||||
<OrderForm
|
|
||||||
isProcessingComplete={isProcessingComplete}
|
|
||||||
processingComplete={this.handleProcessingComplete}
|
|
||||||
isProcessing={isProcessing}
|
|
||||||
onClickSubmit={this.handleClickSubmit}
|
|
||||||
onClickShow={this.handleClickShowOrder}>
|
|
||||||
</OrderForm>
|
|
||||||
}>
|
|
||||||
</OrderPanel>
|
|
||||||
)}>
|
|
||||||
</Layout>
|
|
||||||
|
|
||||||
</DragDropContext>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -53,7 +53,7 @@ export const resource_counters = {
|
||||||
"hp": CounterFactory("hp"),
|
"hp": CounterFactory("hp"),
|
||||||
}
|
}
|
||||||
|
|
||||||
function CountResources(data, index) {
|
export function CountResources(data, index) {
|
||||||
if (!data[index].resources) return null;
|
if (!data[index].resources) return null;
|
||||||
let result = [];
|
let result = [];
|
||||||
data[index].resources.forEach((item, _) => {
|
data[index].resources.forEach((item, _) => {
|
||||||
|
|
|
@ -0,0 +1,192 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import {create} from "zustand";
|
||||||
|
import {data, itemsUnfoldedList} from "./utils";
|
||||||
|
import {true_type_of} from "./options/utils";
|
||||||
|
import {v4 as uuidv4} from "uuid";
|
||||||
|
import {FillResources} from "./count_resources";
|
||||||
|
import {TriggerCrateWarnings, TriggerWarnings} from "./warnings";
|
||||||
|
|
||||||
|
|
||||||
|
const useBacklog = ((set, get) => ({
|
||||||
|
cards: data.items,
|
||||||
|
groups: data.columns.backlog,
|
||||||
|
cards_list: itemsUnfoldedList,
|
||||||
|
currency: data.currency
|
||||||
|
}));
|
||||||
|
|
||||||
|
const useCrateModes = ((set, get) => ({
|
||||||
|
crate_modes: data.crateModes,
|
||||||
|
modes_order: data.crateModeOrder
|
||||||
|
}));
|
||||||
|
|
||||||
|
const useLayout = ((set, get) => ({
|
||||||
|
isTouch: window.isTouchEnabled(),
|
||||||
|
isMobile: window.deviceIsMobile(),
|
||||||
|
sideMenuIsOpen: false,
|
||||||
|
newCardJustAdded: false,
|
||||||
|
importIsOpen: false,
|
||||||
|
|
||||||
|
switchSideMenu: () => set(state => ({
|
||||||
|
sideMenuIsOpen: !state.sideMenuIsOpen
|
||||||
|
})),
|
||||||
|
openImport: () => set(state => ({
|
||||||
|
importIsOpen: true
|
||||||
|
})),
|
||||||
|
closeImport: () => set(state => ({
|
||||||
|
importIsOpen: false
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
const useSubmitForm = ((set, get) => ({
|
||||||
|
// TODO think about it
|
||||||
|
isProcessing: false,
|
||||||
|
shouldShowRFQFeedback: true,
|
||||||
|
RFQBodyType: 'email',
|
||||||
|
isProcessingComplete: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const useCart = ((set, get) => ({
|
||||||
|
crates: data.columns.crates,
|
||||||
|
active_crate: "crate0",
|
||||||
|
highlighted: {
|
||||||
|
crate: "",
|
||||||
|
card: 0
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
newCrate: () => set((state) => ({crates: state.crates.concat({
|
||||||
|
id: "crate"+state.crates.length,
|
||||||
|
crate_mode: "rack",
|
||||||
|
items: [],
|
||||||
|
warnings: []
|
||||||
|
})})),
|
||||||
|
delCrate: (id) => set(state => ({
|
||||||
|
crates: state.crates.filter((crate => crate.id !== id))
|
||||||
|
})),
|
||||||
|
setCrateMode: (id, mode) => set(state => ({
|
||||||
|
crates: state.crates.map((crate, _i) => {
|
||||||
|
if (crate.id === id) {
|
||||||
|
return {
|
||||||
|
...crate,
|
||||||
|
crate_mode: mode
|
||||||
|
}
|
||||||
|
} else return crate;
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
setActiveCrate: (id) => set(state => ({active_crate: id})),
|
||||||
|
addCardFromBacklog: (crate_to, index_from, index_to) => set(state => {
|
||||||
|
const take_from = (true_type_of(index_from) === "array" ? index_from : [index_from]).map((item, _i) => (state.cards_list[item]));
|
||||||
|
const dest = crate_to || state.active_crate;
|
||||||
|
return {
|
||||||
|
crates: state.crates.map((crate, _i) => {
|
||||||
|
if (dest === crate.id) {
|
||||||
|
return {
|
||||||
|
...crate,
|
||||||
|
items: crate.items.toSpliced(index_to, 0, ...take_from.map((card_name, _) => {
|
||||||
|
return {...state.cards[card_name], id: uuidv4()}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} else return crate;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
moveCard: (crate_from, index_from, crate_to, index_to) => set(state => {
|
||||||
|
const the_card = state.crates.find((crate, _) => crate_from === crate.id )[index_from];
|
||||||
|
const del_card = crate_from === crate_to ? 1 : 0;
|
||||||
|
return {
|
||||||
|
crates: state.crates.map((crate, _i) => {
|
||||||
|
if (crate_to === crate.id) {
|
||||||
|
return {
|
||||||
|
...crate,
|
||||||
|
items: crate.items.toSpliced(index_to, del_card, the_card)
|
||||||
|
}
|
||||||
|
} else if (crate_from === crate.id) {
|
||||||
|
return {
|
||||||
|
...crate,
|
||||||
|
items: crate.items.toSpliced(index_to, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else return crate;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
deleteCard: (crate, index) => set(state => ({
|
||||||
|
crates: state.crates.map((crate, _i) => {
|
||||||
|
if (crate === crate.id) {
|
||||||
|
return {
|
||||||
|
...crate,
|
||||||
|
items: crate.items.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else return crate;
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
clearCrate: (id) => set(state => ({
|
||||||
|
crates: state.crates.map((crate, _i) => {
|
||||||
|
if (id === crate.id) {
|
||||||
|
return {
|
||||||
|
...crate,
|
||||||
|
items: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else return crate;
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
updateOptions: (crate, index, new_options) => set(state => ({
|
||||||
|
crates: state.crates.map((crate, _i) => {
|
||||||
|
if (crate === crate.id) {
|
||||||
|
let itemsCopy = Array.from(crate.items);
|
||||||
|
itemsCopy[index].options_data = {...itemsCopy[index].options_data, ...new_options};
|
||||||
|
return {
|
||||||
|
...crate,
|
||||||
|
items: itemsCopy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else return crate;
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
highlightCard: (crate, index) => set(state => ({
|
||||||
|
highlighted: {
|
||||||
|
crate: crate,
|
||||||
|
card: index
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
highlightReset: () => set(state => ({
|
||||||
|
highlighted: {
|
||||||
|
crate: "",
|
||||||
|
card: 0
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
|
||||||
|
fillWarnings: (crate) => set(state => ({
|
||||||
|
// actually seems to be just render-time action, no need to put data in it,
|
||||||
|
// though needs to be optimized to be done only on real crate updates
|
||||||
|
crates: state.crates.map((crate, _i) => {
|
||||||
|
if (crate === crate.id) {
|
||||||
|
let itemsCopy = Array.from(crate.items);
|
||||||
|
itemsCopy = FillResources(itemsCopy);
|
||||||
|
itemsCopy = TriggerWarnings(itemsCopy);
|
||||||
|
const crate_warnings = TriggerCrateWarnings(crate);
|
||||||
|
return {
|
||||||
|
...crate,
|
||||||
|
items: itemsCopy,
|
||||||
|
warnings: crate_warnings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else return crate;
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
// TODO load and save jsons?
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
export const useShopStore = create((...params) => ({
|
||||||
|
...useBacklog(...params),
|
||||||
|
...useCrateModes(...params),
|
||||||
|
...useCart(...params),
|
||||||
|
...useSubmitForm(...params),
|
||||||
|
...useLayout(...params)
|
||||||
|
}))
|
|
@ -98,6 +98,20 @@ const Types = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function TriggerCardWarnings(data, index, precounted) {
|
||||||
|
const element = data[index];
|
||||||
|
return element.warnings
|
||||||
|
.map((warning, _) => {
|
||||||
|
if (!!Types[warning])
|
||||||
|
return Types[warning].trigger(data, index, precounted) ? {trigger: undefined, name: warning, ...Types[warning]} : null;
|
||||||
|
else
|
||||||
|
return Types.default;
|
||||||
|
})
|
||||||
|
.filter((warning, _) => {
|
||||||
|
return !!warning
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function TriggerWarnings(data) {
|
export function TriggerWarnings(data) {
|
||||||
return data.map((element, index) => {
|
return data.map((element, index) => {
|
||||||
if (!element.warnings) return element;
|
if (!element.warnings) return element;
|
||||||
|
|
|
@ -1185,13 +1185,12 @@ const shop_data = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
"crates": {
|
"crates": [{
|
||||||
"crate0": {
|
id: "crate0",
|
||||||
crate_type: "rack",
|
crate_mode: "rack",
|
||||||
items: [],
|
items: [],
|
||||||
warnings: []
|
warnings: []
|
||||||
}
|
}]
|
||||||
}
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue