Order and crate level options #117
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
|
@ -13,24 +13,23 @@
|
||||||
"url": "https://git.m-labs.hk/M-Labs/web2019.git"
|
"url": "https://git.m-labs.hk/M-Labs/web2019.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.23.0",
|
"@babel/cli": "^7.23.9",
|
||||||
"@babel/core": "^7.23.2",
|
"@babel/core": "^7.23.9",
|
||||||
"@babel/preset-env": "^7.23.2",
|
"@babel/preset-env": "^7.23.9",
|
||||||
"@babel/preset-react": "^7.22.15",
|
"@babel/preset-react": "^7.23.3",
|
||||||
"babel-loader": "^9.1.3",
|
"babel-loader": "^9.1.3",
|
||||||
"babel-preset-minify": "^0.5.2",
|
"babel-preset-minify": "^0.5.2",
|
||||||
"bootstrap": "^5.3.0",
|
"bootstrap": "^5.3.2",
|
||||||
"jquery": "^3.7.0",
|
"jquery": "^3.7.1",
|
||||||
"prop-types": "^15.8.1",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-bootstrap": "^2.9.1",
|
"react-bootstrap": "^2.10.0",
|
||||||
"@hello-pangea/dnd": "^16.5.0",
|
"@hello-pangea/dnd": "^16.5.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"webpack": "^5.89.0",
|
"webpack": "^5.90.1",
|
||||||
"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",
|
"zustand": "^4.5.0",
|
||||||
"@uidotdev/usehooks":"^2.4.1",
|
"@uidotdev/usehooks":"^2.4.1",
|
||||||
"webpack-preprocessor-loader": "^1.3.0"
|
"webpack-preprocessor-loader": "^1.3.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -384,6 +384,26 @@ button {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-bar {
|
||||||
|
width: 90%;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 0;
|
||||||
|
input[type="text"] {
|
||||||
|
padding: 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-group {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shop-radio-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.crate {
|
.crate {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,8 @@
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root-shop .panel .summary>.summary-form form {
|
#root-shop .panel .summary>.summary-form form,
|
||||||
|
#root-shop .panel .summary>.summary-form .order-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +230,8 @@
|
||||||
font-size: .7rem;
|
font-size: .7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root-shop .panel .summary>.summary-form form {
|
#root-shop .panel .summary>.summary-form form,
|
||||||
|
#root-shop .panel .summary>.summary-form .order-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,7 +348,8 @@
|
||||||
font-size: .7rem;
|
font-size: .7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root-shop .panel .summary>.summary-form form {
|
#root-shop .panel .summary>.summary-form form,
|
||||||
|
#root-shop .panel .summary>.summary-form .order-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -611,7 +614,8 @@
|
||||||
font-size: .7rem;
|
font-size: .7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root-shop .panel .summary>.summary-form form {
|
#root-shop .panel .summary>.summary-form form,
|
||||||
|
#root-shop .panel .summary>.summary-form .order-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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}/>
|
||||||
|
|
|
@ -3,10 +3,10 @@ import {Cart} from "./Cart";
|
||||||
import {CrateMode} from "./CrateMode";
|
import {CrateMode} from "./CrateMode";
|
||||||
import {CrateWarnings} from "./CrateWarnings";
|
import {CrateWarnings} from "./CrateWarnings";
|
||||||
import {useShopStore} from "./shop_store";
|
import {useShopStore} from "./shop_store";
|
||||||
|
import {CrateOptions} from "./CrateOptions";
|
||||||
|
|
||||||
// #!render_count
|
// #!render_count
|
||||||
import {useRenderCount} from "@uidotdev/usehooks";
|
import {useRenderCount} from "@uidotdev/usehooks";
|
||||||
import {CrateFanTray} from "./CrateFanTray";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,7 +45,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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import {useShopStore} from "./shop_store";
|
|
||||||
import {Tip} from "./options/components/Tip";
|
|
||||||
import {formatMoney} from "./utils";
|
|
||||||
|
|
||||||
export function CrateFanTray({crate_index}) {
|
|
||||||
const currency = useShopStore((state) => state.currency);
|
|
||||||
const fanTray = useShopStore((state) => state.fanTray);
|
|
||||||
const crate_id = useShopStore((state) => state.crates[crate_index].id);
|
|
||||||
const fanTrayAvailable = useShopStore((state) => state.fanTrayAvailableByIndex(crate_index));
|
|
||||||
const fanTrayEnabled = useShopStore((state) => state.crates[crate_index].fan_tray);
|
|
||||||
const updateFanTray = useShopStore((state) => state.updateFanTrayOption);
|
|
||||||
|
|
||||||
const base_id = crate_id + "fan_tray";
|
|
||||||
return fanTrayAvailable ? (
|
|
||||||
<div className="crate-bar">
|
|
||||||
<div className="shop-switch">
|
|
||||||
<div className="form-check form-switch">
|
|
||||||
<input
|
|
||||||
className="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
role="switch"
|
|
||||||
id={base_id}
|
|
||||||
checked={fanTrayEnabled}
|
|
||||||
onClick={() => updateFanTray(crate_id, !fanTrayEnabled)}
|
|
||||||
onChange={() => updateFanTray(crate_id, !fanTrayEnabled)}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label" htmlFor={base_id} style={{"display": "inline", marginRight: "0.125rem"}}>
|
|
||||||
{fanTray.optionTitle} (+{`${currency} ${formatMoney(fanTray.price)}`})
|
|
||||||
</label>
|
|
||||||
{fanTray.tip && <Tip id={base_id + "tooltip"} tip={fanTray.tip}/>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null
|
|
||||||
}
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
import {ProcessOptions} 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 || {});
|
||||||
|
|
||||||
|
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});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="crate-bar">
|
||||||
|
{options}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import {ShowJSON} from "./ShowJSON";
|
||||||
|
|
||||||
// #!render_count
|
// #!render_count
|
||||||
import {useRenderCount} from "@uidotdev/usehooks";
|
import {useRenderCount} from "@uidotdev/usehooks";
|
||||||
|
import {OrderOptions} from "./OrderOptions";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,6 +30,8 @@ export function OrderForm() {
|
||||||
return (
|
return (
|
||||||
<div className="summary-form">
|
<div className="summary-form">
|
||||||
|
|
||||||
|
<OrderOptions/>
|
||||||
|
|
||||||
<form onSubmit={submitForm} noValidate>
|
<form onSubmit={submitForm} noValidate>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
import {ProcessOptions} from "./options/Options";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export function OrderOptions() {
|
||||||
|
const optionsLogic = useShopStore((state) => state.order_options);
|
||||||
|
const updateOptions = useShopStore((state) => state.updateOrderOptions);
|
||||||
|
const options_data = useShopStore((state) => state.order_options_data || {});
|
||||||
|
|
||||||
|
const options = ProcessOptions({
|
||||||
|
options: optionsLogic,
|
||||||
|
data: options_data,
|
||||||
|
id: "order_options",
|
||||||
|
target: {
|
||||||
|
construct: ((outvar, value) => {
|
||||||
|
// #!options_log
|
||||||
|
console.log("construct", outvar, value, options_data);
|
||||||
|
|
||||||
|
options_data[outvar] = value;
|
||||||
|
updateOptions({[outvar]: value});
|
||||||
|
}),
|
||||||
|
update: ((outvar, value) => {
|
||||||
|
// #!options_log
|
||||||
|
console.log("update", outvar, value, options_data);
|
||||||
|
|
||||||
|
if (outvar in options_data) options_data[outvar] = value;
|
||||||
|
|
||||||
|
updateOptions({[outvar]: value});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="order-bar">
|
||||||
|
{options}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {OrderSummary} from "./OrderSummary";
|
import {SummaryOrder} from "./SummaryOrder";
|
||||||
import {OrderForm} from "./OrderForm";
|
import {OrderForm} from "./OrderForm";
|
||||||
import {CrateList} from "./CrateList";
|
import {CrateList} from "./CrateList";
|
||||||
import {useShopStore} from "./shop_store";
|
import {useShopStore} from "./shop_store";
|
||||||
|
@ -8,6 +8,7 @@ import {RFQFeedback} from "./RFQFeedback";
|
||||||
|
|
||||||
// #!render_count
|
// #!render_count
|
||||||
import {useRenderCount} from "@uidotdev/usehooks";
|
import {useRenderCount} from "@uidotdev/usehooks";
|
||||||
|
import {OrderOptions} from "./OrderOptions";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that renders all things for order.
|
* Component that renders all things for order.
|
||||||
|
@ -47,8 +48,7 @@ export function OrderPanel({title, description}) {
|
||||||
<CrateList/>
|
<CrateList/>
|
||||||
|
|
||||||
<section className="summary">
|
<section className="summary">
|
||||||
<OrderSummary/>
|
<SummaryOrder/>
|
||||||
|
|
||||||
<OrderForm/>
|
<OrderForm/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ export function ProductItem({card_index}) {
|
||||||
<img src="/images/shop/icon-add.svg" alt="add"/>
|
<img src="/images/shop/icon-add.svg" alt="add"/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Draggable draggableId={card.id} index={card_index}>
|
<Draggable draggableId={card.id + card_index} index={card_index}>
|
||||||
{(provided, snapshot) => (
|
{(provided, snapshot) => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<img
|
<img
|
||||||
|
|
|
@ -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);
|
||||||
|
@ -26,6 +27,7 @@ export function Shop() {
|
||||||
const handleOnDragEnd = (drop_result, _provided) => {
|
const handleOnDragEnd = (drop_result, _provided) => {
|
||||||
if (!drop_result.destination) {
|
if (!drop_result.destination) {
|
||||||
console.warn("No drop destination");
|
console.warn("No drop destination");
|
||||||
|
console.log(drop_result)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (drop_result.source.droppableId === "backlog")
|
if (drop_result.source.droppableId === "backlog")
|
||||||
|
@ -38,6 +40,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>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -1,45 +0,0 @@
|
||||||
import {formatMoney} from "./utils";
|
|
||||||
import React from "react";
|
|
||||||
import {useShopStore} from "./shop_store";
|
|
||||||
|
|
||||||
// #!render_count
|
|
||||||
import {useRenderCount} from "@uidotdev/usehooks";
|
|
||||||
|
|
||||||
|
|
||||||
export function SummaryCrateFanTray({crate_index}) {
|
|
||||||
// #!render_count
|
|
||||||
const renderCount = useRenderCount();
|
|
||||||
|
|
||||||
const currency = useShopStore((state) => state.currency);
|
|
||||||
const fanTray = useShopStore((state) => state.fanTray);
|
|
||||||
const crate_id = useShopStore((state) => state.crates[crate_index].id);
|
|
||||||
const fanTrayAvailable = useShopStore((state) => state.fanTrayAvailableByIndex(crate_index));
|
|
||||||
const fanTrayEnabled = useShopStore((state) => state.crates[crate_index].fan_tray);
|
|
||||||
const updateFanTray = useShopStore((state) => state.updateFanTrayOption);
|
|
||||||
|
|
||||||
|
|
||||||
// #!render_count
|
|
||||||
console.log("SummaryCrateCard renders: ", renderCount)
|
|
||||||
|
|
||||||
return (fanTrayAvailable && fanTrayEnabled) ? (<tr
|
|
||||||
key={"summary_crate_" + crate_id + "_fan_tray"}>
|
|
||||||
<td className="item-card-name">
|
|
||||||
<span style={{
|
|
||||||
'display': 'inline-block', 'width': '16px',
|
|
||||||
}}> </span>
|
|
||||||
<div>{fanTray.optionTitle}</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td className="price">
|
|
||||||
<div className="d-inline-flex align-content-center">
|
|
||||||
{`${currency} ${formatMoney(fanTray.price)}`}
|
|
||||||
|
|
||||||
<button onClick={() => updateFanTray(crate_id, false)}>
|
|
||||||
<img src="/images/shop/icon-remove.svg" className="d-block"/>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div style={{'width': '45px', 'height': '20px'}} className="d-inline"></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>) : null;
|
|
||||||
}
|
|
|
@ -4,7 +4,6 @@ import {useShopStore} from "./shop_store";
|
||||||
|
|
||||||
// #!render_count
|
// #!render_count
|
||||||
import {useRenderCount} from "@uidotdev/usehooks";
|
import {useRenderCount} from "@uidotdev/usehooks";
|
||||||
import {CrateMode} from "./CrateMode";
|
|
||||||
|
|
||||||
export function SummaryCrateHeader({crate_index}) {
|
export function SummaryCrateHeader({crate_index}) {
|
||||||
// #!render_count
|
// #!render_count
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
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});
|
||||||
|
|
||||||
|
// #!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>
|
||||||
|
));
|
||||||
|
}
|
|
@ -5,6 +5,8 @@ import {SummaryCrate} from "./SummaryCrate";
|
||||||
|
|
||||||
// #!render_count
|
// #!render_count
|
||||||
import {useRenderCount} from "@uidotdev/usehooks";
|
import {useRenderCount} from "@uidotdev/usehooks";
|
||||||
|
import {SummaryOrderPricedOptions} from "./SummaryOrderPricedOptions";
|
||||||
|
import {SummaryOrderShipping} from "./SummaryOrderShipping";
|
||||||
|
|
||||||
export function SummaryCrates() {
|
export function SummaryCrates() {
|
||||||
// #!render_count
|
// #!render_count
|
||||||
|
@ -20,6 +22,8 @@ export function SummaryCrates() {
|
||||||
{range(0, crates_l).map((index, _i) => {
|
{range(0, crates_l).map((index, _i) => {
|
||||||
return <SummaryCrate crate_index={index} key={"summary_crate_body_" + index} />
|
return <SummaryCrate crate_index={index} key={"summary_crate_body_" + index} />
|
||||||
})}
|
})}
|
||||||
|
<SummaryOrderPricedOptions/>
|
||||||
|
<SummaryOrderShipping/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {useShopStore} from "./shop_store";
|
|
||||||
import {SummaryCrates} from "./SummaryCrates";
|
import {SummaryCrates} from "./SummaryCrates";
|
||||||
import {SummaryTotalPrice} from "./SummaryTotalPrice";
|
import {SummaryTotalPrice} from "./SummaryTotalPrice";
|
||||||
|
|
||||||
|
@ -11,12 +10,12 @@ import {useRenderCount} from "@uidotdev/usehooks";
|
||||||
* Components that displays the list of card that are used in the crate.
|
* Components that displays the list of card that are used in the crate.
|
||||||
* It is a summary of purchase
|
* It is a summary of purchase
|
||||||
*/
|
*/
|
||||||
export function OrderSummary() {
|
export function SummaryOrder() {
|
||||||
// #!render_count
|
// #!render_count
|
||||||
const renderCount = useRenderCount();
|
const renderCount = useRenderCount();
|
||||||
|
|
||||||
// #!render_count
|
// #!render_count
|
||||||
console.log("OrderSummary renders: ", renderCount)
|
console.log("SummaryOrder renders: ", renderCount)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="summary-price">
|
<div className="summary-price">
|
|
@ -0,0 +1,43 @@
|
||||||
|
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 SummaryOrderPricedOptions() {
|
||||||
|
// #!render_count
|
||||||
|
const renderCount = useRenderCount();
|
||||||
|
|
||||||
|
const currency = useShopStore((state) => state.currency);
|
||||||
|
const optionsPrices = useShopStore((state) => state.order_prices);
|
||||||
|
const updateOptions = useShopStore((state) => state.updateOrderOptions);
|
||||||
|
const options_data = useShopStore((state) => state.order_options_data);
|
||||||
|
|
||||||
|
const options = ProcessOptionsToData({options: optionsPrices, data: options_data});
|
||||||
|
|
||||||
|
// #!render_count
|
||||||
|
console.log("SummaryOrderPricedOptions renders: ", renderCount)
|
||||||
|
|
||||||
|
return <tbody key={"summary_order_body"}>
|
||||||
|
{options.map((option, _i) => (
|
||||||
|
<tr key={"summary_order" + "option_" + option.id}>
|
||||||
|
<td className="item-card-name">
|
||||||
|
<div>{option.title}</div>
|
||||||
|
</td>
|
||||||
|
<td className="price">
|
||||||
|
<div className="d-inline-flex align-content-center">
|
||||||
|
{`${currency} ${formatMoney(option.price)}`}
|
||||||
|
|
||||||
|
<button onClick={() => updateOptions(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>
|
||||||
|
))}
|
||||||
|
</tbody>;
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
import {formatMoney} from "./utils";
|
||||||
|
import React from "react";
|
||||||
|
import {useShopStore} from "./shop_store";
|
||||||
|
import {ProcessOptions, ProcessOptionsToData} from "./options/Options";
|
||||||
|
|
||||||
|
// #!render_count
|
||||||
|
import {useRenderCount} from "@uidotdev/usehooks";
|
||||||
|
|
||||||
|
export function SummaryOrderShipping() {
|
||||||
|
// #!render_count
|
||||||
|
const renderCount = useRenderCount();
|
||||||
|
|
||||||
|
const shipping_summary = useShopStore((state) => state.shipping_summary);
|
||||||
|
const options_data = useShopStore((state) => state.order_options_data);
|
||||||
|
|
||||||
|
// #!render_count
|
||||||
|
console.log("SummaryOrderShipping renders: ", renderCount)
|
||||||
|
|
||||||
|
const options = ProcessOptions({
|
||||||
|
options: shipping_summary,
|
||||||
|
data: options_data,
|
||||||
|
id: "shipping_options",
|
||||||
|
target: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <tbody key="summary_shipping_order_body">
|
||||||
|
{options.map((option, i) => (
|
||||||
|
<tr key={"summary_shipping_order_option_" + i} id={"summary_shipping_order_option_" + i}>
|
||||||
|
<td className="item-card-name" key={"summary_shipping_order_key_option_" + i} id={"summary_shipping_order_key_option_" + i}>
|
||||||
|
{option}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>;
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import React from "react";
|
||||||
|
|
||||||
export function SummaryTotalPrice() {
|
export function SummaryTotalPrice() {
|
||||||
const currency = useShopStore((state) => state.currency);
|
const currency = useShopStore((state) => state.currency);
|
||||||
const total_price = useShopStore((state) => state.totalOrderPrice());
|
const total_price = useShopStore((state) => state.total_order_price);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -5,20 +5,23 @@ import {v4 as uuidv4} from "uuid";
|
||||||
|
|
||||||
export function validateJSON(description) {
|
export function validateJSON(description) {
|
||||||
let crates_raw;
|
let crates_raw;
|
||||||
|
let order_options;
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(description);
|
const parsed = JSON.parse(description);
|
||||||
// here we can check additional fields
|
// here we can check additional fields
|
||||||
crates_raw = parsed.crates;
|
crates_raw = parsed.crates;
|
||||||
|
order_options = parsed.options;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!order_options) return false;
|
||||||
const crate_modes = useShopStore.getState().crate_modes;
|
const crate_modes = useShopStore.getState().crate_modes;
|
||||||
const modes_order = useShopStore.getState().modes_order;
|
const modes_order = useShopStore.getState().modes_order;
|
||||||
const pn_to_card = useShopStore.getState().pn_to_cards;
|
const pn_to_card = useShopStore.getState().pn_to_cards;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const crate of crates_raw) {
|
for (const crate of crates_raw) {
|
||||||
if (!crate.type || !crate.items || !(crate.type in crate_modes)) return false;
|
if (!crate.type || !crate.items || !crate.options || !(crate.type in crate_modes)) return false;
|
||||||
for (const card of crate.items) {
|
for (const card of crate.items) {
|
||||||
if (!(card.pn in pn_to_card) || card.options === undefined) return false;
|
if (!(card.pn in pn_to_card) || card.options === undefined) return false;
|
||||||
}
|
}
|
||||||
|
@ -36,12 +39,11 @@ export function JSONToCrates(description) {
|
||||||
const parsed = JSON.parse(description);
|
const parsed = JSON.parse(description);
|
||||||
const crates_raw = parsed.crates;
|
const crates_raw = parsed.crates;
|
||||||
const pn_to_card = useShopStore.getState().getCardDescriptionByPn;
|
const pn_to_card = useShopStore.getState().getCardDescriptionByPn;
|
||||||
const fanTrayAvailable = useShopStore.getState().fanTrayAvailableForMode;
|
|
||||||
|
|
||||||
const crates = Array.from(crates_raw.map((crate, c_i) => ({
|
const crates = Array.from(crates_raw.map((crate, c_i) => ({
|
||||||
id: crate.type === "no_crate" ? "spare" : "crate" + c_i,
|
id: crate.type === "no_crate" ? "spare" : "crate" + c_i,
|
||||||
name: crate.type === "no_crate" ? "Spare cards" : undefined,
|
name: crate.type === "no_crate" ? "Spare cards" : undefined,
|
||||||
fan_tray: fanTrayAvailable(crate.type) && crate.fan_tray === true,
|
options_data: crate.options,
|
||||||
crate_mode: crate.type,
|
crate_mode: crate.type,
|
||||||
items: Array.from(crate.items.map((card, _i) => ({
|
items: Array.from(crate.items.map((card, _i) => ({
|
||||||
...pn_to_card(card.pn),
|
...pn_to_card(card.pn),
|
||||||
|
@ -54,12 +56,15 @@ export function JSONToCrates(description) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// some additional fields go here
|
// some additional fields go here
|
||||||
|
order_options_data: parsed.options,
|
||||||
crates: crates
|
crates: crates
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CratesToJSON(crates) {
|
export function CratesToJSON(crates) {
|
||||||
const fanTrayAvailable = useShopStore.getState().fanTrayAvailableForMode;
|
const crateOptions = useShopStore.getState().crate_options;
|
||||||
|
const orderOptions = useShopStore.getState().order_options;
|
||||||
|
const orderOptionsData = useShopStore.getState().order_options_data;
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
// additional fields can go here
|
// additional fields can go here
|
||||||
crates: Array.from(crates.map((crate, _i) => ({
|
crates: Array.from(crates.map((crate, _i) => ({
|
||||||
|
@ -68,7 +73,8 @@ export function CratesToJSON(crates) {
|
||||||
options: (card.options_data && card.options) ? FilterOptions(card.options, card.options_data) : null
|
options: (card.options_data && card.options) ? FilterOptions(card.options, card.options_data) : null
|
||||||
}))),
|
}))),
|
||||||
type: crate.crate_mode,
|
type: crate.crate_mode,
|
||||||
fan_tray: (!fanTrayAvailable(crate.crate_mode) ? undefined : true) && crate.fan_tray
|
options: FilterOptions(crateOptions, crate.options_data)
|
||||||
})))
|
}))),
|
||||||
|
options: FilterOptions(orderOptions, orderOptionsData)
|
||||||
}, null, 2)
|
}, null, 2)
|
||||||
}
|
}
|
|
@ -21,7 +21,7 @@ export function Notification({id, tip, content, sideMenuIsOpen}) {
|
||||||
style={{display: 'inline'}}
|
style={{display: 'inline'}}
|
||||||
show={show}
|
show={show}
|
||||||
onToggle={() => setShow(false)}
|
onToggle={() => setShow(false)}
|
||||||
overlay={<Tooltip id={id}>{tip}</Tooltip>}
|
overlay={props => <Tooltip id={id} {...props}>{tip}</Tooltip>}
|
||||||
rootClose={!sideMenuIsOpen}
|
rootClose={!sideMenuIsOpen}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function ProcessOptions({options, data, target, id}) {
|
||||||
return componentsList[options.type](target, id + options.type, data, options.args);
|
return componentsList[options.type](target, id + options.type, data, options.args);
|
||||||
} else if (options.type === "Group") {
|
} else if (options.type === "Group") {
|
||||||
return (
|
return (
|
||||||
<div className="border rounded" key={id + "group"}>
|
<div className="border rounded options-group" key={id + "group"}>
|
||||||
{ProcessOptions({
|
{ProcessOptions({
|
||||||
options: json_logic_apply(options.items, data),
|
options: json_logic_apply(options.items, data),
|
||||||
data: data,
|
data: data,
|
||||||
|
@ -41,3 +41,23 @@ export function ProcessOptions({options, data, target, id}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ProcessOptionsToData({options, data}) {
|
||||||
|
let options_t = true_type_of(options);
|
||||||
|
if (options_t === "array") {
|
||||||
|
return Array.from(
|
||||||
|
options.map((option_item, _i) => ProcessOptionsToData({
|
||||||
|
options: option_item,
|
||||||
|
data: data,
|
||||||
|
}))
|
||||||
|
).filter((item, _i) => !!item).flat();
|
||||||
|
} else if (options_t === "object") {
|
||||||
|
if (true_type_of(options.title) === "string") {
|
||||||
|
return options;
|
||||||
|
} else {
|
||||||
|
return ProcessOptionsToData({options: json_logic_apply(options, data), data: data});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//throw Error("Incompatible type for the option: " + options_t)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import React from "react";
|
||||||
|
import {apply as json_logic_apply, add_operation as json_add_operation} from "json-logic-js";
|
||||||
|
|
||||||
|
|
||||||
|
json_add_operation("lower", (some_str) => some_str && some_str.toLowerCase(some_str));
|
||||||
|
json_add_operation("upper", (some_str) => some_str && some_str.toUpperCase(some_str));
|
||||||
|
json_add_operation("capitalize", (some_str) => some_str && some_str.capitalizeFirstLetter(some_str));
|
||||||
|
|
||||||
|
export function Label(target, id, data, {content}) {
|
||||||
|
const resulting_string = json_logic_apply(content, data).flat().join("");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id={id+"label"} key={id+"label"} className="options-label">
|
||||||
|
{resulting_string}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -21,6 +21,15 @@ class Line extends Component {
|
||||||
this.props.target.update(this.props.outvar, text);
|
this.props.target.update(this.props.outvar, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props, current_state) {
|
||||||
|
if (current_state.text !== props.data[props.outvar]) {
|
||||||
|
return {
|
||||||
|
text: 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 (
|
||||||
|
@ -37,7 +46,7 @@ class Line extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LineWrapper(target, id, data, {title, fallback, outvar, icon, tip}) {
|
export function LineWrapper(target, id, data, {title, fallback, outvar, icon, tip, classes}) {
|
||||||
return <Line target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
|
return <Line target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
|
||||||
id={id} data={data}/>;
|
id={id} data={data} classes={classes}/>;
|
||||||
}
|
}
|
|
@ -23,38 +23,49 @@ class Radio extends Component {
|
||||||
this.props.target.update(this.props.outvar, variant);
|
this.props.target.update(this.props.outvar, variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props, current_state) {
|
||||||
|
if (current_state.variant !== props.data[props.outvar]) {
|
||||||
|
return {
|
||||||
|
variant: 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 (
|
||||||
<div className="shop-radio" key={this.props.id}>
|
<div className="shop-radio" key={this.props.id}>
|
||||||
<div style={{"display": "inline"}}>
|
<div style={{"display": "inline"}} className="shop-radio-label">
|
||||||
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
|
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
|
||||||
{this.props.title}
|
{this.props.title}
|
||||||
</div>
|
</div>
|
||||||
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
|
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
|
||||||
{this.props.variants.map((variant, _) => (
|
<div className="d-block">
|
||||||
<div className="form-check" key={key + variant}>
|
{this.props.variants.map((variant, _) => (
|
||||||
<input
|
<div className={`form-check shop-radio-variant ${this.props.classes}`} key={key + variant}>
|
||||||
className="form-check-input"
|
<input
|
||||||
type="radio"
|
className="form-check-input"
|
||||||
name={key}
|
type="radio"
|
||||||
id={key + variant}
|
name={key}
|
||||||
checked={this.state.variant === variant}
|
id={key + variant}
|
||||||
onClick={() => this.handleClick(variant)}
|
checked={this.state.variant === variant}
|
||||||
onChange={() => this.handleClick(variant)}
|
onClick={() => this.handleClick(variant)}
|
||||||
/>
|
onChange={() => this.handleClick(variant)}
|
||||||
<label className="form-check-label" htmlFor={key + variant}>
|
/>
|
||||||
{variant}
|
<label className="form-check-label" htmlFor={key + variant}>
|
||||||
</label>
|
{variant}
|
||||||
</div>
|
</label>
|
||||||
))}
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RadioWrapper(target, id, data, {title, variants, outvar, fallback, icon, tip}) {
|
export function RadioWrapper(target, id, data, {title, variants, outvar, fallback, icon, tip, classes}) {
|
||||||
return <Radio target={target} title={title} variants={variants} outvar={outvar} icon={icon} tip={tip} key={id}
|
return <Radio target={target} title={title} variants={variants} outvar={outvar} icon={icon} tip={tip} key={id}
|
||||||
fallback={fallback}
|
fallback={fallback} classes={classes}
|
||||||
id={id} data={data}/>;
|
id={id} data={data}/>;
|
||||||
}
|
}
|
|
@ -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 (
|
||||||
|
@ -48,7 +57,7 @@ class Switch extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SwitchWrapper(target, id, data, {title, fallback, outvar, icon, tip}) {
|
export function SwitchWrapper(target, id, data, {title, fallback, outvar, icon, tip, classes}) {
|
||||||
return <Switch target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
|
return <Switch target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
|
||||||
id={id} data={data}/>;
|
id={id} data={data} classes={classes}/>;
|
||||||
}
|
}
|
|
@ -34,6 +34,16 @@ class SwitchLine extends Component {
|
||||||
this.props.target.update(this.props.outvar, new_state);
|
this.props.target.update(this.props.outvar, new_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props, current_state) {
|
||||||
|
if (current_state.checked !== props.data[props.outvar].checked || current_state.text !== props.data[props.outvar].text) {
|
||||||
|
return {
|
||||||
|
checked: props.data[props.outvar].checked,
|
||||||
|
text: props.data[props.outvar].text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let key = this.props.id + this.props.outvar;
|
let key = this.props.id + this.props.outvar;
|
||||||
return (
|
return (
|
||||||
|
@ -61,7 +71,7 @@ class SwitchLine extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SwitchLineWrapper(target, id, data, {title, fallback, outvar, icon, tip}) {
|
export function SwitchLineWrapper(target, id, data, {title, fallback, outvar, icon, tip, classes}) {
|
||||||
return <SwitchLine target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
|
return <SwitchLine target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} tip={tip} key={id}
|
||||||
id={id} data={data}/>;
|
id={id} data={data} classes={classes}/>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ export function Tip({id, tip}) {
|
||||||
placement="auto"
|
placement="auto"
|
||||||
trigger={['click', 'hover', 'focus']}
|
trigger={['click', 'hover', 'focus']}
|
||||||
style={{display: 'inline'}}
|
style={{display: 'inline'}}
|
||||||
overlay={<Tooltip id={id}>{tip}</Tooltip>}
|
overlay={props => <Tooltip id={id} {...props}>{tip}</Tooltip>}
|
||||||
rootClose
|
rootClose
|
||||||
>
|
>
|
||||||
<img src={`/images/shop/icon-reminder.svg`} className="options-icon"/>
|
<img src={`/images/shop/icon-reminder.svg`} className="options-icon"/>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {RadioWrapper} from "./Radio";
|
||||||
import {SwitchWrapper} from "./Switch";
|
import {SwitchWrapper} from "./Switch";
|
||||||
import {SwitchLineWrapper} from "./SwitchLine";
|
import {SwitchLineWrapper} from "./SwitchLine";
|
||||||
import {UnimplementedComponent} from "./UnimplementedComponent";
|
import {UnimplementedComponent} from "./UnimplementedComponent";
|
||||||
|
import {Label} from "./Label";
|
||||||
|
|
||||||
|
|
||||||
// Class components are used because we cannot use hooks for updating the state
|
// Class components are used because we cannot use hooks for updating the state
|
||||||
|
@ -13,5 +14,6 @@ export const componentsList = {
|
||||||
"Switch": SwitchWrapper,
|
"Switch": SwitchWrapper,
|
||||||
"Line": LineWrapper,
|
"Line": LineWrapper,
|
||||||
"SwitchLine": SwitchLineWrapper,
|
"SwitchLine": SwitchLineWrapper,
|
||||||
|
"Label": Label,
|
||||||
"Default": UnimplementedComponent,
|
"Default": UnimplementedComponent,
|
||||||
};
|
};
|
|
@ -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,18 @@ export function FillExtData(data, index) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function FillExtCrateData(crate) {
|
||||||
|
return {
|
||||||
|
crate_mode: crate.crate_mode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FillExtOrderData(crates, modes_order) {
|
||||||
|
return {
|
||||||
|
has_crate: crates.filter((crate) => modes_order.includes(crate.crate_mode)).length >= 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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,13 +2,14 @@
|
||||||
|
|
||||||
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, FillExtOrderData, 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";
|
||||||
|
import {ProcessOptionsToData} from "./options/Options";
|
||||||
|
|
||||||
|
|
||||||
const cards_to_pn_map = (cards) => {
|
const cards_to_pn_map = (cards) => {
|
||||||
|
@ -34,11 +35,75 @@ const useCrateModes = ((set, get) => ({
|
||||||
crateParams: mode => get().crate_modes[mode],
|
crateParams: mode => get().crate_modes[mode],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const useFanTray = ((set, get) => ({
|
const useCrateOptions = ((set, get) => ({
|
||||||
fanTray: shared_data.fanTray,
|
crate_options: shared_data.crateOptions.options,
|
||||||
fanTrayAvailableForMode: (crate_mode) => {
|
crate_prices: shared_data.crateOptions.prices,
|
||||||
return get().fanTray.crateModesAvailable[crate_mode] === true;
|
|
||||||
},
|
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 || {};
|
||||||
|
return {
|
||||||
|
...crate,
|
||||||
|
options_data: {
|
||||||
|
...previous_options,
|
||||||
|
...new_options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else return crate;
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
|
||||||
|
updateCrateOptions: (crate_id, new_options) => {
|
||||||
|
get().fillExtCrateData(crate_id);
|
||||||
|
get().fillOrderExtData();
|
||||||
|
get()._updateCrateOption(crate_id, new_options);
|
||||||
|
get()._updateTotalOrderPrice();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const useOrderOptions = ((set, get) => ({
|
||||||
|
order_options: shared_data.orderOptions.options,
|
||||||
|
order_prices: shared_data.orderOptions.prices,
|
||||||
|
shipping_summary: shared_data.orderOptions.shippingSummary,
|
||||||
|
order_options_data: {},
|
||||||
|
|
||||||
|
fillOrderExtData: () => set(state => ({
|
||||||
|
order_options_data: {
|
||||||
|
...state.order_options_data,
|
||||||
|
ext_data: FillExtOrderData(state.crates, state.modes_order)
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
|
||||||
|
_updateOrderOptions: (new_options) => set(state => ({
|
||||||
|
order_options_data: {
|
||||||
|
...state.order_options_data,
|
||||||
|
...new_options
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
|
||||||
|
updateOrderOptions: (new_options) => {
|
||||||
|
get()._updateOrderOptions(new_options);
|
||||||
|
get()._updateTotalOrderPrice();
|
||||||
|
get().fillOrderExtData();
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const useLayout = ((set, get) => ({
|
const useLayout = ((set, get) => ({
|
||||||
|
@ -88,16 +153,20 @@ const useImportJSON = ((set, get) => ({
|
||||||
const parsed = JSONToCrates(state.importValue.value);
|
const parsed = JSONToCrates(state.importValue.value);
|
||||||
// if (parsed.crates[-1].crate_mode !== "")
|
// if (parsed.crates[-1].crate_mode !== "")
|
||||||
return {
|
return {
|
||||||
importShouldOpen: false,
|
importShouldOpen: false,
|
||||||
// additional fields go here
|
// additional fields go here
|
||||||
crates: parsed.crates
|
crates: parsed.crates,
|
||||||
|
order_options_data: parsed.order_options_data
|
||||||
}}),
|
}}),
|
||||||
loadDescription: () => {
|
loadDescription: () => {
|
||||||
get()._loadDescription()
|
get()._loadDescription()
|
||||||
|
get().fillOrderExtData();
|
||||||
get().crates.forEach((crate, _i) => {
|
get().crates.forEach((crate, _i) => {
|
||||||
get().fillExtData(crate.id)
|
get().fillExtData(crate.id);
|
||||||
get().fillWarnings(crate.id)
|
get().fillWarnings(crate.id);
|
||||||
})
|
get().fillExtCrateData(crate.id);
|
||||||
|
});
|
||||||
|
get()._updateTotalOrderPrice();
|
||||||
},
|
},
|
||||||
updateImportDescription: (new_description) => set(state => ({
|
updateImportDescription: (new_description) => set(state => ({
|
||||||
importValue: {
|
importValue: {
|
||||||
|
@ -246,6 +315,7 @@ const useCart = ((set, get) => ({
|
||||||
crates: shared_data.columns.crates,
|
crates: shared_data.columns.crates,
|
||||||
active_crate: "crate0",
|
active_crate: "crate0",
|
||||||
_defaultCrates: Array.from(shared_data.columns.crates),
|
_defaultCrates: Array.from(shared_data.columns.crates),
|
||||||
|
total_order_price: 0,
|
||||||
|
|
||||||
_newCrate: (crate_id) => set((state) => ({
|
_newCrate: (crate_id) => set((state) => ({
|
||||||
crates: state.crates.toSpliced(-1, 0, {
|
crates: state.crates.toSpliced(-1, 0, {
|
||||||
|
@ -254,7 +324,7 @@ const useCart = ((set, get) => ({
|
||||||
}),
|
}),
|
||||||
active_crate: crate_id || "crate" + state.crates.length
|
active_crate: crate_id || "crate" + state.crates.length
|
||||||
})),
|
})),
|
||||||
delCrate: (id) => set(state => ({
|
_delCrate: (id) => set(state => ({
|
||||||
crates: state.crates.filter((crate => crate.id !== id || !state.modes_order.includes(crate.crate_mode))),
|
crates: state.crates.filter((crate => crate.id !== id || !state.modes_order.includes(crate.crate_mode))),
|
||||||
active_crate: state.active_crate === id ? null : state.active_crate,
|
active_crate: state.active_crate === id ? null : state.active_crate,
|
||||||
})),
|
})),
|
||||||
|
@ -386,7 +456,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 {
|
||||||
|
@ -398,17 +468,20 @@ const useCart = ((set, get) => ({
|
||||||
})
|
})
|
||||||
})),
|
})),
|
||||||
|
|
||||||
totalOrderPrice: () => {
|
_updateTotalOrderPrice: () => set(state => {
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
get().crates.forEach( (crate, i) => {
|
get().crates.forEach( (crate, i) => {
|
||||||
sum += get().crate_modes[crate.crate_mode].price;
|
sum += get().crate_modes[crate.crate_mode].price;
|
||||||
sum += (crate.fan_tray && get().fanTrayAvailableByIndex(i)) ? get().fanTray.price : 0;
|
const crate_options = ProcessOptionsToData({options: get().crate_prices, data: crate.options_data || {}});
|
||||||
|
sum += crate_options ? crate_options.reduce((accumulator, currentValue) => accumulator+currentValue.price, 0) : 0;
|
||||||
crate.items.forEach((item, _) => {
|
crate.items.forEach((item, _) => {
|
||||||
sum += item.price;
|
sum += item.price;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return sum;
|
const order_options = ProcessOptionsToData({options: get().order_prices, data: get().order_options_data || {}});
|
||||||
},
|
sum += order_options ? order_options.reduce((accumulator, currentValue) => accumulator+currentValue.price, 0) : 0;
|
||||||
|
return {total_order_price: sum};
|
||||||
|
}),
|
||||||
|
|
||||||
// Composite actions that require warnings recalculation:
|
// Composite actions that require warnings recalculation:
|
||||||
|
|
||||||
|
@ -416,14 +489,25 @@ 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().fillOrderExtData();
|
||||||
get().fillWarnings(crate_id);
|
get().fillWarnings(crate_id);
|
||||||
|
get()._updateTotalOrderPrice();
|
||||||
},
|
},
|
||||||
|
|
||||||
setCrateMode: (id, mode) => {
|
setCrateMode: (id, mode) => {
|
||||||
get()._setCrateMode(id, mode)
|
get()._setCrateMode(id, mode)
|
||||||
get().fillExtData(id);
|
get().fillExtData(id);
|
||||||
|
get().fillExtCrateData(id);
|
||||||
|
get().fillOrderExtData();
|
||||||
get().fillWarnings(id);
|
get().fillWarnings(id);
|
||||||
get().setActiveCrate(id);
|
get().setActiveCrate(id);
|
||||||
|
get()._updateTotalOrderPrice();
|
||||||
|
},
|
||||||
|
|
||||||
|
delCrate: (id) => {
|
||||||
|
get()._delCrate(id);
|
||||||
|
get().fillOrderExtData();
|
||||||
},
|
},
|
||||||
|
|
||||||
addCardFromBacklog: (crate_to, index_from, index_to, just_mounted) => {
|
addCardFromBacklog: (crate_to, index_from, index_to, just_mounted) => {
|
||||||
|
@ -437,6 +521,7 @@ const useCart = ((set, get) => ({
|
||||||
get().fillExtData(dest);
|
get().fillExtData(dest);
|
||||||
get().fillWarnings(dest);
|
get().fillWarnings(dest);
|
||||||
get().setActiveCrate(dest);
|
get().setActiveCrate(dest);
|
||||||
|
get()._updateTotalOrderPrice();
|
||||||
if (!just_mounted) {
|
if (!just_mounted) {
|
||||||
get().cardAdded()
|
get().cardAdded()
|
||||||
}
|
}
|
||||||
|
@ -447,6 +532,7 @@ const useCart = ((set, get) => ({
|
||||||
get().fillExtData(crate_to);
|
get().fillExtData(crate_to);
|
||||||
get().fillWarnings(crate_to);
|
get().fillWarnings(crate_to);
|
||||||
get().setActiveCrate(crate_to);
|
get().setActiveCrate(crate_to);
|
||||||
|
get()._updateTotalOrderPrice();
|
||||||
if (crate_from !== crate_to) {
|
if (crate_from !== crate_to) {
|
||||||
get().fillExtData(crate_from);
|
get().fillExtData(crate_from);
|
||||||
get().fillWarnings(crate_from);
|
get().fillWarnings(crate_from);
|
||||||
|
@ -456,6 +542,7 @@ const useCart = ((set, get) => ({
|
||||||
get()._deleteCard(crate_id, index);
|
get()._deleteCard(crate_id, index);
|
||||||
get().fillExtData(crate_id);
|
get().fillExtData(crate_id);
|
||||||
get().fillWarnings(crate_id);
|
get().fillWarnings(crate_id);
|
||||||
|
get()._updateTotalOrderPrice();
|
||||||
if (crate_id === get().highlighted.crate && index === get().highlighted.card) get().highlightReset()
|
if (crate_id === get().highlighted.crate && index === get().highlighted.card) get().highlightReset()
|
||||||
},
|
},
|
||||||
clearCrate: (id) => {
|
clearCrate: (id) => {
|
||||||
|
@ -469,20 +556,13 @@ const useCart = ((set, get) => ({
|
||||||
get().fillWarnings(crate_id);
|
get().fillWarnings(crate_id);
|
||||||
},
|
},
|
||||||
|
|
||||||
updateFanTrayOption: (crate_id, add_fan_tray) => set(state => ({
|
initExtData: () => {
|
||||||
crates: state.crates.map((crate, _i) => {
|
get().fillOrderExtData();
|
||||||
if (crate_id === crate.id) {
|
get().crates.forEach((crate, _i) => {
|
||||||
return {
|
get().fillExtData(crate.id);
|
||||||
...crate,
|
get().fillExtCrateData(crate.id);
|
||||||
fan_tray: add_fan_tray
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else return crate;
|
|
||||||
})
|
})
|
||||||
})),
|
get()._updateTotalOrderPrice();
|
||||||
|
|
||||||
fanTrayAvailableByIndex: (crate_index) => {
|
|
||||||
return get().fanTrayAvailableForMode(get().crates[crate_index].crate_mode);
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -495,5 +575,6 @@ export const useShopStore = createWithEqualityFn((...params) => ({
|
||||||
...useLayout(...params),
|
...useLayout(...params),
|
||||||
...useHighlighted(...params),
|
...useHighlighted(...params),
|
||||||
...useImportJSON(...params),
|
...useImportJSON(...params),
|
||||||
...useFanTray(...params),
|
...useCrateOptions(...params),
|
||||||
|
...useOrderOptions(...params),
|
||||||
}))
|
}))
|
|
@ -53,7 +53,7 @@ export function formatMoney(amount, decimalCount = 2, decimal = ".", thousands =
|
||||||
let i = parseInt(amount = Math.abs(Number(amount) || 0).toFixed(decimalCount)).toString();
|
let i = parseInt(amount = Math.abs(Number(amount) || 0).toFixed(decimalCount)).toString();
|
||||||
let j = (i.length > 3) ? i.length % 3 : 0;
|
let j = (i.length > 3) ? i.length % 3 : 0;
|
||||||
|
|
||||||
return negativeSign + (j ? i.substr(0, j) + thousands : '') + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + thousands) + (decimalCount ? decimal + Math.abs(amount - i).toFixed(decimalCount).slice(2) : "");
|
return negativeSign + (j ? i.substring(0, j) + thousands : '') + i.substring(j).replace(/(\d{3})(?=\d)/g, "$1" + thousands) + (decimalCount ? decimal + Math.abs(amount - i).toFixed(decimalCount).slice(2) : "");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,13 +28,140 @@ const shop_data = {
|
||||||
"rack", "desktop"
|
"rack", "desktop"
|
||||||
],
|
],
|
||||||
|
|
||||||
fanTray: {
|
crateOptions: {
|
||||||
price: 470,
|
options: [
|
||||||
crateModesAvailable: {
|
{"if": [
|
||||||
'rack': true
|
{"==": [{"var": "ext_data.crate_mode"}, "rack",]},
|
||||||
},
|
{type: "Switch", args: {
|
||||||
optionTitle: "Add fan tray",
|
title: "Add fan tray",
|
||||||
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."
|
outvar: "fan_tray",
|
||||||
|
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.",
|
||||||
|
fallback: false
|
||||||
|
}}
|
||||||
|
]},
|
||||||
|
],
|
||||||
|
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: {
|
||||||
|
options: [
|
||||||
|
{"type": "Group", items:[
|
||||||
|
{type: "Switch", args: {
|
||||||
|
title: "Include optional Intel® NUC mini-computer with pre-installed ARTIQ software",
|
||||||
|
outvar: "nuc",
|
||||||
|
tip: "OS: latest stable NixOS with Gnome or KDE with pre-installed ARTIQ software. " +
|
||||||
|
"Hardware (other choices available): Intel® NUC 13 Pro Kit NUC13ANKi7, i7-1360P CPU, " +
|
||||||
|
"32GB RAM from reputable vendor, 1TB M.2 NVM Express SSD from reputable vendor.",
|
||||||
|
fallback: true,
|
||||||
|
}},
|
||||||
|
{
|
||||||
|
"if": [
|
||||||
|
{"var": "nuc"},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: "Radio",
|
||||||
|
args: {
|
||||||
|
title: "Desktop Environment",
|
||||||
|
outvar: "nuc_desktop",
|
||||||
|
variants: ["Gnome", "KDE"],
|
||||||
|
fallback: 0,
|
||||||
|
classes: "form-check-inline ms-4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{type: "Line", args: {title: "Additional software to be pre-installed", outvar: "software", fallback: "",
|
||||||
|
tip: "Pre-install additional software, if needed."}},
|
||||||
|
{"if": [
|
||||||
|
{"var": "ext_data.has_crate"},
|
||||||
|
{type: "Switch", args: {
|
||||||
|
title: "Include promotional USB stick",
|
||||||
|
outvar: "include_usb_stick_nuc",
|
||||||
|
tip: "Choose if you need a USB stick with device database and other relevant files. Alternative is to to receive them via other electronic means (e.g. email or cloud).",
|
||||||
|
fallback: false,
|
||||||
|
}}
|
||||||
|
]},
|
||||||
|
],
|
||||||
|
{"if": [
|
||||||
|
{"var": "ext_data.has_crate"},
|
||||||
|
{type: "Switch", args: {
|
||||||
|
title: "Include promotional USB stick",
|
||||||
|
outvar: "include_usb_stick",
|
||||||
|
tip: "Choose if you need a USB stick with device database and other relevant files. Alternative is to to receive them via other electronic means (e.g. email or cloud).",
|
||||||
|
fallback: true,
|
||||||
|
}}
|
||||||
|
]},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]},
|
||||||
|
{"type": "Group", items: [
|
||||||
|
{
|
||||||
|
type: "Radio",
|
||||||
|
args: {
|
||||||
|
title: "Shipping options",
|
||||||
|
outvar: "shipping",
|
||||||
|
variants: [
|
||||||
|
"Incoterms 2020 FCA",
|
||||||
|
"Incoterms 2020 DAP",
|
||||||
|
"Prepay and add shipping (only available to credit customers)"
|
||||||
|
],
|
||||||
|
fallback: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"if": [
|
||||||
|
{"==": [{"var": "shipping"}, "Incoterms 2020 FCA"]},
|
||||||
|
{type: "Line", args: {title: "Please provide your carrier account information and/or other shipping instructions",
|
||||||
|
outvar: "shipping_instructions", fallback: ""}}
|
||||||
|
]},
|
||||||
|
{"if": [
|
||||||
|
{"==": [{"var": "shipping"}, "Incoterms 2020 DAP"]},
|
||||||
|
{type: "Line", args: {title: "Please provide delivery address",
|
||||||
|
outvar: "shipping_instructions", fallback: "",
|
||||||
|
tip: "Additional customs fees may be charged to you by the carrier at the time of delivery."}}
|
||||||
|
]},
|
||||||
|
{"if": [
|
||||||
|
{"==": [{"var": "shipping"}, "Prepay and add shipping (only available to credit customers)"]},
|
||||||
|
[{type: "Radio", args: {title: "In case of additional customs fees",
|
||||||
|
outvar: "prepay_fees_handling", fallback: 0,
|
||||||
|
variants: [
|
||||||
|
"Add to your final invoice",
|
||||||
|
"Carrier bills you directly"
|
||||||
|
]}},
|
||||||
|
{type: "Line", args: {title: "Please provide delivery address",
|
||||||
|
outvar: "shipping_instructions", fallback: "",}}],
|
||||||
|
]},
|
||||||
|
]}
|
||||||
|
],
|
||||||
|
|
||||||
|
prices: [{
|
||||||
|
"if": [{"var": "nuc"}, {title: "Include optional pre-installed Intel® NUC mini-computer", price: 1300, disable_patch: {"nuc": false}, id: "nuc"}],
|
||||||
|
}],
|
||||||
|
shippingSummary: [
|
||||||
|
{type: "Label", args: {
|
||||||
|
content: ["Shipping method: ", {"var": "shipping"}]
|
||||||
|
}},
|
||||||
|
{"if": [
|
||||||
|
{"var": "shipping_instructions"},
|
||||||
|
{type: "Label", args: {
|
||||||
|
content: [
|
||||||
|
{"if": [
|
||||||
|
{"==": [{"var": "shipping"}, "Incoterms 2020 FCA"]},
|
||||||
|
"carrier account information and/or other shipping instructions: ",
|
||||||
|
"delivery address: "
|
||||||
|
]},
|
||||||
|
{"var": "shipping_instructions"}
|
||||||
|
]
|
||||||
|
}}]},
|
||||||
|
{type: "Label", args: {
|
||||||
|
content: [
|
||||||
|
{"if": [
|
||||||
|
{"==": [{"var": "shipping"}, "Prepay and add shipping (only available to credit customers)"]},
|
||||||
|
["In case of additional customs fees: ", {"lower": {"var": "prepay_fees_handling"}}],
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
}},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
items: {
|
items: {
|
||||||
|
@ -75,8 +202,8 @@ const shop_data = {
|
||||||
{type: "Switch", args: {title: "Optical fiber", outvar: "optics", tip: "Use optical fiber instead of direct attach copper cable"}},
|
{type: "Switch", args: {title: "Optical fiber", outvar: "optics", tip: "Use optical fiber instead of direct attach copper cable"}},
|
||||||
{"if": [
|
{"if": [
|
||||||
{"var": "optics"},
|
{"var": "optics"},
|
||||||
{type: "Radio", args: {title: "Fiber cable length", outvar: "cable_len", variants: ["1 M", "3 M", "5 M"], tip: "The desired length of the optical fiber cable", fallback: 1}},
|
{type: "Radio", args: {title: "Fiber cable length", outvar: "fiber_cable_len", variants: ["1 M", "3 M", "5 M"], tip: "The desired length of the optical fiber cable", fallback: 1}},
|
||||||
{type: "Radio", args: {title: "Copper cable length", outvar: "cable_len", variants: ["0.5 M", "1 M", "2 M"], tip: "The desired length of the direct attach copper cable", fallback: 0}},
|
{type: "Radio", args: {title: "Copper cable length", outvar: "copper_cable_len", variants: ["0.5 M", "1 M", "2 M"], tip: "The desired length of the direct attach copper cable", fallback: 0}},
|
||||||
]}
|
]}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -143,8 +270,8 @@ const shop_data = {
|
||||||
{type: "Switch", args: {title: "Optical fiber", outvar: "optics", tip: "Use optical fiber instead of direct attach copper cable"}},
|
{type: "Switch", args: {title: "Optical fiber", outvar: "optics", tip: "Use optical fiber instead of direct attach copper cable"}},
|
||||||
{"if": [
|
{"if": [
|
||||||
{"var": "optics"},
|
{"var": "optics"},
|
||||||
{type: "Radio", args: {title: "Fiber cable length", outvar: "cable_len", variants: ["1 M", "3 M", "5 M"], tip: "The desired length of the optical fiber cable", fallback: 1}},
|
{type: "Radio", args: {title: "Fiber cable length", outvar: "fiber_cable_len", variants: ["1 M", "3 M", "5 M"], tip: "The desired length of the optical fiber cable", fallback: 1}},
|
||||||
{type: "Radio", args: {title: "Copper cable length", outvar: "cable_len", variants: ["0.5 M", "1 M", "2 M"], tip: "The desired length of the direct attach copper cable", fallback: 0}},
|
{type: "Radio", args: {title: "Copper cable length", outvar: "copper_cable_len", variants: ["0.5 M", "1 M", "2 M"], tip: "The desired length of the direct attach copper cable", fallback: 0}},
|
||||||
]}
|
]}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -756,7 +883,7 @@ const shop_data = {
|
||||||
size: 'small',
|
size: 'small',
|
||||||
type: 'hd68',
|
type: 'hd68',
|
||||||
options: [
|
options: [
|
||||||
{type: "Radio", args: {title: "Cable length", outvar: "cable_len", variants: ["1 M", "2 M", "3 M"], tip: "The desired length of the HD68 cable", fallback: 1}},
|
{type: "Radio", args: {title: "Cable length", outvar: "hd68_cable_len", variants: ["1 M", "2 M", "3 M"], tip: "The desired length of the HD68 cable", fallback: 1}},
|
||||||
],
|
],
|
||||||
options_class: "hd68-idc",
|
options_class: "hd68-idc",
|
||||||
warnings: [
|
warnings: [
|
||||||
|
|
Loading…
Reference in New Issue