add custom tariffs value inputs
This commit is contained in:
parent
c1894398f2
commit
912d16c22d
87
src/components/NumberInputWithUnitAdornment.tsx
Normal file
87
src/components/NumberInputWithUnitAdornment.tsx
Normal file
@ -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
5
src/utils/serviceKeys.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const serviceNameByKey: Record<string, string | undefined> = {
|
||||
templategen: "Шаблонизатор",
|
||||
squiz: "Опросник",
|
||||
reducer: "Сокращатель ссылок",
|
||||
};
|
Loading…
Reference in New Issue
Block a user