@12-apps/discounts 1.17.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -7
- package/package.json +3 -3
- package/src/react/api.ts +6 -2
- package/src/react/combo-cart-preview.tsx +189 -0
- package/src/react/combo-preview-lines.ts +171 -0
- package/src/react/combo-preview.ts +217 -0
- package/src/react/combo-reward-copy.ts +59 -0
- package/src/react/combo-reward-fields.tsx +162 -0
- package/src/react/copy.ts +7 -4
- package/src/react/discount-actions-menu.tsx +20 -26
- package/src/react/discount-dialog.tsx +67 -0
- package/src/react/discount-form-fields.tsx +4 -40
- package/src/react/discount-form-validate.ts +3 -2
- package/src/react/discount-form-values.ts +1 -1
- package/src/react/discount-form.tsx +61 -52
- package/src/react/discount-target-picker.tsx +1 -1
- package/src/react/discounts-screen.tsx +11 -17
- package/src/react/en-US.ts +27 -0
- package/src/react/form-kind.ts +42 -48
- package/src/react/kind-field.tsx +8 -4
- package/src/react/pt-BR.ts +27 -0
- package/src/react/table-config.ts +15 -1
- package/src/server/collections.ts +20 -0
- package/src/server/index.ts +1 -0
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
import type { RowAction } from "@12-apps/ui/data-display/DataViews";
|
|
19
19
|
import { ErrorState } from "@12-apps/ui/data-display/ErrorState";
|
|
20
20
|
import { LoadingState } from "@12-apps/ui/data-display/LoadingState";
|
|
21
|
-
import { Dialog, DialogContent } from "@12-apps/ui/feedback/Dialog";
|
|
22
21
|
import { HeaderButton } from "@12-apps/ui/form/HeaderButton";
|
|
23
22
|
import { Dashboard } from "@12-apps/ui/layout/Dashboard";
|
|
24
23
|
import { Text } from "@12-apps/ui/typography/Text";
|
|
@@ -28,6 +27,7 @@ import type { DiscountsApiClient } from "./api";
|
|
|
28
27
|
import { fill, type DiscountsWebCopy } from "./copy";
|
|
29
28
|
import { DiscountActionsMenu } from "./discount-actions-menu";
|
|
30
29
|
import { DiscountCard } from "./discount-card";
|
|
30
|
+
import { DiscountDialog } from "./discount-dialog";
|
|
31
31
|
import { DiscountForm } from "./discount-form";
|
|
32
32
|
import type { CurrencyFieldComponent } from "./discount-form-fields";
|
|
33
33
|
import { DiscountListCard, discountCells } from "./discount-list-card";
|
|
@@ -214,7 +214,7 @@ function DiscountsGrid({
|
|
|
214
214
|
);
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
/** The create dialog
|
|
217
|
+
/** The create dialog; each opening mounts a fresh form, so it re-seeds empty. */
|
|
218
218
|
function CreateDialog({
|
|
219
219
|
open,
|
|
220
220
|
onClose,
|
|
@@ -227,25 +227,19 @@ function CreateDialog({
|
|
|
227
227
|
menu: MenuBinding;
|
|
228
228
|
}): JSX.Element {
|
|
229
229
|
return (
|
|
230
|
-
<
|
|
230
|
+
<DiscountDialog
|
|
231
231
|
open={open}
|
|
232
232
|
onClose={onClose}
|
|
233
233
|
title={menu.copy.form.createTitle}
|
|
234
|
-
|
|
235
|
-
showCloseButton
|
|
236
|
-
dataTestId="discount-dialog"
|
|
234
|
+
copy={menu.copy}
|
|
237
235
|
>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
/>
|
|
246
|
-
</DialogContent>
|
|
247
|
-
)}
|
|
248
|
-
</Dialog>
|
|
236
|
+
<DiscountForm
|
|
237
|
+
{...menu}
|
|
238
|
+
groups={menu.groups ?? []}
|
|
239
|
+
editing={null}
|
|
240
|
+
onSaved={onSaved}
|
|
241
|
+
/>
|
|
242
|
+
</DiscountDialog>
|
|
249
243
|
);
|
|
250
244
|
}
|
|
251
245
|
|
package/src/react/en-US.ts
CHANGED
|
@@ -139,6 +139,7 @@ export const EN_US_DISCOUNTS_WEB_COPY: DiscountsWebCopy = {
|
|
|
139
139
|
form: {
|
|
140
140
|
createTitle: "New discount",
|
|
141
141
|
editTitle: "Edit discount",
|
|
142
|
+
dialog: { close: EN_US_UI_LABELS.common.close, back: "Back" },
|
|
142
143
|
submitCreate: "Create discount",
|
|
143
144
|
submitEdit: "Save changes",
|
|
144
145
|
name: "Promotion name",
|
|
@@ -201,6 +202,32 @@ export const EN_US_DISCOUNTS_WEB_COPY: DiscountsWebCopy = {
|
|
|
201
202
|
wholeNumberOnly: "Whole numbers only, no decimals.",
|
|
202
203
|
endsBeforeStarts: "The end date must be after the start date.",
|
|
203
204
|
},
|
|
205
|
+
comboRewards: {
|
|
206
|
+
labels: { PERCENTAGE: "Percentage", FIXED_AMOUNT: "Fixed amount", BUNDLE_PRICE: "Fixed price" },
|
|
207
|
+
hints: {
|
|
208
|
+
PERCENTAGE: "The bundle sells for the price of its items minus this discount.",
|
|
209
|
+
FIXED_AMOUNT: "The bundle sells for the price of its items minus this amount.",
|
|
210
|
+
BUNDLE_PRICE: "The bundle sells for this price, whatever its items cost.",
|
|
211
|
+
},
|
|
212
|
+
preview: {
|
|
213
|
+
title: "In the cart",
|
|
214
|
+
itemsRange: "{units} add up to {min}–{max}",
|
|
215
|
+
itemsExact: "{units} add up to {sum}",
|
|
216
|
+
paysRange: "The customer pays {min}–{max}",
|
|
217
|
+
paysRangeOff: "The customer pays {min}–{max} ({off} off)",
|
|
218
|
+
paysExact: "The customer pays {pays}",
|
|
219
|
+
paysExactOff: "The customer pays {pays} ({off} off)",
|
|
220
|
+
paysOff: "The customer pays {off} less",
|
|
221
|
+
paysPriceSaving: "The customer pays {price} — saves {min}–{max}",
|
|
222
|
+
paysPriceSavingExact: "The customer pays {price} — saves {saving}",
|
|
223
|
+
paysPriceSavingUpTo: "The customer pays {price} — saves up to {max}",
|
|
224
|
+
paysUpToPrice: "The customer pays {min}–{price} — saves up to {max}",
|
|
225
|
+
noSaving: "The customer pays the items' price: the bundle price is not lower than theirs.",
|
|
226
|
+
examples: "Examples: {cheapest} · {dearest}",
|
|
227
|
+
example: "{quantity} × {name} → {pays}",
|
|
228
|
+
exampleSaving: "{quantity} × {name} → {pays} (saves {saving})",
|
|
229
|
+
},
|
|
230
|
+
},
|
|
204
231
|
combo: {
|
|
205
232
|
title: "Build the bundle",
|
|
206
233
|
hint: "Each group is one part of the bundle: how many units it takes, and what can go in it.",
|
package/src/react/form-kind.ts
CHANGED
|
@@ -20,18 +20,20 @@ import { DISCOUNT_SCOPES, type DiscountScope, type DiscountType } from "../engin
|
|
|
20
20
|
* |---|---|---|---|
|
|
21
21
|
* | `PERCENTAGE` | `PERCENTAGE` | ORDER / CATEGORY / ITEM | the rate, and what it covers |
|
|
22
22
|
* | `FIXED_AMOUNT` | `FIXED_AMOUNT` | ORDER / CATEGORY / ITEM | the amount, and what it covers |
|
|
23
|
-
* | `COMBO` | `PERCENTAGE` or `
|
|
23
|
+
* | `COMBO` | `PERCENTAGE`, `FIXED_AMOUNT` or `BUNDLE_PRICE` | `COMBO` | the groups, and which of the three rewards |
|
|
24
24
|
* | `FREE_UNITS` | `FREE_UNITS` | `COMBO` | the products, how many, how many free |
|
|
25
25
|
*
|
|
26
26
|
* Two consequences worth stating, because both are deliberate:
|
|
27
27
|
*
|
|
28
|
-
* **A combo is a
|
|
29
|
-
* hambúrgueres e 2 batatas com 15% de
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
28
|
+
* **A combo's reward is a discount off its items OR a closed price**
|
|
29
|
+
* (FUT-4417). "2 refrigerantes, 2 hambúrgueres e 2 batatas com 15% de
|
|
30
|
+
* desconto" and "…menos R$ 5,00" are two offers a merchant describes; "5
|
|
31
|
+
* marmitas por R$ 120" is the third, and the one the form used to refuse. A
|
|
32
|
+
* closed price reprices the matched group whatever its items cost — which is
|
|
33
|
+
* exactly what the merchant asked for, and why the form says so under the price
|
|
34
|
+
* field and previews it against the store's real prices ("Como fica no
|
|
35
|
+
* carrinho"). `BUNDLE_PRICE` is therefore a combo REWARD, not a kind: a rule
|
|
36
|
+
* saved with it before FUT-4417 opens as a combo with "Preço fechado" chosen.
|
|
35
37
|
*
|
|
36
38
|
* **`COMBO` scope is not a choice any more.** It was never one: a combo is
|
|
37
39
|
* defined by its groups, so "combo scope, but with an order-wide discount" is
|
|
@@ -39,15 +41,14 @@ import { DISCOUNT_SCOPES, type DiscountScope, type DiscountType } from "../engin
|
|
|
39
41
|
* genuinely a choice, and the two combo kinds carry `COMBO` implicitly.
|
|
40
42
|
*/
|
|
41
43
|
|
|
42
|
-
/** The kinds
|
|
44
|
+
/** The kinds the form offers, in the order the toggle shows them. */
|
|
43
45
|
export const DISCOUNT_KINDS = ["PERCENTAGE", "FIXED_AMOUNT", "COMBO", "FREE_UNITS"] as const;
|
|
44
46
|
|
|
45
47
|
/**
|
|
46
|
-
* Every kind the form can
|
|
47
|
-
*
|
|
48
|
-
* save without losing what it is.
|
|
48
|
+
* Every kind the form can hold. `BUNDLE_PRICE` is not one any more (FUT-4417):
|
|
49
|
+
* it is the third reward of a `COMBO`, see {@link COMBO_REWARDS}.
|
|
49
50
|
*/
|
|
50
|
-
export type DiscountKind = (typeof DISCOUNT_KINDS)[number]
|
|
51
|
+
export type DiscountKind = (typeof DISCOUNT_KINDS)[number];
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
54
|
* True when this kind's targets are groups — a builder, not a flat picker, and
|
|
@@ -58,7 +59,7 @@ export type DiscountKind = (typeof DISCOUNT_KINDS)[number] | "BUNDLE_PRICE";
|
|
|
58
59
|
* second `includes` that drifts from this one.
|
|
59
60
|
*/
|
|
60
61
|
export function isComboKind(kind: string): boolean {
|
|
61
|
-
return kind === "COMBO" || kind === "FREE_UNITS"
|
|
62
|
+
return kind === "COMBO" || kind === "FREE_UNITS";
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
/** The scopes the toggle offers: every scope except the one a kind implies. */
|
|
@@ -70,39 +71,30 @@ export const SELECTABLE_DISCOUNT_SCOPES: readonly DiscountScope[] = DISCOUNT_SCO
|
|
|
70
71
|
* Which kind an existing rule is, read back from the pair it was stored as.
|
|
71
72
|
*
|
|
72
73
|
* `FREE_UNITS` is checked before the scope because it is the narrower fact: it
|
|
73
|
-
* is only ever legal at `COMBO` scope, so the type alone identifies it.
|
|
74
|
+
* is only ever legal at `COMBO` scope, so the type alone identifies it. A
|
|
75
|
+
* `BUNDLE_PRICE` rule is a combo (FUT-4417) — whatever scope it carries, since
|
|
76
|
+
* the type is only legal at `COMBO`.
|
|
74
77
|
*/
|
|
75
78
|
export function kindOf(rule: { type: string; scope: string } | null): DiscountKind {
|
|
76
79
|
if (rule === null) return "PERCENTAGE";
|
|
77
|
-
if (rule.type === "BUNDLE_PRICE") return "BUNDLE_PRICE";
|
|
78
80
|
if (rule.type === "FREE_UNITS") return "FREE_UNITS";
|
|
79
|
-
if (rule.scope === "COMBO") return "COMBO";
|
|
81
|
+
if (rule.type === "BUNDLE_PRICE" || rule.scope === "COMBO") return "COMBO";
|
|
80
82
|
return rule.type === "FIXED_AMOUNT" ? "FIXED_AMOUNT" : "PERCENTAGE";
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* learned, and present ONLY on the legacy rule's own form: an operator editing
|
|
89
|
-
* a percentage promotion is not offered a bundle price they cannot create.
|
|
90
|
-
*/
|
|
91
|
-
export function kindOptions(editing: { type: string; scope: string } | null): DiscountKind[] {
|
|
92
|
-
const kind = kindOf(editing);
|
|
93
|
-
return kind === "BUNDLE_PRICE" ? [...DISCOUNT_KINDS, kind] : [...DISCOUNT_KINDS];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* A combo's reward is one of the two plain types, chosen separately from the
|
|
98
|
-
* kind — "15% off the combo" and "R$ 5,00 off the combo" are the same kind of
|
|
99
|
-
* promotion with different arithmetic.
|
|
86
|
+
* A combo's reward, chosen separately from the kind — "15% off the combo",
|
|
87
|
+
* "R$ 5,00 off the combo" and "the combo for R$ 120" (FUT-4417) are the same
|
|
88
|
+
* kind of promotion with different arithmetic. In the order the toggle shows
|
|
89
|
+
* them.
|
|
100
90
|
*/
|
|
101
|
-
export const COMBO_REWARDS = ["PERCENTAGE", "FIXED_AMOUNT"] as const;
|
|
91
|
+
export const COMBO_REWARDS = ["PERCENTAGE", "FIXED_AMOUNT", "BUNDLE_PRICE"] as const;
|
|
92
|
+
export type ComboReward = (typeof COMBO_REWARDS)[number];
|
|
102
93
|
|
|
103
|
-
/** Which reward an existing combo was saved with. */
|
|
104
|
-
export function comboRewardOf(rule: { type: string } | null):
|
|
105
|
-
|
|
94
|
+
/** Which reward an existing combo was saved with; a new one starts on the percentage. */
|
|
95
|
+
export function comboRewardOf(rule: { type: string } | null): ComboReward {
|
|
96
|
+
if (rule?.type === "FIXED_AMOUNT" || rule?.type === "BUNDLE_PRICE") return rule.type;
|
|
97
|
+
return "PERCENTAGE";
|
|
106
98
|
}
|
|
107
99
|
|
|
108
100
|
/**
|
|
@@ -114,17 +106,19 @@ export function comboRewardOf(rule: { type: string } | null): string {
|
|
|
114
106
|
* rather than trusted: the toggle is unmounted at combo kinds, so its value is
|
|
115
107
|
* whatever it was before the operator changed their mind.
|
|
116
108
|
*/
|
|
117
|
-
export function typeAndScopeFor(values: {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
109
|
+
export function typeAndScopeFor(values: { kind: string; comboReward: string; scope: string }): {
|
|
110
|
+
type: DiscountType;
|
|
111
|
+
scope: DiscountScope;
|
|
112
|
+
} {
|
|
113
|
+
if (values.kind === "COMBO")
|
|
114
|
+
return {
|
|
115
|
+
type: comboRewardOf({ type: values.comboReward }),
|
|
116
|
+
scope: "COMBO",
|
|
117
|
+
};
|
|
126
118
|
if (values.kind === "FREE_UNITS") return { type: "FREE_UNITS", scope: "COMBO" };
|
|
127
|
-
if (values.kind === "BUNDLE_PRICE") return { type: "BUNDLE_PRICE", scope: "COMBO" };
|
|
128
119
|
const type = values.kind === "FIXED_AMOUNT" ? "FIXED_AMOUNT" : "PERCENTAGE";
|
|
129
|
-
return {
|
|
120
|
+
return {
|
|
121
|
+
type,
|
|
122
|
+
scope: values.scope === "COMBO" ? "ORDER" : (values.scope as DiscountScope),
|
|
123
|
+
};
|
|
130
124
|
}
|
package/src/react/kind-field.tsx
CHANGED
|
@@ -7,14 +7,15 @@ import { Box } from "@12-apps/ui/mui/Box";
|
|
|
7
7
|
|
|
8
8
|
import type { DiscountsWebCopy } from "./copy";
|
|
9
9
|
import { optionsFor } from "./discount-form-fields";
|
|
10
|
-
import type
|
|
10
|
+
import { DISCOUNT_KINDS, type DiscountKind } from "./form-kind";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* What kind of promotion this is — the form's first and most consequential
|
|
14
14
|
* question, and the only one that decides what the rest of it looks like.
|
|
15
15
|
*
|
|
16
|
-
* The option list comes from the caller rather than from the constant
|
|
17
|
-
*
|
|
16
|
+
* The option list comes from the caller rather than from the constant. It used
|
|
17
|
+
* to be one longer on a legacy `BUNDLE_PRICE` rule's form; since FUT-4417 that
|
|
18
|
+
* rule is a combo with the "Preço fechado" reward, so it is always the four.
|
|
18
19
|
*
|
|
19
20
|
* **One line, at every width** (FUT-4157, ADR 0190 in the host). Four equal
|
|
20
21
|
* parts need about 530 px before "Porcentagem" and "Itens grátis" wrap inside
|
|
@@ -28,8 +29,11 @@ import type { DiscountKind } from "./form-kind";
|
|
|
28
29
|
/** The widest pt-BR kind label at the toggle's type size (132 px measured), plus its padding. */
|
|
29
30
|
const PART_MIN_PX = 136;
|
|
30
31
|
|
|
32
|
+
/** How wide the form must be for the four kinds to be a toggle rather than a select. */
|
|
33
|
+
export const KIND_TOGGLE_MIN_PX = DISCOUNT_KINDS.length * PART_MIN_PX;
|
|
34
|
+
|
|
31
35
|
/** The element's own width as it changes; `null` until measured, or where nothing can measure. */
|
|
32
|
-
function useElementWidth(ref: RefObject<HTMLElement | null>): number | null {
|
|
36
|
+
export function useElementWidth(ref: RefObject<HTMLElement | null>): number | null {
|
|
33
37
|
const [width, setWidth] = useState<number | null>(null);
|
|
34
38
|
useLayoutEffect(() => {
|
|
35
39
|
const element = ref.current;
|
package/src/react/pt-BR.ts
CHANGED
|
@@ -130,6 +130,7 @@ export const PT_BR_DISCOUNTS_WEB_COPY: DiscountsWebCopy = {
|
|
|
130
130
|
form: {
|
|
131
131
|
createTitle: "Novo desconto",
|
|
132
132
|
editTitle: "Editar desconto",
|
|
133
|
+
dialog: { close: PT_BR_UI_LABELS.common.close, back: "Voltar" },
|
|
133
134
|
submitCreate: "Criar desconto",
|
|
134
135
|
submitEdit: "Salvar alterações",
|
|
135
136
|
name: "Nome da promoção",
|
|
@@ -191,6 +192,32 @@ export const PT_BR_DISCOUNTS_WEB_COPY: DiscountsWebCopy = {
|
|
|
191
192
|
wholeNumberOnly: "Use só números inteiros, sem vírgula.",
|
|
192
193
|
endsBeforeStarts: "A data de término deve ser posterior à data de início.",
|
|
193
194
|
},
|
|
195
|
+
comboRewards: {
|
|
196
|
+
labels: { PERCENTAGE: "Porcentagem", FIXED_AMOUNT: "Valor fixo", BUNDLE_PRICE: "Preço fechado" },
|
|
197
|
+
hints: {
|
|
198
|
+
PERCENTAGE: "O combo sai pelo preço dos itens menos este desconto.",
|
|
199
|
+
FIXED_AMOUNT: "O combo sai pelo preço dos itens menos este valor.",
|
|
200
|
+
BUNDLE_PRICE: "O combo sai por este preço, qualquer que seja o preço dos itens.",
|
|
201
|
+
},
|
|
202
|
+
preview: {
|
|
203
|
+
title: "Como fica no carrinho",
|
|
204
|
+
itemsRange: "{units} somam de {min} a {max}",
|
|
205
|
+
itemsExact: "{units} somam {sum}",
|
|
206
|
+
paysRange: "Cliente paga de {min} a {max}",
|
|
207
|
+
paysRangeOff: "Cliente paga de {min} a {max} ({off} a menos)",
|
|
208
|
+
paysExact: "Cliente paga {pays}",
|
|
209
|
+
paysExactOff: "Cliente paga {pays} ({off} a menos)",
|
|
210
|
+
paysOff: "Cliente paga {off} a menos",
|
|
211
|
+
paysPriceSaving: "Cliente paga {price} — economiza de {min} a {max}",
|
|
212
|
+
paysPriceSavingExact: "Cliente paga {price} — economiza {saving}",
|
|
213
|
+
paysPriceSavingUpTo: "Cliente paga {price} — economiza até {max}",
|
|
214
|
+
paysUpToPrice: "Cliente paga de {min} a {price} — economiza até {max}",
|
|
215
|
+
noSaving: "Cliente paga o preço dos itens: o preço do combo não é menor que eles.",
|
|
216
|
+
examples: "Exemplos: {cheapest} · {dearest}",
|
|
217
|
+
example: "{quantity} de {name} → {pays}",
|
|
218
|
+
exampleSaving: "{quantity} de {name} → {pays} (economiza {saving})",
|
|
219
|
+
},
|
|
220
|
+
},
|
|
194
221
|
combo: {
|
|
195
222
|
title: "Monte o combo",
|
|
196
223
|
hint: "Cada grupo é uma parte do combo: quantas unidades leva e o que pode entrar nela.",
|
|
@@ -54,9 +54,23 @@ function labelled(labels: Readonly<Record<string, string>>, value: string): stri
|
|
|
54
54
|
return labels[value] ?? value;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/** The Nome column's floor, in design px (the grid converts it through the type scale). */
|
|
58
|
+
const NAME_COLUMN_MIN_PX = 200;
|
|
59
|
+
|
|
57
60
|
export function discountColumns(copy: DiscountsWebCopy): DataViewColumn<DiscountListItem>[] {
|
|
58
61
|
return [
|
|
59
|
-
{
|
|
62
|
+
{
|
|
63
|
+
id: "name",
|
|
64
|
+
header: copy.screen.columns.name,
|
|
65
|
+
accessor: "name",
|
|
66
|
+
searchable: true,
|
|
67
|
+
// The column a row is read by: nine columns share the width, and with no
|
|
68
|
+
// floor the grid squeezed it until "Frete grátis na segunda" stood on
|
|
69
|
+
// three lines with "na" alone on one (ADR 0074 U1, FUT-4417). The floor
|
|
70
|
+
// holds a typical promotion name on one or two lines; the table scrolls
|
|
71
|
+
// sideways inside its own wrapper on a phone, as it already did.
|
|
72
|
+
minWidth: NAME_COLUMN_MIN_PX,
|
|
73
|
+
},
|
|
60
74
|
// Pre-formatted by the page: a cell must not do arithmetic on basis points.
|
|
61
75
|
{ id: "value", header: copy.screen.columns.value, accessor: (row) => row.valueLabel },
|
|
62
76
|
{
|
|
@@ -40,6 +40,26 @@ export interface DiscountTarget {
|
|
|
40
40
|
* category path is built from the same edges. Absent or null at the root.
|
|
41
41
|
*/
|
|
42
42
|
parentId?: string | null;
|
|
43
|
+
/**
|
|
44
|
+
* What a buyer can pay for one unit of this row, cheapest to dearest — its own
|
|
45
|
+
* price and its variations' (FUT-4417). Only an ITEM row carries it, and only
|
|
46
|
+
* so the combo form can say what a group's items sum to ("Como fica no
|
|
47
|
+
* carrinho"); nothing prices a cart from it. Absent: the form shows no
|
|
48
|
+
* preview for a group that can take this row alone.
|
|
49
|
+
*/
|
|
50
|
+
priceRangeCents?: DiscountTargetPriceRange | null;
|
|
51
|
+
/**
|
|
52
|
+
* The category an ITEM row is filed under, for the same preview: a group that
|
|
53
|
+
* names a category takes every product filed under it or under its
|
|
54
|
+
* subcategories. Absent or null: uncategorized.
|
|
55
|
+
*/
|
|
56
|
+
categoryId?: string | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The cheapest and dearest unit price of one target, in integer cents. */
|
|
60
|
+
export interface DiscountTargetPriceRange {
|
|
61
|
+
minCents: number;
|
|
62
|
+
maxCents: number;
|
|
43
63
|
}
|
|
44
64
|
|
|
45
65
|
/**
|