Cache total price calculation

Signed-off-by: Egor Savkin <es@m-labs.hk>
This commit is contained in:
Egor Savkin 2024-01-30 14:45:30 +08:00
parent 4527189994
commit 15d9124025
9 changed files with 61787 additions and 127 deletions

File diff suppressed because one or more lines are too long

View File

@ -3,11 +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";
import {CrateOptions} from "./CrateOptions";
/** /**

View File

@ -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
}

View File

@ -9,8 +9,6 @@ export function CrateOptions({crate_index}) {
const updateOptions = useShopStore((state) => state.updateCrateOptions); const updateOptions = useShopStore((state) => state.updateCrateOptions);
const options_data = useShopStore((state) => state.crates[crate_index].options_data || {}); const options_data = useShopStore((state) => state.crates[crate_index].options_data || {});
console.log(options_data)
const options = ProcessOptions({ const options = ProcessOptions({
options: optionsLogic, options: optionsLogic,
data: options_data, data: options_data,
@ -33,8 +31,6 @@ export function CrateOptions({crate_index}) {
} }
}); });
console.log(options)
return ( return (
<div className="crate-bar"> <div className="crate-bar">
{options} {options}

View File

@ -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',
}}>&nbsp;</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;
}

View File

@ -18,8 +18,6 @@ export function SummaryCratePricedOptions({crate_index}) {
const options = ProcessOptionsToData({options: optionsPrices, data: options_data}); const options = ProcessOptionsToData({options: optionsPrices, data: options_data});
console.log(options, options_data, optionsPrices)
// #!render_count // #!render_count
console.log("SummaryCratePricedOptions renders: ", renderCount) console.log("SummaryCratePricedOptions renders: ", renderCount)

View File

@ -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>

View File

@ -36,12 +36,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),
@ -59,7 +58,7 @@ export function JSONToCrates(description) {
} }
export function CratesToJSON(crates) { export function CratesToJSON(crates) {
const fanTrayAvailable = useShopStore.getState().fanTrayAvailableForMode; const crateOptions = useShopStore.getState().crate_options;
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 +67,7 @@ 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)
}))) })))
}, null, 2) }, null, 2)
} }

View File

@ -9,6 +9,8 @@ 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";
import {forEach} from "react-bootstrap/ElementChildren";
const cards_to_pn_map = (cards) => { const cards_to_pn_map = (cards) => {
@ -58,13 +60,6 @@ const useCrateOptions = ((set, get) => ({
crates: state.crates.map((crate, _i) => { crates: state.crates.map((crate, _i) => {
if (crate_id === crate.id) { if (crate_id === crate.id) {
const previous_options = crate.options_data || {}; const previous_options = crate.options_data || {};
console.log(crate_id, new_options, {
...crate,
options_data: {
...previous_options,
...new_options
}
})
return { return {
...crate, ...crate,
options_data: { options_data: {
@ -80,6 +75,7 @@ const useCrateOptions = ((set, get) => ({
updateCrateOptions: (crate_id, new_options) => { updateCrateOptions: (crate_id, new_options) => {
get().fillExtCrateData(crate_id); get().fillExtCrateData(crate_id);
get()._updateCrateOption(crate_id, new_options); get()._updateCrateOption(crate_id, new_options);
get()._updateTotalOrderPrice();
} }
})); }));
@ -101,6 +97,7 @@ const useOrderOptions = ((set, get) => ({
updateOrderOptions: (new_options) => { updateOrderOptions: (new_options) => {
get().fillOrderExtData(); get().fillOrderExtData();
get()._updateOrderOptions(new_options); get()._updateOrderOptions(new_options);
get()._updateTotalOrderPrice();
} }
})); }));
@ -158,9 +155,11 @@ const useImportJSON = ((set, get) => ({
loadDescription: () => { loadDescription: () => {
get()._loadDescription() get()._loadDescription()
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: {
@ -309,6 +308,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, {
@ -461,17 +461,18 @@ 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; return {total_order_price: sum};
}, }),
// Composite actions that require warnings recalculation: // Composite actions that require warnings recalculation:
@ -481,6 +482,7 @@ const useCart = ((set, get) => ({
get().fillExtData(crate_id); get().fillExtData(crate_id);
get().fillExtCrateData(crate_id); get().fillExtCrateData(crate_id);
get().fillWarnings(crate_id); get().fillWarnings(crate_id);
get()._updateTotalOrderPrice();
}, },
setCrateMode: (id, mode) => { setCrateMode: (id, mode) => {
@ -489,6 +491,7 @@ const useCart = ((set, get) => ({
get().fillExtCrateData(id); get().fillExtCrateData(id);
get().fillWarnings(id); get().fillWarnings(id);
get().setActiveCrate(id); get().setActiveCrate(id);
get()._updateTotalOrderPrice();
}, },
addCardFromBacklog: (crate_to, index_from, index_to, just_mounted) => { addCardFromBacklog: (crate_to, index_from, index_to, just_mounted) => {
@ -502,6 +505,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()
} }
@ -512,6 +516,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);
@ -521,6 +526,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) => {
@ -534,28 +540,13 @@ const useCart = ((set, get) => ({
get().fillWarnings(crate_id); get().fillWarnings(crate_id);
}, },
updateFanTrayOption: (crate_id, add_fan_tray) => set(state => ({
crates: state.crates.map((crate, _i) => {
if (crate_id === crate.id) {
return {
...crate,
fan_tray: add_fan_tray
}
}
else return crate;
})
})),
fanTrayAvailableByIndex: (crate_index) => {
return get().fanTrayAvailableForMode(get().crates[crate_index].crate_mode);
},
initExtData: () => { initExtData: () => {
get().fillOrderExtData(); get().fillOrderExtData();
get().crates.forEach((crate, _i) => { get().crates.forEach((crate, _i) => {
get().fillExtData(crate.id); get().fillExtData(crate.id);
get().fillExtCrateData(crate.id); get().fillExtCrateData(crate.id);
}) })
get()._updateTotalOrderPrice();
} }
})) }))