saleor-dashboard/src/orders/components/OrderDiscountCommonModal/OrderDiscountCommonModal.tsx

342 lines
9.2 KiB
TypeScript
Raw Normal View History

Saleor 1856/implement discount modal 2 (#978) * Add currency to orderline unitprice and update hella lots of types * wip * Add diiscount modal component * Refactor action dialog - move buttons to separate component so they can be reused * Add discount provider to keep logic of discounts separated and wrap proper components * Add discount ccalculator util class, and make draft details summary use it, along with discounts data, modal etc * UUpdate lots of types, fragments, schema etc * Update quries and mutations * ARename OrderLineDiscountModal -> OrderDiscountCommonModal, add types etc * Add order line discount provider + consumer, same for order discount * Fix ts wip * Update schema and types * Update order discount provider * Add nnetto price to order details fragment and update lots of types * Adjust fixtures to order details containing net total * Move both order and order line provider to same dir to make types and utils more accessible * Update schema to match master * Update schema and types * Update order history, add some related components, add events etc. * Fix types * Fix schema to match master * Update messages * Update changelog * Retrigger build * Add stories and update common modal to display floats properly * Add and update stories and tests * Add optional displaying of reason in case it's empty * Make user name label for history events return email if last name and first name are absent * Update schema, types, and mutations to properly refresh * Remove unnecessary imports * Add discounts decorator to draft details page storybook * Fixs after review * Update messages * Small fixes to timeline events * Update types for order shipping price to use net as well, fix labels in draft summary and add colors to theme palette * Updaste tests, messages * Fixs after review * Add theme highlighted active and inactive color text, add valuue conversion to discount modal when changing calculation mode * Add change to changelog * Add extra options to select employee display name for order event when some data is missing. Also add filtering null elements in event header when data missing alltogether and element is null * Refactor selecting employee name in utils * Add conditional to extended timeline event when orderline is null
2021-03-05 14:52:02 +00:00
import {
Card,
CardContent,
Popper,
TextField,
Typography
} from "@material-ui/core";
import { PopperPlacementType } from "@material-ui/core/Popper";
import DialogButtons from "@saleor/components/ActionDialog/DialogButtons";
import CardSpacer from "@saleor/components/CardSpacer";
import ConfirmButton, {
ConfirmButtonTransitionState
} from "@saleor/components/ConfirmButton";
import PriceField from "@saleor/components/PriceField";
import RadioGroupField from "@saleor/components/RadioGroupField";
import { Money } from "@saleor/fragments/types/Money";
import { useUpdateEffect } from "@saleor/hooks/useUpdateEffect";
Saleor 1856/implement discount modal 2 (#978) * Add currency to orderline unitprice and update hella lots of types * wip * Add diiscount modal component * Refactor action dialog - move buttons to separate component so they can be reused * Add discount provider to keep logic of discounts separated and wrap proper components * Add discount ccalculator util class, and make draft details summary use it, along with discounts data, modal etc * UUpdate lots of types, fragments, schema etc * Update quries and mutations * ARename OrderLineDiscountModal -> OrderDiscountCommonModal, add types etc * Add order line discount provider + consumer, same for order discount * Fix ts wip * Update schema and types * Update order discount provider * Add nnetto price to order details fragment and update lots of types * Adjust fixtures to order details containing net total * Move both order and order line provider to same dir to make types and utils more accessible * Update schema to match master * Update schema and types * Update order history, add some related components, add events etc. * Fix types * Fix schema to match master * Update messages * Update changelog * Retrigger build * Add stories and update common modal to display floats properly * Add and update stories and tests * Add optional displaying of reason in case it's empty * Make user name label for history events return email if last name and first name are absent * Update schema, types, and mutations to properly refresh * Remove unnecessary imports * Add discounts decorator to draft details page storybook * Fixs after review * Update messages * Small fixes to timeline events * Update types for order shipping price to use net as well, fix labels in draft summary and add colors to theme palette * Updaste tests, messages * Fixs after review * Add theme highlighted active and inactive color text, add valuue conversion to discount modal when changing calculation mode * Add change to changelog * Add extra options to select employee display name for order event when some data is missing. Also add filtering null elements in event header when data missing alltogether and element is null * Refactor selecting employee name in utils * Add conditional to extended timeline event when orderline is null
2021-03-05 14:52:02 +00:00
import { buttonMessages } from "@saleor/intl";
Use MacawUI (#1229) * Replace withStyleswith useStyles (#1100) * Replace withStyleswith useStyles * Update messages * Use rem as a spacing unit (#1101) * Use rems as spacing units * Fix visual bugs * Update stories * Use macaw-ui as theme provider (#1108) * Use macaw ui as a theme provider * Add react-dom to aliases * Fix jest module resolution * Update useTheme hook usage * Fix test wrapper * Use macaw from git repo * Fix CI * Update stories * Fix aliasing * Extract savebar to macaw ui (#1146) * wip * Use savebar from macaw * Use confirm button from macaw * Improve file structure * Use sidebar context from macaw * Update macaw * Update macaw version * Remove savebar from storybook * Update stories * Use alerts and notifications from macaw (#1166) * Use alerts from macaw * Add notifications from macaw * Update stories * Pin macaw version * Encapsulate limit reached in one component * Remove unused imports * Use backlinks from macaw (#1183) * Use backlink from macaw * Update macaw version * Use macaw sidebar (#1148) * Use sidebar from macaw * Use shipped logo * Use lowercase * Update stories * Use user chip from macaw (#1191) * Use user chip from macaw * Use dedicated components for menu items * Simplify code * Bump version and fix types (#1210) * Rename onBack to onClick * Rename UserChip to UserChipMenu * Rename IMenuItem to SidebarMenuItem * Update macaw version * Fix tables after changes in macaw (#1220) * Update macaw version * Update changelog * Update stories * Fix after rebase * Update to macaw 0.2.0 * Lint files * Update macaw to 0.2.2
2021-07-21 08:59:52 +00:00
import { makeStyles } from "@saleor/macaw-ui";
Saleor 1856/implement discount modal 2 (#978) * Add currency to orderline unitprice and update hella lots of types * wip * Add diiscount modal component * Refactor action dialog - move buttons to separate component so they can be reused * Add discount provider to keep logic of discounts separated and wrap proper components * Add discount ccalculator util class, and make draft details summary use it, along with discounts data, modal etc * UUpdate lots of types, fragments, schema etc * Update quries and mutations * ARename OrderLineDiscountModal -> OrderDiscountCommonModal, add types etc * Add order line discount provider + consumer, same for order discount * Fix ts wip * Update schema and types * Update order discount provider * Add nnetto price to order details fragment and update lots of types * Adjust fixtures to order details containing net total * Move both order and order line provider to same dir to make types and utils more accessible * Update schema to match master * Update schema and types * Update order history, add some related components, add events etc. * Fix types * Fix schema to match master * Update messages * Update changelog * Retrigger build * Add stories and update common modal to display floats properly * Add and update stories and tests * Add optional displaying of reason in case it's empty * Make user name label for history events return email if last name and first name are absent * Update schema, types, and mutations to properly refresh * Remove unnecessary imports * Add discounts decorator to draft details page storybook * Fixs after review * Update messages * Small fixes to timeline events * Update types for order shipping price to use net as well, fix labels in draft summary and add colors to theme palette * Updaste tests, messages * Fixs after review * Add theme highlighted active and inactive color text, add valuue conversion to discount modal when changing calculation mode * Add change to changelog * Add extra options to select employee display name for order event when some data is missing. Also add filtering null elements in event header when data missing alltogether and element is null * Refactor selecting employee name in utils * Add conditional to extended timeline event when orderline is null
2021-03-05 14:52:02 +00:00
import { DiscountValueTypeEnum } from "@saleor/types/globalTypes";
import React, {
ChangeEvent,
MutableRefObject,
useEffect,
useRef,
useState
} from "react";
import { useIntl } from "react-intl";
import { defineMessages } from "react-intl";
import ModalTitle from "./ModalTitle";
import {
ORDER_LINE_DISCOUNT,
OrderDiscountCommonInput,
OrderDiscountType
} from "./types";
const fullNumbersRegex = /^[0-9]*$/;
const numbersRegex = /([0-9]+\.?[0-9]*)$/;
const PERMIL = 0.01;
const useStyles = makeStyles(
theme => ({
container: {
zIndex: 1000,
marginTop: theme.spacing(1)
},
removeButton: {
"&:hover": {
backgroundColor: theme.palette.error.main
},
backgroundColor: theme.palette.error.main,
color: theme.palette.error.contrastText
},
radioContainer: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center"
},
reasonInput: {
marginTop: theme.spacing(1),
width: "100%"
},
buttonWrapper: {
display: "flex",
flexDirection: "row",
flex: 1
}
}),
{ name: "OrderLineDiscountModal" }
);
const messages = defineMessages({
buttonLabel: {
defaultMessage: "Add",
description: "add button label"
},
itemDiscountTitle: {
defaultMessage: "Discount Item",
description: "dialog title item discount"
},
orderDiscountTitle: {
defaultMessage: "Discount this Order by:",
description: "dialog title order discount"
},
percentageOption: {
defaultMessage: "Percentage",
description: "percentage option"
},
fixedAmountOption: {
defaultMessage: "Fixed Amount",
description: "fixed amount"
},
invalidValue: {
defaultMessage: "Invalid value",
description: "value input helper text"
},
discountValueLabel: {
defaultMessage: "Discount value",
description: "value input label"
},
discountReasonLabel: {
defaultMessage: "Reason",
description: "discount reason input lavel"
}
});
export interface OrderDiscountCommonModalProps {
maxPrice: Money;
onConfirm: (discount: OrderDiscountCommonInput) => void;
onClose: () => void;
onRemove: () => void;
modalType: OrderDiscountType;
anchorRef: MutableRefObject<any>;
existingDiscount: OrderDiscountCommonInput;
dialogPlacement: PopperPlacementType;
isOpen: boolean;
confirmStatus: ConfirmButtonTransitionState;
removeStatus: ConfirmButtonTransitionState;
}
const OrderDiscountCommonModal: React.FC<OrderDiscountCommonModalProps> = ({
maxPrice = { amount: null, currency: "" },
onConfirm,
modalType,
anchorRef,
onClose,
onRemove,
existingDiscount,
dialogPlacement,
isOpen,
confirmStatus,
removeStatus
}) => {
const { currency, amount: maxAmount } = maxPrice;
const getInitialDiscountValue = (calculationMode: DiscountValueTypeEnum) => {
if (!existingDiscount?.value) {
return "";
}
const stringifiedValue = existingDiscount.value.toString();
if (calculationMode === DiscountValueTypeEnum.FIXED) {
return parseFloat(stringifiedValue).toFixed(2);
}
return stringifiedValue;
};
const getInitialData = () => {
const calculationMode =
existingDiscount?.calculationMode || DiscountValueTypeEnum.PERCENTAGE;
return {
calculationMode,
reason: existingDiscount?.reason || "",
value: getInitialDiscountValue(calculationMode)
};
};
const initialData = getInitialData();
const [isValueError, setValueError] = useState<boolean>(false);
const [reason, setReason] = useState<string>(initialData.reason);
const [value, setValue] = useState<string>(initialData.value);
const [calculationMode, setCalculationMode] = useState<DiscountValueTypeEnum>(
initialData.calculationMode
);
const previousCalculationMode = useRef(calculationMode);
const classes = useStyles({});
const intl = useIntl();
const discountTypeChoices = [
{
label: intl.formatMessage(messages.percentageOption),
value: DiscountValueTypeEnum.PERCENTAGE
},
{
label: intl.formatMessage(messages.fixedAmountOption),
value: DiscountValueTypeEnum.FIXED
}
];
const isDiscountTypePercentage =
calculationMode === DiscountValueTypeEnum.PERCENTAGE;
const handleSetDiscountValue = (
event: React.ChangeEvent<HTMLInputElement>
) => {
const value = event.target.value;
handleSetError(value);
setValue(value);
};
const getParsedDiscountValue = () => parseFloat(value) || 0;
const isAmountTooLarge = () => {
const topAmount = isDiscountTypePercentage ? 100 : maxAmount;
return getParsedDiscountValue() > topAmount;
};
const handleSetError = (value: string) => {
const regexToCheck = isDiscountTypePercentage
? fullNumbersRegex
: numbersRegex;
setValueError(!regexToCheck.test(value));
};
const handleConfirm = () => {
onConfirm({
calculationMode,
reason,
value: getParsedDiscountValue()
});
};
const setDefaultValues = () => {
setReason(initialData.reason);
setValue(initialData.value);
setCalculationMode(initialData.calculationMode);
setValueError(false);
};
useEffect(setDefaultValues, [
existingDiscount?.value,
existingDiscount?.reason
]);
const handleValueConversion = () => {
if (getParsedDiscountValue() === 0) {
return;
}
const changedFromPercentageToFixed =
previousCalculationMode.current === DiscountValueTypeEnum.PERCENTAGE &&
calculationMode === DiscountValueTypeEnum.FIXED;
const recalculatedValueFromPercentageToFixed = (
getParsedDiscountValue() *
PERMIL *
maxPrice.amount
).toFixed(2);
const recalculatedValueFromFixedToPercentage = Math.round(
(getParsedDiscountValue() * (1 / PERMIL)) / maxPrice.amount
).toString();
const recalculatedValue = changedFromPercentageToFixed
? recalculatedValueFromPercentageToFixed
: recalculatedValueFromFixedToPercentage;
handleSetError(recalculatedValue);
setValue(recalculatedValue);
};
useUpdateEffect(handleValueConversion, [calculationMode]);
Saleor 1856/implement discount modal 2 (#978) * Add currency to orderline unitprice and update hella lots of types * wip * Add diiscount modal component * Refactor action dialog - move buttons to separate component so they can be reused * Add discount provider to keep logic of discounts separated and wrap proper components * Add discount ccalculator util class, and make draft details summary use it, along with discounts data, modal etc * UUpdate lots of types, fragments, schema etc * Update quries and mutations * ARename OrderLineDiscountModal -> OrderDiscountCommonModal, add types etc * Add order line discount provider + consumer, same for order discount * Fix ts wip * Update schema and types * Update order discount provider * Add nnetto price to order details fragment and update lots of types * Adjust fixtures to order details containing net total * Move both order and order line provider to same dir to make types and utils more accessible * Update schema to match master * Update schema and types * Update order history, add some related components, add events etc. * Fix types * Fix schema to match master * Update messages * Update changelog * Retrigger build * Add stories and update common modal to display floats properly * Add and update stories and tests * Add optional displaying of reason in case it's empty * Make user name label for history events return email if last name and first name are absent * Update schema, types, and mutations to properly refresh * Remove unnecessary imports * Add discounts decorator to draft details page storybook * Fixs after review * Update messages * Small fixes to timeline events * Update types for order shipping price to use net as well, fix labels in draft summary and add colors to theme palette * Updaste tests, messages * Fixs after review * Add theme highlighted active and inactive color text, add valuue conversion to discount modal when changing calculation mode * Add change to changelog * Add extra options to select employee display name for order event when some data is missing. Also add filtering null elements in event header when data missing alltogether and element is null * Refactor selecting employee name in utils * Add conditional to extended timeline event when orderline is null
2021-03-05 14:52:02 +00:00
const dialogTitle =
modalType === ORDER_LINE_DISCOUNT
? messages.itemDiscountTitle
: messages.orderDiscountTitle;
const valueFieldSymbol =
calculationMode === DiscountValueTypeEnum.FIXED ? currency : "%";
const isSubmitDisabled =
!getParsedDiscountValue() || isValueError || isAmountTooLarge();
return (
<Popper
open={isOpen}
anchorEl={anchorRef.current}
className={classes.container}
placement={dialogPlacement}
>
<Card>
<ModalTitle title={intl.formatMessage(dialogTitle)} onClose={onClose} />
<CardContent>
<RadioGroupField
innerContainerClassName={classes.radioContainer}
choices={discountTypeChoices}
name="discountType"
variant="inlineJustify"
value={calculationMode}
onChange={event => setCalculationMode(event.target.value)}
/>
<CardSpacer />
<PriceField
label={intl.formatMessage(messages.discountValueLabel)}
error={isValueError}
hint={isValueError && intl.formatMessage(messages.invalidValue)}
value={value}
onChange={handleSetDiscountValue}
currencySymbol={valueFieldSymbol}
/>
<CardSpacer />
<Typography>
{intl.formatMessage(messages.discountReasonLabel)}
</Typography>
<TextField
className={classes.reasonInput}
label={intl.formatMessage(messages.discountReasonLabel)}
value={reason}
onChange={(event: ChangeEvent<HTMLInputElement>) =>
setReason(event.target.value)
}
/>
</CardContent>
<DialogButtons
onConfirm={handleConfirm}
onClose={onClose}
disabled={isSubmitDisabled}
showBackButton={false}
confirmButtonState={confirmStatus}
>
{existingDiscount && (
<div className={classes.buttonWrapper}>
<ConfirmButton
data-test="button-remove"
onClick={onRemove}
variant="contained"
className={classes.removeButton}
transitionState={removeStatus}
>
{intl.formatMessage(buttonMessages.remove)}
</ConfirmButton>
</div>
)}
</DialogButtons>
</Card>
</Popper>
);
};
export default OrderDiscountCommonModal;