add custom tariffs value inputs

This commit is contained in:
nflnkr 2023-07-27 19:51:12 +03:00 committed by Nastya
parent c1894398f2
commit 912d16c22d
5 changed files with 222 additions and 108 deletions

@ -0,0 +1,87 @@
import { InputAdornment, TextField, Typography, useTheme } from "@mui/material";
import { useState } from "react";
interface Props {
id: string;
adornmentText: string;
onChange: (value: number) => void;
}
export default function NumberInputWithUnitAdornment({ id, adornmentText, onChange }: Props) {
const theme = useTheme();
const [valueField, setValueField] = useState<string>("");
return (
<TextField
type="number"
size="small"
placeholder="Введите вручную"
id={id}
value={valueField}
onChange={e => {
let n = parseInt(e.target.value);
if (!isFinite(n)) n = 0;
onChange(n);
setValueField(n.toString());
}}
sx={{
maxWidth: "200px",
minWidth: "200px",
".MuiInputBase-root": {
pr: 0,
height: "48px",
borderRadius: "8px",
backgroundColor: "#F2F3F7",
"fieldset": {
border: "1px solid" + theme.palette.grey2.main,
},
"&.Mui-focused fieldset": {
borderColor: theme.palette.brightPurple.main,
},
"input": {
height: "31px",
borderRight: !valueField ? "none" : "1px solid #9A9AAF",
},
"&.Mui-focused input": {
borderRight: "1px solid #9A9AAF",
},
"&:not(.Mui-focused) .MuiInputAdornment-root": {
display: !valueField ? "none" : undefined,
},
"&.Mui-focused ::-webkit-input-placeholder": {
color: "transparent",
},
// Hiding arrows
"input::-webkit-outer-spin-button, input::-webkit-inner-spin-button": {
"WebkitAppearance": "none",
margin: 0,
},
"input[type = number]": {
"MozAppearance": "textfield",
}
},
}}
InputProps={{
endAdornment: (
<InputAdornment
position="end"
sx={{
userSelect: "none",
pointerEvents: "none",
pl: "2px",
pr: "13px",
}}
>
<Typography variant="body2" color="#4D4D4D">
{adornmentText}
</Typography>
</InputAdornment>
)
}}
/>
);
}

@ -11,10 +11,10 @@ import { enqueueSnackbar } from "notistack";
interface Props {
serviceKey: string;
tariffs: Privilege[];
privileges: Privilege[];
}
export default function CustomTariffCard({ serviceKey, tariffs }: Props) {
export default function CustomTariffCard({ serviceKey, privileges }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const summaryPriceBeforeDiscounts = useCustomTariffsStore(state => state.summaryPriceBeforeDiscountsMap);
@ -43,29 +43,31 @@ export default function CustomTariffCard({ serviceKey, tariffs }: Props) {
boxShadow: cardShadow,
}}>
<Box sx={{
width: upMd ? "68.5%" : undefined,
p: "20px",
pr: upMd ? "35px" : undefined,
display: "flex",
flexBasis: 0,
flexGrow: 2.37,
flexWrap: "wrap",
flexDirection: "column",
gap: "25px",
}}>
{tariffs.map(tariff =>
{privileges.map(privilege =>
<TariffPrivilegeSlider
key={tariff._id}
tariff={tariff}
key={privilege._id}
privilege={privilege}
/>
)}
</Box>
{!upMd && <Divider sx={{ mx: "20px", my: "10px", borderColor: theme.palette.grey2.main }} />}
<Box sx={{
display: "flex",
flexBasis: 0,
flexGrow: 1,
flexDirection: "column",
justifyContent: "space-between",
alignItems: "start",
color: theme.palette.grey3.main,
width: upMd ? "31.5%" : undefined,
p: "20px",
pl: upMd ? "33px" : undefined,
borderLeft: upMd ? `1px solid ${theme.palette.grey2.main}` : undefined,

@ -7,11 +7,8 @@ import ComplexHeader from "@root/components/ComplexHeader";
import CustomTariffCard from "./CustomTariffCard";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import TotalPrice from "@root/components/TotalPrice";
import { serviceNameByKey } from "@root/utils/serviceKeys";
interface ServiceMap {
[key: string]: string;
}
const servicemap: ServiceMap = { templategen: "Шаблонизатор" };
export default function TariffConstructor() {
const theme = useTheme();
@ -23,72 +20,71 @@ export default function TariffConstructor() {
const basePrice = Object.values(summaryPriceBeforeDiscountsMap).reduce((a, e) => a + e, 0);
const discountedPrice = Object.values(summaryPriceAfterDiscountsMap).reduce((a, e) => a + e, 0);
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: upMd ? "25px" : "20px",
mb: upMd ? "93px" : "48px",
}}
>
{upMd && <ComplexNavText text1="Все тарифы — " text2="Кастомный тариф" />}
<Box
sx={{
mt: "20px",
display: "flex",
flexDirection: "column",
gap: "80px",
}}
>
{Object.entries(customTariffs).map(([serviceKey, tariffs], index) => (
<Box key={serviceKey}>
<Box
sx={{
mb: "40px",
display: "flex",
gap: "10px",
}}
>
{!upMd && index === 0 && (
<IconButton
sx={{
p: 0,
height: "28px",
width: "28px",
color: "black",
}}
>
<ArrowBackIcon />
</IconButton>
)}
<ComplexHeader
text1="Кастомный тариф "
text2={servicemap[serviceKey]}
/>
</Box>
<CustomTariffCard serviceKey={serviceKey} tariffs={tariffs} />
</Box>
))}
</Box>
{upMd && (
<Link
to="/tariffconstructor/savedtariffs"
style={{
display: "block",
marginTop: "60px",
textUnderlinePosition: "under",
color: theme.palette.brightPurple.main,
textDecorationColor: theme.palette.brightPurple.main,
}}
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: upMd ? "25px" : "20px",
mb: upMd ? "93px" : "48px",
}}
>
Ваши сохраненные тарифы
</Link>
)}
<TotalPrice
priceBeforeDiscounts={basePrice}
priceAfterDiscounts={discountedPrice}
/>
</SectionWrapper>
);
{upMd && <ComplexNavText text1="Все тарифы — " text2="Кастомный тариф" />}
<Box
sx={{
mt: "20px",
display: "flex",
flexDirection: "column",
gap: "80px",
}}
>
{Object.entries(customTariffs).map(([serviceKey, privileges], index) => (
<Box key={serviceKey}>
<Box
sx={{
mb: "40px",
display: "flex",
gap: "10px",
}}
>
{!upMd && index === 0 && (
<IconButton
sx={{
p: 0,
height: "28px",
width: "28px",
color: "black",
}}
>
<ArrowBackIcon />
</IconButton>
)}
<ComplexHeader
text1="Кастомный тариф "
text2={serviceNameByKey[serviceKey]}
/>
</Box>
<CustomTariffCard serviceKey={serviceKey} privileges={privileges} />
</Box>
))}
</Box>
{upMd && (
<Link
to="/tariffconstructor/savedtariffs"
style={{
display: "block",
marginTop: "60px",
textUnderlinePosition: "under",
color: theme.palette.brightPurple.main,
textDecorationColor: theme.palette.brightPurple.main,
}}
>
Ваши сохраненные тарифы
</Link>
)}
<TotalPrice
priceBeforeDiscounts={basePrice}
priceAfterDiscounts={discountedPrice}
/>
</SectionWrapper>
);
}

@ -1,6 +1,7 @@
import { useThrottle } from "@frontend/kitui";
import { Box, SliderProps, Typography, useMediaQuery, useTheme } from "@mui/material";
import CustomSlider from "@root/components/CustomSlider";
import NumberInputWithUnitAdornment from "@root/components/NumberInputWithUnitAdornment";
import CalendarIcon from "@root/components/icons/CalendarIcon";
import PieChartIcon from "@root/components/icons/PieChartIcon";
import { Privilege, PrivilegeValueType } from "@root/model/privilege";
@ -8,14 +9,13 @@ import { useCartStore } from "@root/stores/cart";
import { setCustomTariffsUserValue, useCustomTariffsStore } from "@root/stores/customTariffs";
import { useDiscountStore } from "@root/stores/discounts";
import { useUserStore } from "@root/stores/user";
import { formatDateWithDeclention } from "@root/utils/date";
import { getDeclension } from "@root/utils/declension";
import { useEffect, useState } from "react";
const sliderSettingsByType: Record<PrivilegeValueType, Partial<SliderProps>> = {
"день": {
max: 366,
max: 365,
step: 1,
},
"шаблон": {
@ -29,30 +29,29 @@ const sliderSettingsByType: Record<PrivilegeValueType, Partial<SliderProps>> = {
};
interface Props {
tariff: Privilege;
privilege: Privilege;
}
export default function TariffPrivilegeSlider({ tariff }: Props) {
export default function TariffPrivilegeSlider({ privilege }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const userValue = useCustomTariffsStore(state => state.userValuesMap[tariff.serviceKey]?.[tariff._id]) ?? 0;
const userValue = useCustomTariffsStore(state => state.userValuesMap[privilege.serviceKey]?.[privilege._id]) ?? 0;
const discounts = useDiscountStore(state => state.discounts);
const currentCartTotal = useCartStore(state => state.cart.priceAfterDiscounts);
const purchasesAmount = useUserStore(state => state.userAccount?.wallet.purchasesAmount) ?? 0;
const [value, setValue] = useState<number>(userValue);
const throttledValue = useThrottle(value, 200);
const quantityText = `${value} ${getDeclension(value, tariff.value)}`;
const quantityElement = (
<Typography variant="p1" color={theme.palette.brightPurple.main} mt={upMd ? undefined : "12px"}>
{quantityText}
</Typography>
);
const icon = tariff.type === "day"
? <CalendarIcon color={theme.palette.orange.main} bgcolor="#FEDFD0" />
: <PieChartIcon color={theme.palette.orange.main} bgcolor="#FEDFD0" />;
useEffect(function setStoreValue() {
setCustomTariffsUserValue(
privilege.serviceKey,
privilege._id,
throttledValue,
discounts,
currentCartTotal,
purchasesAmount
);
}, [currentCartTotal, discounts, purchasesAmount, privilege._id, privilege.serviceKey, throttledValue]);
function handleSliderChange(event: Event, value: number | number[]) {
if (Array.isArray(value)) throw new Error("Slider uses multiple values instead of one");
@ -60,22 +59,44 @@ export default function TariffPrivilegeSlider({ tariff }: Props) {
setValue(value);
}
useEffect(function setStoreValue() {
console.log(currentCartTotal)
setCustomTariffsUserValue(
tariff.serviceKey,
tariff._id,
throttledValue,
discounts,
currentCartTotal,
purchasesAmount
);
}, [currentCartTotal, discounts, purchasesAmount, tariff._id, tariff.serviceKey, throttledValue]);
const quantityText = `${value} ${getDeclension(value, privilege.value)}`;
const quantityElement = (
<Box sx={{
display: "flex",
gap: "15px",
alignItems: "center",
justifyContent: upMd ? "end" : undefined,
flexWrap: "wrap",
mt: upMd ? undefined : "12px",
}}>
<Typography variant="p1" color={theme.palette.brightPurple.main} textAlign="end">
{quantityText}
</Typography>
<Box sx={{
display: "flex",
gap: "15px",
alignItems: "center",
flexWrap: "wrap",
}}>
<Typography sx={{ fontSize: "16px", lineHeight: "19px", mt: "1px" }}>или</Typography>
<NumberInputWithUnitAdornment
id={"privilege_input_" + privilege._id}
adornmentText={getDeclension(0, privilege.value)}
onChange={value => setValue(value)}
/>
</Box>
</Box>
);
const icon = privilege.type === "day"
? <CalendarIcon color={theme.palette.orange.main} bgcolor="#FEDFD0" />
: <PieChartIcon color={theme.palette.orange.main} bgcolor="#FEDFD0" />;
return (
<Box>
<Typography sx={{ color: theme.palette.grey3.main, mb: "auto" }}>
{tariff.description}
{privilege.description}
</Typography>
<Box sx={{
display: "flex",
@ -84,16 +105,19 @@ export default function TariffPrivilegeSlider({ tariff }: Props) {
}}>
<Box sx={{
display: "flex",
// flexWrap: "wrap",
alignItems: "center",
mb: "8px",
justifyContent: "space-between",
gap: "10px",
}}>
<Box sx={{
display: "flex",
alignItems: "center",
gap: "22px",
}}>
{icon}
<Typography variant="h5">{tariff.name}</Typography>
<Typography variant="h5">{privilege.name}</Typography>
</Box>
{upMd && quantityElement}
</Box>
@ -102,7 +126,7 @@ export default function TariffPrivilegeSlider({ tariff }: Props) {
defaultValue={0}
min={0}
onChange={handleSliderChange}
{...sliderSettingsByType[tariff.value]}
{...sliderSettingsByType[privilege.value]}
/>
{!upMd && quantityElement}
</Box>

5
src/utils/serviceKeys.ts Normal file

@ -0,0 +1,5 @@
export const serviceNameByKey: Record<string, string | undefined> = {
templategen: "Шаблонизатор",
squiz: "Опросник",
reducer: "Сокращатель ссылок",
};