forked from M-Labs/web2019
Add flexible crate options
Signed-off-by: Egor Savkin <es@m-labs.hk>
This commit is contained in:
parent
bc81035555
commit
4527189994
|
@ -3,7 +3,6 @@ import {Droppable} from "@hello-pangea/dnd";
|
||||||
import {cartStyle, compareArraysWithIds} from "./utils";
|
import {cartStyle, compareArraysWithIds} from "./utils";
|
||||||
import {ProductCartItem} from "./ProductCartItem";
|
import {ProductCartItem} from "./ProductCartItem";
|
||||||
import {FakePlaceholder} from "./FakePlaceholder";
|
import {FakePlaceholder} from "./FakePlaceholder";
|
||||||
import {FillExtData} from "./options/utils";
|
|
||||||
import {hp_to_slots} from "./count_resources";
|
import {hp_to_slots} from "./count_resources";
|
||||||
import {useShopStore} from "./shop_store";
|
import {useShopStore} from "./shop_store";
|
||||||
|
|
||||||
|
@ -29,12 +28,10 @@ export function Cart({crate_index}) {
|
||||||
const nbrSlots = hp_to_slots(crateParams(crate.crate_mode).hp);
|
const nbrSlots = hp_to_slots(crateParams(crate.crate_mode).hp);
|
||||||
|
|
||||||
const products = crate.items.map((item, index) => {
|
const products = crate.items.map((item, index) => {
|
||||||
const ext_data = FillExtData(crate.items, index);
|
|
||||||
return (
|
return (
|
||||||
<ProductCartItem
|
<ProductCartItem
|
||||||
card_index={index}
|
card_index={index}
|
||||||
crate_index={crate_index}
|
crate_index={crate_index}
|
||||||
ext_data={ext_data}
|
|
||||||
first={index === 0}
|
first={index === 0}
|
||||||
last={index === crate.items.length - 1 && nbrOccupied >= nbrSlots}
|
last={index === crate.items.length - 1 && nbrOccupied >= nbrSlots}
|
||||||
key={item.id}/>
|
key={item.id}/>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {useShopStore} from "./shop_store";
|
||||||
// #!render_count
|
// #!render_count
|
||||||
import {useRenderCount} from "@uidotdev/usehooks";
|
import {useRenderCount} from "@uidotdev/usehooks";
|
||||||
import {CrateFanTray} from "./CrateFanTray";
|
import {CrateFanTray} from "./CrateFanTray";
|
||||||
|
import {CrateOptions} from "./CrateOptions";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,7 +46,7 @@ export function Crate({crate_index}) {
|
||||||
|
|
||||||
<CrateWarnings crate_index={crate_index} />
|
<CrateWarnings crate_index={crate_index} />
|
||||||
|
|
||||||
<CrateFanTray crate_index={crate_index}/>
|
<CrateOptions crate_index={crate_index}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
import {ProcessOptions, ProcessOptionsToData} from "./options/Options";
|
||||||
|
|
||||||
|
export function CrateOptions({crate_index}) {
|
||||||
|
const crate_id = useShopStore((state) => state.crates[crate_index].id);
|
||||||
|
const optionsLogic = useShopStore((state) => state.crate_options);
|
||||||
|
const updateOptions = useShopStore((state) => state.updateCrateOptions);
|
||||||
|
const options_data = useShopStore((state) => state.crates[crate_index].options_data || {});
|
||||||
|
|
||||||
|
console.log(options_data)
|
||||||
|
|
||||||
|
const options = ProcessOptions({
|
||||||
|
options: optionsLogic,
|
||||||
|
data: options_data,
|
||||||
|
id: "crate_options" + crate_id,
|
||||||
|
target: {
|
||||||
|
construct: ((outvar, value) => {
|
||||||
|
// #!options_log
|
||||||
|
console.log("construct", outvar, value, options_data);
|
||||||
|
|
||||||
|
options_data[outvar] = value;
|
||||||
|
}),
|
||||||
|
update: ((outvar, value) => {
|
||||||
|
// #!options_log
|
||||||
|
console.log("update", outvar, value, options_data);
|
||||||
|
|
||||||
|
if (outvar in options_data) options_data[outvar] = value;
|
||||||
|
|
||||||
|
updateOptions(crate_id, {[outvar]: value});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(options)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="crate-bar">
|
||||||
|
{options}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ export function Shop() {
|
||||||
const renderCount = useRenderCount();
|
const renderCount = useRenderCount();
|
||||||
|
|
||||||
const addCardFromBacklog = useShopStore((state) => state.addCardFromBacklog);
|
const addCardFromBacklog = useShopStore((state) => state.addCardFromBacklog);
|
||||||
|
const initExtData = useShopStore((state) => state.initExtData);
|
||||||
const moveCard = useShopStore((state) => state.moveCard);
|
const moveCard = useShopStore((state) => state.moveCard);
|
||||||
const deleteCard = useShopStore((state) => state.deleteCard);
|
const deleteCard = useShopStore((state) => state.deleteCard);
|
||||||
const cardIndexById = useShopStore((state) => state.cardIndexById);
|
const cardIndexById = useShopStore((state) => state.cardIndexById);
|
||||||
|
@ -38,6 +39,7 @@ export function Shop() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addCardFromBacklog(null, [cardIndexById("eem_pwr_mod"), cardIndexById("kasli")], -1, true);
|
addCardFromBacklog(null, [cardIndexById("eem_pwr_mod"), cardIndexById("kasli")], -1, true);
|
||||||
|
initExtData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// #!render_count
|
// #!render_count
|
||||||
|
|
|
@ -3,10 +3,11 @@ import React from "react";
|
||||||
import {useShopStore} from "./shop_store";
|
import {useShopStore} from "./shop_store";
|
||||||
import {SummaryCrateHeader} from "./SummaryCrateHeader";
|
import {SummaryCrateHeader} from "./SummaryCrateHeader";
|
||||||
import {SummaryCrateCard} from "./SummaryCrateCard";
|
import {SummaryCrateCard} from "./SummaryCrateCard";
|
||||||
|
import {SummaryCratePricedOptions} from "./SummaryCratePricedOptions";
|
||||||
|
|
||||||
// #!render_count
|
// #!render_count
|
||||||
import {useRenderCount} from "@uidotdev/usehooks";
|
import {useRenderCount} from "@uidotdev/usehooks";
|
||||||
import {SummaryCrateFanTray} from "./SummaryCrateFanTray";
|
|
||||||
|
|
||||||
export function SummaryCrate({crate_index}) {
|
export function SummaryCrate({crate_index}) {
|
||||||
// #!render_count
|
// #!render_count
|
||||||
|
@ -26,7 +27,8 @@ export function SummaryCrate({crate_index}) {
|
||||||
{range(0, crate_len).map((index, _i) =>
|
{range(0, crate_len).map((index, _i) =>
|
||||||
<SummaryCrateCard crate_index={crate_index} card_index={index} key={"summary_crate_" + crate_id + "_" +index} />
|
<SummaryCrateCard crate_index={crate_index} card_index={index} key={"summary_crate_" + crate_id + "_" +index} />
|
||||||
)}
|
)}
|
||||||
<SummaryCrateFanTray crate_index={crate_index}/>
|
|
||||||
|
<SummaryCratePricedOptions crate_index={crate_index}/>
|
||||||
</tbody>
|
</tbody>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
import {formatMoney} from "./utils";
|
||||||
|
import React from "react";
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
import {ProcessOptionsToData} from "./options/Options";
|
||||||
|
|
||||||
|
// #!render_count
|
||||||
|
import {useRenderCount} from "@uidotdev/usehooks";
|
||||||
|
|
||||||
|
export function SummaryCratePricedOptions({crate_index}) {
|
||||||
|
// #!render_count
|
||||||
|
const renderCount = useRenderCount();
|
||||||
|
|
||||||
|
const currency = useShopStore((state) => state.currency);
|
||||||
|
const crate_id = useShopStore((state) => state.crates[crate_index].id);
|
||||||
|
const optionsPrices = useShopStore((state) => state.crate_prices);
|
||||||
|
const updateOptions = useShopStore((state) => state.updateCrateOptions);
|
||||||
|
const options_data = useShopStore((state) => state.crates[crate_index].options_data || {});
|
||||||
|
|
||||||
|
const options = ProcessOptionsToData({options: optionsPrices, data: options_data});
|
||||||
|
|
||||||
|
console.log(options, options_data, optionsPrices)
|
||||||
|
|
||||||
|
// #!render_count
|
||||||
|
console.log("SummaryCratePricedOptions renders: ", renderCount)
|
||||||
|
|
||||||
|
return options.map((option, i) => (
|
||||||
|
<tr key={"summary_crate_" + crate_id +"option_" + option.id}>
|
||||||
|
<td className="item-card-name">
|
||||||
|
<span style={{
|
||||||
|
'display': 'inline-block', 'width': '16px',
|
||||||
|
}}> </span>
|
||||||
|
<div>{option.title}</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td className="price">
|
||||||
|
<div className="d-inline-flex align-content-center">
|
||||||
|
{`${currency} ${formatMoney(option.price)}`}
|
||||||
|
|
||||||
|
<button onClick={() => updateOptions(crate_id, option.disable_patch)}>
|
||||||
|
<img src="/images/shop/icon-remove.svg" className="d-block"/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div style={{'width': '45px', 'height': '20px'}} className="d-inline"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
));
|
||||||
|
}
|
|
@ -49,7 +49,7 @@ export function ProcessOptionsToData({options, data}) {
|
||||||
options: option_item,
|
options: option_item,
|
||||||
data: data,
|
data: data,
|
||||||
}))
|
}))
|
||||||
).flat();
|
).filter((item, i) => !!item).flat();
|
||||||
} else if (options_t === "object") {
|
} else if (options_t === "object") {
|
||||||
if (true_type_of(options.title) === "string") {
|
if (true_type_of(options.title) === "string") {
|
||||||
return options;
|
return options;
|
||||||
|
@ -57,6 +57,7 @@ export function ProcessOptionsToData({options, data}) {
|
||||||
return ProcessOptionsToData({options: json_logic_apply(options, data), data: data});
|
return ProcessOptionsToData({options: json_logic_apply(options, data), data: data});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw Error("Incompatible type for the option: " + options_t)
|
//throw Error("Incompatible type for the option: " + options_t)
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -23,6 +23,15 @@ class Switch extends Component {
|
||||||
this.props.target.update(this.props.outvar, new_checked);
|
this.props.target.update(this.props.outvar, new_checked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props, current_state) {
|
||||||
|
if (current_state.checked !== props.data[props.outvar]) {
|
||||||
|
return {
|
||||||
|
checked: props.data[props.outvar]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let key = this.props.id + this.props.outvar;
|
let key = this.props.id + this.props.outvar;
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {componentsList} from "./components/components";
|
||||||
// https://stackoverflow.com/a/70511311
|
// https://stackoverflow.com/a/70511311
|
||||||
export const true_type_of = (obj) => Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
|
export const true_type_of = (obj) => Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
|
||||||
|
|
||||||
export function FillExtData(data, index) {
|
export function FillExtCardData(data, index) {
|
||||||
return {
|
return {
|
||||||
has_other_dio: data.filter((value, item_index) => index !== item_index && value.name &&value.name.endsWith("-TTL")).length > 0,
|
has_other_dio: data.filter((value, item_index) => index !== item_index && value.name &&value.name.endsWith("-TTL")).length > 0,
|
||||||
has_dds: data.filter(((value, _) => value.name === "DDS" && value.name_number === "4410" && (!value.options_data || !value.options_data.mono_eem))).length > 0,
|
has_dds: data.filter(((value, _) => value.name === "DDS" && value.name_number === "4410" && (!value.options_data || !value.options_data.mono_eem))).length > 0,
|
||||||
|
@ -12,6 +12,12 @@ export function FillExtData(data, index) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function FillExtCrateData(crate) {
|
||||||
|
return {
|
||||||
|
crate_mode: crate.crate_mode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function FilterOptions(options, data) {
|
export function FilterOptions(options, data) {
|
||||||
let options_t = true_type_of(options);
|
let options_t = true_type_of(options);
|
||||||
let target = {};
|
let target = {};
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
import {createWithEqualityFn} from "zustand/traditional";
|
import {createWithEqualityFn} from "zustand/traditional";
|
||||||
import {data as shared_data, itemsUnfoldedList} from "./utils";
|
import {data as shared_data, itemsUnfoldedList} from "./utils";
|
||||||
import {true_type_of} from "./options/utils";
|
import {FillExtCrateData, true_type_of} from "./options/utils";
|
||||||
import {v4 as uuidv4} from "uuid";
|
import {v4 as uuidv4} from "uuid";
|
||||||
import {FillResources} from "./count_resources";
|
import {FillResources} from "./count_resources";
|
||||||
import {FillExtData} from "./options/utils";
|
import {FillExtCardData} from "./options/utils";
|
||||||
import {TriggerCrateWarnings, TriggerWarnings} from "./warnings";
|
import {TriggerCrateWarnings, TriggerWarnings} from "./warnings";
|
||||||
import {Validation, validateEmail, validateNote, validateJSONInput} from "./validate";
|
import {Validation, validateEmail, validateNote, validateJSONInput} from "./validate";
|
||||||
import {CratesToJSON, JSONToCrates} from "./json_porter";
|
import {CratesToJSON, JSONToCrates} from "./json_porter";
|
||||||
|
@ -38,6 +38,49 @@ const useCrateOptions = ((set, get) => ({
|
||||||
crate_options: shared_data.crateOptions.options,
|
crate_options: shared_data.crateOptions.options,
|
||||||
crate_prices: shared_data.crateOptions.prices,
|
crate_prices: shared_data.crateOptions.prices,
|
||||||
|
|
||||||
|
fillExtCrateData: (crate_id) => set(state => ({
|
||||||
|
crates: state.crates.map((crate, _i) => {
|
||||||
|
if (crate_id === crate.id) {
|
||||||
|
const previous_options = crate.options_data || {};
|
||||||
|
return {
|
||||||
|
...crate,
|
||||||
|
options_data: {
|
||||||
|
...previous_options,
|
||||||
|
ext_data: FillExtCrateData(crate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else return crate;
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
|
||||||
|
_updateCrateOption: (crate_id, new_options) => set(state => ({
|
||||||
|
crates: state.crates.map((crate, _i) => {
|
||||||
|
if (crate_id === crate.id) {
|
||||||
|
const previous_options = crate.options_data || {};
|
||||||
|
console.log(crate_id, new_options, {
|
||||||
|
...crate,
|
||||||
|
options_data: {
|
||||||
|
...previous_options,
|
||||||
|
...new_options
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
...crate,
|
||||||
|
options_data: {
|
||||||
|
...previous_options,
|
||||||
|
...new_options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else return crate;
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
|
||||||
|
updateCrateOptions: (crate_id, new_options) => {
|
||||||
|
get().fillExtCrateData(crate_id);
|
||||||
|
get()._updateCrateOption(crate_id, new_options);
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const useOrderOptions = ((set, get) => ({
|
const useOrderOptions = ((set, get) => ({
|
||||||
|
@ -45,14 +88,20 @@ const useOrderOptions = ((set, get) => ({
|
||||||
orderPrices: shared_data.crateOptions.prices,
|
orderPrices: shared_data.crateOptions.prices,
|
||||||
order_options_data: {},
|
order_options_data: {},
|
||||||
|
|
||||||
|
// in case of future needs
|
||||||
fillOrderExtData: _ => {},
|
fillOrderExtData: _ => {},
|
||||||
|
|
||||||
_updateOrderOption: (new_options) => set(state => ({
|
_updateOrderOptions: (new_options) => set(state => ({
|
||||||
order_options_data: {
|
order_options_data: {
|
||||||
...state.order_options_data,
|
...state.order_options_data,
|
||||||
...new_options
|
...new_options
|
||||||
}
|
}
|
||||||
}))
|
})),
|
||||||
|
|
||||||
|
updateOrderOptions: (new_options) => {
|
||||||
|
get().fillOrderExtData();
|
||||||
|
get()._updateOrderOptions(new_options);
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const useLayout = ((set, get) => ({
|
const useLayout = ((set, get) => ({
|
||||||
|
@ -400,7 +449,7 @@ const useCart = ((set, get) => ({
|
||||||
itemsCopy = itemsCopy.map((item, index) => {
|
itemsCopy = itemsCopy.map((item, index) => {
|
||||||
if (!item.options) return item;
|
if (!item.options) return item;
|
||||||
if (!item.options_data) item.options_data = {};
|
if (!item.options_data) item.options_data = {};
|
||||||
item.options_data.ext_data = FillExtData(itemsCopy, index);
|
item.options_data.ext_data = FillExtCardData(itemsCopy, index);
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
@ -430,12 +479,14 @@ const useCart = ((set, get) => ({
|
||||||
const crate_id = "crate" + get().crates.length;
|
const crate_id = "crate" + get().crates.length;
|
||||||
get()._newCrate(crate_id)
|
get()._newCrate(crate_id)
|
||||||
get().fillExtData(crate_id);
|
get().fillExtData(crate_id);
|
||||||
|
get().fillExtCrateData(crate_id);
|
||||||
get().fillWarnings(crate_id);
|
get().fillWarnings(crate_id);
|
||||||
},
|
},
|
||||||
|
|
||||||
setCrateMode: (id, mode) => {
|
setCrateMode: (id, mode) => {
|
||||||
get()._setCrateMode(id, mode)
|
get()._setCrateMode(id, mode)
|
||||||
get().fillExtData(id);
|
get().fillExtData(id);
|
||||||
|
get().fillExtCrateData(id);
|
||||||
get().fillWarnings(id);
|
get().fillWarnings(id);
|
||||||
get().setActiveCrate(id);
|
get().setActiveCrate(id);
|
||||||
},
|
},
|
||||||
|
@ -497,6 +548,14 @@ const useCart = ((set, get) => ({
|
||||||
|
|
||||||
fanTrayAvailableByIndex: (crate_index) => {
|
fanTrayAvailableByIndex: (crate_index) => {
|
||||||
return get().fanTrayAvailableForMode(get().crates[crate_index].crate_mode);
|
return get().fanTrayAvailableForMode(get().crates[crate_index].crate_mode);
|
||||||
|
},
|
||||||
|
|
||||||
|
initExtData: () => {
|
||||||
|
get().fillOrderExtData();
|
||||||
|
get().crates.forEach((crate, _i) => {
|
||||||
|
get().fillExtData(crate.id);
|
||||||
|
get().fillExtCrateData(crate.id);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
@ -34,14 +34,16 @@ const shop_data = {
|
||||||
{"==": [{"var": "ext_data.crate_mode"}, "rack",]},
|
{"==": [{"var": "ext_data.crate_mode"}, "rack",]},
|
||||||
{type: "Switch", args: {
|
{type: "Switch", args: {
|
||||||
title: "Add fan tray",
|
title: "Add fan tray",
|
||||||
outvar: "nuc",
|
outvar: "fan_tray",
|
||||||
tip: "Add 1U 84hp fan tray (to be mounted under the crate) to improve cooling. " +
|
tip: "Add 1U 84hp fan tray (to be mounted under the crate) to improve cooling. " +
|
||||||
"Fans need 220VAC 50/60Hz power. 3 fans, 167m³/h air flow.",
|
"Fans need 220VAC 50/60Hz power. 3 fans, 167m³/h air flow.",
|
||||||
fallback: false,
|
fallback: false
|
||||||
}}
|
}}
|
||||||
]},
|
]},
|
||||||
],
|
],
|
||||||
prices: [{"if": [{"and": [{"var": "fan_tray"}, {"==": [{"var": "ext_data.crate_mode"}, "rack",]}]}, {title: "Add fan tray", price: 470}]}]
|
prices: [{"if": [
|
||||||
|
{"and": [{"var": "fan_tray"}, {"==": [{"var": "ext_data.crate_mode"}, "rack",]}]},
|
||||||
|
{title: "Add fan tray", price: 470, disable_patch: {"fan_tray": false}, id: "fan_tray"}]}]
|
||||||
},
|
},
|
||||||
|
|
||||||
orderOptions: {
|
orderOptions: {
|
||||||
|
|
Loading…
Reference in New Issue