Merge branch 'dev' into 'main'

CI\CD setup and some refactoring

See merge request frontend/admin!1
This commit is contained in:
Mikhail 2023-02-21 19:57:28 +00:00
commit 73aed5ea2e
36 changed files with 12727 additions and 34243 deletions

31
.gitlab-ci.yml Normal file

@ -0,0 +1,31 @@
include:
- project: "devops/pena-continuous-integration"
file: "/templates/docker/build-template.gitlab-ci.yml"
- project: "devops/pena-continuous-integration"
file: "/templates/docker/clean-template.gitlab-ci.yml"
- project: "devops/pena-continuous-integration"
file: "/templates/docker/deploy-template.gitlab-ci.yml"
stages:
- clean
- build
- deploy
clear-old-images:
extends: .clean_template
variables:
STAGING_BRANCH: "main"
PRODUCTION_BRANCH: "main"
build-app:
extends: .build_template
variables:
DOCKER_BUILD_PATH: "./Dockerfile"
STAGING_BRANCH: "main"
PRODUCTION_BRANCH: "main"
deploy-to-staging:
extends: .deploy_template
variables:
DEPLOY_TO: "staging"
BRANCH: "main"

17
Dockerfile Normal file

@ -0,0 +1,17 @@
FROM node:19.1-alpine as build
RUN apk update && rm -rf /var/cache/apk/*
WORKDIR /usr/app
COPY package.json .
COPY tsconfig.json .
RUN yarn install --ignore-scripts --non-interactive --frozen-lockfile && yarn cache clean
COPY . .
RUN ls
RUN yarn build
FROM nginx:latest as result
WORKDIR /usr/share/nginx/html
COPY --from=build /usr/app/build/ /usr/share/nginx/html
COPY admin.conf /etc/nginx/conf.d/default.conf

12
admin.conf Normal file

@ -0,0 +1,12 @@
server {
listen 80;
server_name _;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
root /usr/share/nginx/html;
}

@ -0,0 +1,11 @@
services:
admin:
container_name: admin_front
restart: unless-stopped
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
networks:
- penahub_frontend
hostname: hub
networks:
penahub_frontend:
external: true

32012
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -21,6 +21,7 @@
"@types/node": "^16.11.56",
"@types/react": "^18.0.18",
"@types/react-dom": "^18.0.6",
"@types/react-router-dom": "^5.3.3",
"dayjs": "^1.11.5",
"moment": "^2.29.4",
"numeral": "^2.0.6",

36
src/model/cart.ts Normal file

@ -0,0 +1,36 @@
import { ServiceType } from "./tariff";
export interface CartSummary {
mbs: number;
points: number;
days: number;
}
export interface Promocode {
id: number;
name: string;
endless: boolean;
from: string;
dueTo: string;
privileges: Array<Privileges>;
}
export interface Privileges {
good: ServiceType,
discount: number;
}
export interface Discount {
id: number;
name: string;
endless: boolean;
from: string;
dueTo: string;
privileges: Array<Privileges>;
active: boolean;
basketMore: number;
incomeMore: number;
toTime: number;
toCapacity: number;
}

43
src/model/tariff.ts Normal file

@ -0,0 +1,43 @@
export type ServiceType =
| "Шаблонизатор документов"
| "Опросник"
| "Сокращатель ссылок"
| "АБ тесты";
export interface Tariff {
id: number;
name: string;
type: string;
service: ServiceType | "";
disk: number;
time: number;
points: number;
price: number;
}
// TODO тип пакета тарифов надо как-то реорганизовать
export interface ArrayProps {
id: number;
name: string;
type: "package" | "tariff";
service: ServiceType | "";
disk: number;
time: number;
points: number;
price: number;
tariffs?: Array<Tariff>;
}
// Идея для типа пакета тарифов
interface TariffPackage {
id: number;
name: string;
service: ServiceType;
disk: number;
time: number;
points: number;
price: number;
tariffs: Tariff[];
}
type TariffsOrPackages = Array<Tariff | TariffPackage>; // Этот тип должен пойти вместо ArrayProps

@ -12,10 +12,10 @@ import Paper from "@mui/material/Paper";
import { DataGrid, GridColDef, GridSelectionModel, GridToolbar } from "@mui/x-data-grid";
import MenuItem from "@mui/material/MenuItem";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { PrivilegesProps, DiscountProps } from "./types";
import useStore, { StoreState } from "../../../../store";
import theme from "../../../../theme";
import {styled} from "@mui/material/styles";
import { styled } from "@mui/material/styles";
import { Discount } from "../../../../model/cart";
import { useDiscountStore } from "../../../../stores/discounts";
const BoxButton = styled('div')(({ theme }) => ({
@ -26,9 +26,9 @@ const BoxButton = styled('div')(({ theme }) => ({
const columns: GridColDef[] = [
{
field: "id",
headerName: "ID",
{
field: "id",
headerName: "ID",
width: 30,
sortable: false,
},
@ -90,133 +90,105 @@ const columns: GridColDef[] = [
}
];
const rows:Array<DiscountProps> = [
{ id: 1, name: "Скидка 1", endless: false, from: "", dueTo: "", privileges: [
{
good: "Товар 1",
discount: 0.3
},
{
good: "Товар 2",
discount: 0.2
}
], active: false,incomeMore:1, basketMore: 10, toTime: 20, toCapacity: 30 },
{ id: 2, name: "Скидка 2", endless: false, from: "", dueTo: "", privileges: [
{
good: "Товар 3",
discount: 0.3
},
{
good: "Товар 4",
discount: 0.2
}
], active: true,incomeMore:1, basketMore: 10, toTime: 20, toCapacity: 30 },
{ id: 3, name: "Скидка 3", endless: false, from: "", dueTo: "", privileges: [
{
good: "Товар 5",
discount: 0.3
},
{
good: "Товар 6",
discount: 0.2
}
], active: false, incomeMore: 1, basketMore: 10, toTime: 20, toCapacity: 30 },
];
const Discounts: React.FC = () => {
const Discounts: React.FC = () => {
const [checkboxState, setCheckboxState] = React.useState<boolean>(false);
const toggleCheckbox = () => { setCheckboxState( !checkboxState ); }
const toggleCheckbox = () => { setCheckboxState(!checkboxState); };
const [value1, setValue1] = React.useState<Date>( new Date() );
const [value2, setValue2] = React.useState<Date>( new Date() );
const [value1, setValue1] = React.useState<Date>(new Date());
const [value2, setValue2] = React.useState<Date>(new Date());
const [service, setService] = React.useState("Шаблонизатор");
const handleChange = (event: SelectChangeEvent) => {
setService(event.target.value as string);
};
const discountsArray = useDiscountStore(state => state.discountsArray);
const discountsArraySet = useDiscountStore(state => state.setDiscountsArray);
const { discountsArray, discountsArraySet } = useStore<StoreState>((state) => state);
//discountsArraySet( rows );
const { discountsActiveArray, discountsActiveArraySet } = useStore<StoreState>((state) => state);
let discountsActiveArrayUpdated:Array<number>;
const discountsActiveArray = useDiscountStore(state => state.discountsActiveArray);
const discountsActiveArraySet = useDiscountStore(state => state.setDiscountsActiveArray);
let discountsActiveArrayUpdated: Array<number>;
const findActiveDiscounts = () => {
const actives:Array<number> = [];
discountsArray.forEach( (item, i) => {
if( item.active == true ) { actives.push( i ); }
} );
const findActiveDiscounts = () => {
const actives: Array<number> = [];
discountsActiveArrayUpdated = [ ...actives ];
discountsArray.forEach((item, i) => {
if (item.active == true) { actives.push(i); }
});
if( JSON.stringify(discountsActiveArray) != JSON.stringify(discountsActiveArrayUpdated) ) {
discountsActiveArraySet( discountsActiveArrayUpdated );
discountsActiveArrayUpdated = [...actives];
if (JSON.stringify(discountsActiveArray) != JSON.stringify(discountsActiveArrayUpdated)) {
discountsActiveArraySet(discountsActiveArrayUpdated);
}
}
};
findActiveDiscounts();
const discountsArrayConverted = discountsArray.map( (item) => {
const discountsArrayConverted = discountsArray.map((item) => {
const basketMorePerc = Math.round(Number(item.basketMore) * 100) + "%";
const toTimePerc = Math.round(Number(item.toTime) * 100) + "%";
const toCapacityPerc = Math.round(Number(item.toCapacity) * 100) + "%";
const dateFrom = item.from ? new Date( Number(item.from) ) : "";
const dateDueTo = item.from ? new Date( Number(item.dueTo) ) : "";
const dateFrom = item.from ? new Date(Number(item.from)) : "";
const dateDueTo = item.from ? new Date(Number(item.dueTo)) : "";
const strFrom = dateFrom
const strFrom = dateFrom
? `${dateFrom.getDate()}.${dateFrom.getMonth()}.${dateFrom.getFullYear()}`
: "-"
: "-";
const strDueTo = dateDueTo
const strDueTo = dateDueTo
? `${dateDueTo.getDate()}.${dateDueTo.getMonth()}.${dateDueTo.getFullYear()}`
: "-"
: "-";
if( item.privileges.length ) {
const result = item.privileges.reduce( (acc, privilege) => {
acc = acc
? `${acc}, ${privilege.good} - ${privilege.discount}%`
: `${privilege.good} - ${ Math.round(privilege.discount * 100) }%`;
if (item.privileges.length) {
const result = item.privileges.reduce((acc, privilege) => {
acc = acc
? `${acc}, ${privilege.good} - ${privilege.discount}%`
: `${privilege.good} - ${Math.round(privilege.discount * 100)}%`;
return acc;
}, "" );
}, "");
return { ...item, privileges: result, from: strFrom, dueTo: strDueTo,
basketMore: basketMorePerc, toTime: toTimePerc, toCapacity: toCapacityPerc }
return {
...item, privileges: result, from: strFrom, dueTo: strDueTo,
basketMore: basketMorePerc, toTime: toTimePerc, toCapacity: toCapacityPerc
};
} else {
return { ...item, from: strFrom, dueTo: strDueTo,
basketMore: basketMorePerc, toTime: toTimePerc, toCapacity: toCapacityPerc }
return {
...item, from: strFrom, dueTo: strDueTo,
basketMore: basketMorePerc, toTime: toTimePerc, toCapacity: toCapacityPerc
};
}
} );
});
const createDiscount = ( name:string,
discount: number,
addedMore: number,
basketMore: number,
toTime: number,
toCapacity: number, ) => {
const newDiscount = {
id: new Date().getTime(),
name,
endless: checkboxState,
const createDiscount = (name: string,
discount: number,
addedMore: number,
basketMore: number,
toTime: number,
toCapacity: number,) => {
const newDiscount = {
id: new Date().getTime(),
name,
endless: checkboxState,
incomeMore: addedMore,
from: checkboxState ? "" : new Date( value1 ).getTime() +"",
dueTo: checkboxState ? "" : new Date( value2 ).getTime() +"",
privileges: [{
good: service,
discount: discount / 100
}],
from: checkboxState ? "" : new Date(value1).getTime() + "",
dueTo: checkboxState ? "" : new Date(value2).getTime() + "",
privileges: [{
good: service,
discount: discount / 100
}],
active: false,
basketMore: basketMore / 100,
toTime: toTime / 100,
toCapacity: toCapacity / 100
}
} as Discount;
const discountsArrayUpdated = [ ...discountsArray, newDiscount ];
discountsArraySet( discountsArrayUpdated );
}
const discountsArrayUpdated = [...discountsArray, newDiscount];
discountsArraySet(discountsArrayUpdated);
};
const fieldName = React.useRef<HTMLInputElement | null>(null);
const fieldDiscount = React.useRef<HTMLInputElement | null>(null);
@ -232,61 +204,62 @@ const Discounts: React.FC = () => {
// }
const checkFields = () => {
if( fieldName.current != null
&& fieldDiscount.current != null
if (fieldName.current != null
&& fieldDiscount.current != null
&& fieldAddedMore.current != null
&& basketMore.current != null
&& toTime.current != null
&& toCapacity.current != null ) {
&& toCapacity.current != null) {
createDiscount( fieldName.current.value,
Number(fieldDiscount.current.value),
createDiscount(fieldName.current.value,
Number(fieldDiscount.current.value),
Number(fieldAddedMore.current.value),
Number(basketMore.current.value),
Number(toTime.current.value),
Number(toCapacity.current.value) );
Number(toCapacity.current.value));
}
}
const { discountsSelectedRowsData, discountsSelectedRowsDataSet } = useStore<StoreState>((state) => state);
const onRowsSelectionHandler = ( ids:GridSelectionModel ) => {
const result:Array<DiscountProps> = [];
ids.forEach((id) => discountsArray.forEach( (row) => {
if(row.id === id) result.push(row);
} ) );
discountsSelectedRowsDataSet( [ ...result ] );
};
const activation = ( value:boolean ) => {
discountsArray.forEach( (item, i) => {
discountsSelectedRowsData.forEach( (selected) => {
if( item.id == selected.id ) {
if( value ) {
discountsArray[ i ].active = true;
const discountsSelectedRowsData = useDiscountStore(state => state.discountsSelectedRowsData);
const discountsSelectedRowsDataSet = useDiscountStore(state => state.setDiscountsSelectedRowsData);
const onRowsSelectionHandler = (ids: GridSelectionModel) => {
const result: Array<Discount> = [];
ids.forEach((id) => discountsArray.forEach((row) => {
if (row.id === id) result.push(row);
}));
discountsSelectedRowsDataSet([...result]);
};
const activation = (value: boolean) => {
discountsArray.forEach((item, i) => {
discountsSelectedRowsData.forEach((selected) => {
if (item.id == selected.id) {
if (value) {
discountsArray[i].active = true;
} else {
discountsArray[ i ].active = false;
discountsArray[i].active = false;
}
}
} );
} );
});
});
discountsArraySet( discountsArray );
}
discountsArraySet(discountsArray);
};
const PositiveInput = (event:any) => {
const PositiveInput = (event: any) => {
const numberInput = parseInt(event.target.value);
if(isNaN(numberInput) || numberInput < 0) {event.target.value = '0'}
}
if (isNaN(numberInput) || numberInput < 0) { event.target.value = '0'; }
};
return (
<React.Fragment>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<Typography
variant="subtitle1"
<LocalizationProvider dateAdapter={AdapterDayjs}>
<Typography
variant="subtitle1"
sx={{
width: "90%",
height: "60px",
@ -295,24 +268,24 @@ const Discounts: React.FC = () => {
justifyContent: "center",
alignItems: "center",
color: theme.palette.secondary.main
}}>
}}>
СКИДКИ
</Typography>
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "left",
alignItems: "left",
marginTop: "15px",
}}
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "left",
alignItems: "left",
marginTop: "15px",
}}
>
<TextField
id = "standard-basic"
label = { "Название" }
variant = "filled"
color = "secondary"
id="standard-basic"
label={"Название"}
variant="filled"
color="secondary"
sx={{
height: "30px",
}}
@ -320,16 +293,18 @@ const Discounts: React.FC = () => {
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
} }}
}
}}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
} }}
inputRef={ fieldName }
}
}}
inputRef={fieldName}
/>
<Typography
variant="h4"
<Typography
variant="h4"
sx={{
width: "90%",
height: "40px",
@ -337,7 +312,7 @@ const Discounts: React.FC = () => {
color: theme.palette.grayDisabled.main,
marginTop: "75px",
paddingLeft: '10px',
}}>
}}>
Условия:
</Typography>
@ -347,7 +322,7 @@ const Discounts: React.FC = () => {
value={service}
label="Age"
onChange={handleChange}
sx={{
sx={{
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
@ -366,10 +341,10 @@ const Discounts: React.FC = () => {
</Select>
<TextField
id = "standard-basic"
label = { "Процент скидки" }
variant = "filled"
color = "secondary"
id="standard-basic"
label={"Процент скидки"}
variant="filled"
color="secondary"
sx={{
marginTop: "15px"
}}
@ -377,20 +352,22 @@ const Discounts: React.FC = () => {
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
} }}
}
}}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
} }}
inputRef={ fieldDiscount }
}
}}
inputRef={fieldDiscount}
/>
<TextField
id = "standard-basic"
label = { "Внесено больше" }
variant = "filled"
color = "secondary"
type = "number"
id="standard-basic"
label={"Внесено больше"}
variant="filled"
color="secondary"
type="number"
sx={{
marginTop: "15px"
}}
@ -398,21 +375,23 @@ const Discounts: React.FC = () => {
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
} }}
}
}}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
} }}
inputRef={ fieldAddedMore }
}
}}
inputRef={fieldAddedMore}
onChange={PositiveInput}
/>
<TextField
id = "standard-basic"
label = { "Объем в корзине" }
variant = "filled"
color = "secondary"
type = "number"
id="standard-basic"
label={"Объем в корзине"}
variant="filled"
color="secondary"
type="number"
sx={{
marginTop: "15px"
}}
@ -420,61 +399,67 @@ const Discounts: React.FC = () => {
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
} }}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
} }}
inputRef={ basketMore }
onChange={ PositiveInput }
/>
<TextField
id = "standard-basic"
label = { "На время" }
variant = "filled"
color = "secondary"
type = "number"
sx={{
marginTop: "15px"
}
}}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
} }}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
} }}
inputRef={ toTime }
onChange={ PositiveInput }
/>
<TextField
id = "standard-basic"
label = { "На объем" }
variant = "filled"
color = "secondary"
type = "number"
sx={{
marginTop: "15px"
}
}}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
} }}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
} }}
inputRef={ toCapacity }
inputRef={basketMore}
onChange={PositiveInput}
/>
<TableContainer component={Paper} sx={{
width: "100%",
<TextField
id="standard-basic"
label={"На время"}
variant="filled"
color="secondary"
type="number"
sx={{
marginTop: "15px"
}}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
}
}}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
}
}}
inputRef={toTime}
onChange={PositiveInput}
/>
<TextField
id="standard-basic"
label={"На объем"}
variant="filled"
color="secondary"
type="number"
sx={{
marginTop: "15px"
}}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
}
}}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
}
}}
inputRef={toCapacity}
onChange={PositiveInput}
/>
<TableContainer component={Paper} sx={{
width: "100%",
marginTop: "35px",
backgroundColor: theme.palette.content.main
}}>
@ -494,24 +479,24 @@ const Discounts: React.FC = () => {
</Table>
</TableContainer>
<Typography
variant="h4"
<Typography
variant="h4"
sx={{
width: "90%",
height: "40px",
fontWeight: "normal",
color: theme.palette.grayDisabled.main,
marginTop: "55px"
}}>
}}>
Дата действия:
</Typography>
<Box
sx={{
width: "100%",
display: "flex",
flexWrap: 'wrap'
}}
sx={{
width: "100%",
display: "flex",
flexWrap: 'wrap'
}}
>
<Typography sx={{
width: "35px",
@ -523,16 +508,18 @@ const Discounts: React.FC = () => {
<DesktopDatePicker
inputFormat="DD/MM/YYYY"
value={ value1 }
onChange={ (e) => { if(e) { setValue1(e) } } }
value={value1}
onChange={(e) => { if (e) { setValue1(e); } }}
renderInput={(params) => <TextField {...params} />}
InputProps={{ sx: {
height: "40px",
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main }
} }}
InputProps={{
sx: {
height: "40px",
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main }
}
}}
/>
<Typography sx={{
@ -545,16 +532,18 @@ const Discounts: React.FC = () => {
<DesktopDatePicker
inputFormat="DD/MM/YYYY"
value={ value2 }
onChange={ (e) => { if(e) { setValue2(e) } } }
value={value2}
onChange={(e) => { if (e) { setValue2(e); } }}
renderInput={(params) => <TextField {...params} />}
InputProps={{ sx: {
height: "40px",
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main }
} }}
InputProps={{
sx: {
height: "40px",
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main }
}
}}
/>
</Box>
@ -564,7 +553,7 @@ const Discounts: React.FC = () => {
width: "90%",
marginTop: theme.spacing(2),
}}>
<Box sx={{
<Box sx={{
width: "20px",
height: "42px",
display: "flex",
@ -573,14 +562,14 @@ const Discounts: React.FC = () => {
alignItems: "left",
marginRight: theme.spacing(1)
}}>
<Checkbox sx={{
<Checkbox sx={{
color: theme.palette.secondary.main,
"&.Mui-checked": {
color: theme.palette.secondary.main,
},
}} onClick={ () => toggleCheckbox() } />
}} onClick={() => toggleCheckbox()} />
</Box>
<Box sx={{
<Box sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
@ -590,7 +579,7 @@ const Discounts: React.FC = () => {
</Box>
</Box>
<Box sx={{
<Box sx={{
width: "90%",
marginTop: "55px",
display: "flex",
@ -598,8 +587,8 @@ const Discounts: React.FC = () => {
justifyContent: "center",
alignItems: "center"
}}>
<Button
variant = "contained"
<Button
variant="contained"
sx={{
backgroundColor: theme.palette.menu.main,
height: "52px",
@ -609,19 +598,19 @@ const Discounts: React.FC = () => {
backgroundColor: theme.palette.grayMedium.main
}
}}
onClick={ () => checkFields() } >
Cоздать
onClick={() => checkFields()} >
Cоздать
</Button>
</Box>
</Box>
<Box style={{ width: "80%", marginTop: "55px" }}>
<Box style={{ height: 400 }}>
<DataGrid
checkboxSelection={true}
rows={ discountsArrayConverted }
columns={ columns }
<DataGrid
checkboxSelection={true}
rows={discountsArrayConverted}
columns={columns}
sx={{
color: theme.palette.secondary.main,
"& .MuiDataGrid-iconSeparator": {
@ -644,12 +633,12 @@ const Discounts: React.FC = () => {
},
}}
components={{ Toolbar: GridToolbar }}
onSelectionModelChange={ (ids) => onRowsSelectionHandler( ids ) }
onSelectionModelChange={(ids) => onRowsSelectionHandler(ids)}
/>
</Box>
</Box>
<Box sx={{
<Box sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
@ -664,9 +653,9 @@ const Discounts: React.FC = () => {
justifyContent: "space-between",
flexWrap: 'wrap',
}}>
<Button
variant = "contained"
onClick={ () => activation( false ) }
<Button
variant="contained"
onClick={() => activation(false)}
sx={{
backgroundColor: theme.palette.menu.main,
width: "200px",
@ -678,12 +667,12 @@ const Discounts: React.FC = () => {
backgroundColor: theme.palette.grayMedium.main
}
}}>
Деактивировать
Деактивировать
</Button>
<Button
variant = "contained"
onClick={ () => activation( true ) }
<Button
variant="contained"
onClick={() => activation(true)}
sx={{
backgroundColor: theme.palette.menu.main,
width: "200px",
@ -694,15 +683,15 @@ const Discounts: React.FC = () => {
backgroundColor: theme.palette.grayMedium.main
}
}}>
Применить
Применить
</Button>
</BoxButton>
</Box>
</LocalizationProvider>
</React.Fragment>
);
}
};
export default Discounts;

@ -1,18 +0,0 @@
export interface PrivilegesProps {
good: string,
discount: number
}
export interface DiscountProps {
id: number
name: string
endless: boolean
from: string
dueTo: string
privileges: Array<PrivilegesProps>
active: boolean
basketMore: number
incomeMore: number
toTime: number
toCapacity: number
}

@ -9,444 +9,421 @@ import TableCell from "@mui/material/TableCell";
import TableRow from "@mui/material/TableRow";
import TableContainer from "@mui/material/TableContainer";
import Paper from "@mui/material/Paper";
import { DataGrid, GridColDef, GridSelectionModel, GridToolbar } from "@mui/x-data-grid";
import { DataGrid, GridColDef, GridToolbar } from "@mui/x-data-grid";
import MenuItem from "@mui/material/MenuItem";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { PrivilegesProps, PromocodeProps } from "./types";
import useStore, { StoreState } from "../../../../store";
import theme from "../../../../theme";
import { Promocode } from "../../../../model/cart";
import { ServiceType } from "../../../../model/tariff";
import { usePromocodeStore } from "../../../../stores/promocodes";
const columns: GridColDef[] = [
{
field: "id",
headerName: "ID",
width: 30,
sortable: false,
},
{
field: "name",
headerName: "Название промокода",
width: 200,
sortable: false,
},
{
field: "endless",
headerName: "Бесконечный",
width: 120,
sortable: false,
},
{
field: "from",
headerName: "От",
width: 120,
sortable: false,
},
{
field: "dueTo",
headerName: "До",
width: 120,
sortable: false,
},
{
field: "privileges",
headerName: "Привилегии",
width: 210,
sortable: false,
}
{
field: "id",
headerName: "ID",
width: 30,
sortable: false,
},
{
field: "name",
headerName: "Название промокода",
width: 200,
sortable: false,
},
{
field: "endless",
headerName: "Бесконечный",
width: 120,
sortable: false,
},
{
field: "from",
headerName: "От",
width: 120,
sortable: false,
},
{
field: "dueTo",
headerName: "До",
width: 120,
sortable: false,
},
{
field: "privileges",
headerName: "Привилегии",
width: 210,
sortable: false,
}
];
const rows:Array<PromocodeProps> = [
{ id: 1, name: "Промокод 1", endless: false, from: "", dueTo: "", privileges: [
{
good: "Товар 1",
discount: 0.3
},
{
good: "Товар 2",
discount: 0.2
}
] },
{ id: 2, name: "Промокод 2", endless: false, from: "", dueTo: "", privileges: [
{
good: "Товар 3",
discount: 0.3
},
{
good: "Товар 4",
discount: 0.2
}
] },
{ id: 3, name: "Промокод 3", endless: false, from: "", dueTo: "", privileges: [
{
good: "Товар 5",
discount: 0.3
},
{
good: "Товар 6",
discount: 0.2
}
] },
];
const Promocodes: React.FC = () => {
const [checkboxState, setCheckboxState] = React.useState<boolean>(false);
const toggleCheckbox = () => { setCheckboxState(!checkboxState); };
const Promocode: React.FC = () => {
const [checkboxState, setCheckboxState] = React.useState<boolean>(false);
const toggleCheckbox = () => { setCheckboxState( !checkboxState ); }
const [value1, setValue1] = React.useState<Date>(new Date());
const [value2, setValue2] = React.useState<Date>(new Date());
const [value1, setValue1] = React.useState<Date>( new Date() );
const [value2, setValue2] = React.useState<Date>( new Date() );
const [service, setService] = React.useState<ServiceType>("Шаблонизатор документов");
const handleChange = (event: SelectChangeEvent) => {
setService(event.target.value as ServiceType);
};
const [service, setService] = React.useState("Шаблонизатор");
const handleChange = (event: SelectChangeEvent) => {
setService(event.target.value as string);
};
const promocodeArray = usePromocodeStore(state => state.promocodeArray);
const promocodeArraySet = usePromocodeStore(state => state.setPromocodeArray);
const { promocodeArray, promocodeArraySet } = useStore<StoreState>((state) => state);
const promocodeArrayConverted = promocodeArray.map( (item) => {
const dateFrom = item.from ? new Date( Number(item.from) ) : "";
const dateDueTo = item.from ? new Date( Number(item.dueTo) ) : "";
const promocodeArrayConverted = promocodeArray.map((item) => {
const dateFrom = item.from ? new Date(Number(item.from)) : "";
const dateDueTo = item.from ? new Date(Number(item.dueTo)) : "";
const strFrom = dateFrom
? `${dateFrom.getDate()}.${dateFrom.getMonth()}.${dateFrom.getFullYear()}`
: "-"
const strFrom = dateFrom
? `${dateFrom.getDate()}.${dateFrom.getMonth()}.${dateFrom.getFullYear()}`
: "-";
const strDueTo = dateDueTo
? `${dateDueTo.getDate()}.${dateDueTo.getMonth()}.${dateDueTo.getFullYear()}`
: "-"
const strDueTo = dateDueTo
? `${dateDueTo.getDate()}.${dateDueTo.getMonth()}.${dateDueTo.getFullYear()}`
: "-";
if( item.privileges.length ) {
const result = item.privileges.reduce( (acc, privilege) => {
acc = acc
? `${acc}, ${privilege.good} - ${privilege.discount}%`
: `${privilege.good} - ${privilege.discount * 100}%`;
if (item.privileges.length) {
const result = item.privileges.reduce((acc, privilege) => {
acc = acc
? `${acc}, ${privilege.good} - ${privilege.discount}%`
: `${privilege.good} - ${privilege.discount * 100}%`;
return acc;
}, "" );
return acc;
}, "");
return { ...item, privileges: result, from: strFrom, dueTo: strDueTo }
} else {
return { ...item, from: strFrom, dueTo: strDueTo }
}
} );
return { ...item, privileges: result, from: strFrom, dueTo: strDueTo };
} else {
return { ...item, from: strFrom, dueTo: strDueTo };
}
});
const createPromocode = ( name:string, discount: number ) => {
const newPromocode = {
id: new Date().getTime(),
name,
endless: checkboxState,
from: checkboxState ? "" : new Date( value1 ).getTime() +"",
dueTo: checkboxState ? "" : new Date( value2 ).getTime() +"",
privileges: [{
good: service,
discount: discount / 100
}]
}
const createPromocode = (name: string, discount: number) => {
const newPromocode = {
id: new Date().getTime(),
name,
endless: checkboxState,
from: checkboxState ? "" : new Date(value1).getTime() + "",
dueTo: checkboxState ? "" : new Date(value2).getTime() + "",
privileges: [{
good: service,
discount: discount / 100
}]
};
const promocodeArrayUpdated = [ ...promocodeArray, newPromocode ];
promocodeArraySet( promocodeArrayUpdated );
}
const promocodeArrayUpdated = [...promocodeArray, newPromocode];
promocodeArraySet(promocodeArrayUpdated);
};
const fieldName = React.useRef<HTMLInputElement | null>(null);
const fieldDiscount = React.useRef<HTMLInputElement | null>(null);
const fieldName = React.useRef<HTMLInputElement | null>(null);
const fieldDiscount = React.useRef<HTMLInputElement | null>(null);
const checkFields = () => {
if( fieldName.current != null && fieldDiscount.current != null ) {
createPromocode( fieldName.current.value, Number(fieldDiscount.current.value) );
}
}
const checkFields = () => {
if (fieldName.current != null && fieldDiscount.current != null) {
createPromocode(fieldName.current.value, Number(fieldDiscount.current.value));
}
};
return (
<React.Fragment>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<Typography
variant="subtitle1"
sx={{
width: "90%",
height: "60px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
color: theme.palette.secondary.main
}}>
ПРОМОКОД
</Typography>
return (
<React.Fragment>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<Typography
variant="subtitle1"
sx={{
width: "90%",
height: "60px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
color: theme.palette.secondary.main
}}>
ПРОМОКОД
</Typography>
<Box sx={{
display: "flex",
flexDirection: "column",
justifyContent: "left",
alignItems: "left",
marginTop: "15px",
}}>
{/*<Typography */}
{/* variant="h4" */}
{/* sx={{*/}
{/* width: "90%",*/}
{/* height: "40px",*/}
{/* fontWeight: "normal",*/}
{/* color: theme.palette.grayDisabled.main,*/}
{/* marginTop: "35px"*/}
{/*}}>*/}
{/* Название:*/}
{/*</Typography>*/}
<TextField
id = "standard-basic"
label = { "Название" }
variant = "filled"
color = "secondary"
sx={{
height: "30px",
}}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
} }}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
} }}
inputRef={ fieldName }
/>
<Box sx={{
display: "flex",
flexDirection: "column",
justifyContent: "left",
alignItems: "left",
marginTop: "15px",
}}>
{/*<Typography */}
{/* variant="h4" */}
{/* sx={{*/}
{/* width: "90%",*/}
{/* height: "40px",*/}
{/* fontWeight: "normal",*/}
{/* color: theme.palette.grayDisabled.main,*/}
{/* marginTop: "35px"*/}
{/*}}>*/}
{/* Название:*/}
{/*</Typography>*/}
<TextField
id="standard-basic"
label={"Название"}
variant="filled"
color="secondary"
sx={{
height: "30px",
}}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
}
}}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
}
}}
inputRef={fieldName}
/>
<Typography
variant="h4"
sx={{
width: "90%",
height: "40px",
fontWeight: "normal",
color: theme.palette.grayDisabled.main,
marginTop: "75px"
}}>
Условия:
</Typography>
<Typography
variant="h4"
sx={{
width: "90%",
height: "40px",
fontWeight: "normal",
color: theme.palette.grayDisabled.main,
marginTop: "75px"
}}>
Условия:
</Typography>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={service}
label="Age"
onChange={handleChange}
sx={{
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
borderColor: theme.palette.secondary.main
},
".MuiSvgIcon-root ": {
fill: theme.palette.secondary.main,
}
}}
>
<MenuItem value={"Шаблонизатор документов"}>Шаблонизатор</MenuItem>
<MenuItem value={"Опросник"}>Опросник</MenuItem>
<MenuItem value={"Аналитика сокращателя"}>Аналитика сокращателя</MenuItem>
<MenuItem value={"АБ тесты"}>АБ тесты</MenuItem>
</Select>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={service}
label="Age"
onChange={handleChange}
sx={{
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
borderColor: theme.palette.secondary.main
},
".MuiSvgIcon-root ": {
fill: theme.palette.secondary.main,
}
}}
>
<MenuItem value={"Шаблонизатор документов"}>Шаблонизатор</MenuItem>
<MenuItem value={"Опросник"}>Опросник</MenuItem>
<MenuItem value={"Аналитика сокращателя"}>Аналитика сокращателя</MenuItem>
<MenuItem value={"АБ тесты"}>АБ тесты</MenuItem>
</Select>
<TextField
id = "standard-basic"
label = { "Процент скидки" }
variant = "filled"
color = "secondary"
sx={{
marginTop: "15px"
}}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
} }}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
} }}
inputRef={ fieldDiscount }
/>
<TextField
id="standard-basic"
label={"Процент скидки"}
variant="filled"
color="secondary"
sx={{
marginTop: "15px"
}}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
}
}}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
}
}}
inputRef={fieldDiscount}
/>
<TableContainer component={Paper} sx={{
width: "100%",
marginTop: "35px",
backgroundColor: theme.palette.content.main
}}>
<Table sx={{ minWidth: 650, }} aria-label="simple table">
<TableBody>
<TableRow sx={{ border: "1px solid white" }} >
<TableCell component="th" scope="row" sx={{ color: theme.palette.secondary.main }}>
Работает, если заплатите 100500 денег
</TableCell>
</TableRow>
<TableRow sx={{ border: "1px solid white" }} >
<TableCell component="th" scope="row" sx={{ color: theme.palette.secondary.main }}>
Вы должны будете продать душу дьяволу
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
<TableContainer component={Paper} sx={{
width: "100%",
marginTop: "35px",
backgroundColor: theme.palette.content.main
}}>
<Table aria-label="simple table">
<TableBody>
<TableRow sx={{ border: "1px solid white" }} >
<TableCell component="th" scope="row" sx={{ color: theme.palette.secondary.main }}>
Работает, если заплатите 100500 денег
</TableCell>
</TableRow>
<TableRow sx={{ border: "1px solid white" }} >
<TableCell component="th" scope="row" sx={{ color: theme.palette.secondary.main }}>
Вы должны будете продать душу дьяволу
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
<Typography
variant="h4"
sx={{
width: "90%",
height: "40px",
fontWeight: "normal",
color: theme.palette.grayDisabled.main,
marginTop: "55px"
}}>
Дата действия:
</Typography>
<Box
sx={{
width: "100%",
display: "flex",
flexWrap: 'wrap'
}}
>
<Typography sx={{
width: "35px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "left",
}}>С</Typography>
<Typography
variant="h4"
sx={{
width: "90%",
height: "40px",
fontWeight: "normal",
color: theme.palette.grayDisabled.main,
marginTop: "55px"
}}>
Дата действия:
</Typography>
<DesktopDatePicker
inputFormat="DD/MM/YYYY"
value={ value1 }
onChange={ (e) => { if(e) { setValue1(e) } } }
renderInput={(params) => <TextField {...params} />}
InputProps={{ sx: {
height: "40px",
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main }
} }}
/>
<Box
sx={{
width: "100%",
display: "flex",
flexWrap: 'wrap'
}}
>
<Typography sx={{
width: "35px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "left",
}}>С</Typography>
<Typography sx={{
width: "65px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}>по</Typography>
<DesktopDatePicker
inputFormat="DD/MM/YYYY"
value={value1}
onChange={(e) => { if (e) { setValue1(e); } }}
renderInput={(params) => <TextField {...params} />}
InputProps={{
sx: {
height: "40px",
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main }
}
}}
/>
<DesktopDatePicker
inputFormat="DD/MM/YYYY"
value={ value2 }
onChange={ (e) => { if(e) { setValue2(e) } } }
renderInput={(params) => <TextField {...params} />}
InputProps={{ sx: {
height: "40px",
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main }
} }}
/>
</Box>
<Typography sx={{
width: "65px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}>по</Typography>
<DesktopDatePicker
inputFormat="DD/MM/YYYY"
value={value2}
onChange={(e) => { if (e) { setValue2(e); } }}
renderInput={(params) => <TextField {...params} />}
InputProps={{
sx: {
height: "40px",
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main }
}
}}
/>
</Box>
<Box sx={{
display: "flex",
width: "90%",
marginTop: theme.spacing(2),
}}>
<Box sx={{
width: "20px",
height: "42px",
display: "flex",
flexDirection: "column",
justifyContent: "left",
alignItems: "left",
marginRight: theme.spacing(1)
}}>
<Checkbox sx={{
color: theme.palette.secondary.main,
"&.Mui-checked": {
color: theme.palette.secondary.main,
},
}} onClick={ () => toggleCheckbox() } />
</Box>
<Box sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center"
}}>
Бессрочно
</Box>
</Box>
<Box sx={{
display: "flex",
width: "90%",
marginTop: theme.spacing(2),
}}>
<Box sx={{
width: "20px",
height: "42px",
display: "flex",
flexDirection: "column",
justifyContent: "left",
alignItems: "left",
marginRight: theme.spacing(1)
}}>
<Checkbox sx={{
color: theme.palette.secondary.main,
"&.Mui-checked": {
color: theme.palette.secondary.main,
},
}} onClick={() => toggleCheckbox()} />
</Box>
<Box sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center"
}}>
Бессрочно
</Box>
</Box>
<Box sx={{
width: "90%",
marginTop: "55px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center"
}}>
<Button
variant = "contained"
sx={{
backgroundColor: theme.palette.menu.main,
height: "52px",
fontWeight: "normal",
fontSize: "17px",
"&:hover": {
backgroundColor: theme.palette.grayMedium.main
}
}}
onClick={ () => checkFields() } >
Cоздать
</Button>
</Box>
</Box>
<Box sx={{
width: "90%",
marginTop: "55px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center"
}}>
<Button
variant="contained"
sx={{
backgroundColor: theme.palette.menu.main,
height: "52px",
fontWeight: "normal",
fontSize: "17px",
"&:hover": {
backgroundColor: theme.palette.grayMedium.main
}
}}
onClick={() => checkFields()} >
Cоздать
</Button>
</Box>
<Box style={{ width: "80%", marginTop: "55px" }}>
<Box style={{ height: 400 }}>
<DataGrid
checkboxSelection={true}
rows={ promocodeArrayConverted }
columns={ columns }
sx={{
color: theme.palette.secondary.main,
"& .MuiDataGrid-iconSeparator": {
display: "none"
},
"& .css-levciy-MuiTablePagination-displayedRows": {
color: theme.palette.secondary.main
},
"& .MuiSvgIcon-root": {
color: theme.palette.secondary.main
},
"& .MuiTablePagination-selectLabel": {
color: theme.palette.secondary.main
},
"& .MuiInputBase-root": {
color: theme.palette.secondary.main
},
"& .MuiButton-text": {
color: theme.palette.secondary.main
},
}}
components={{ Toolbar: GridToolbar }}
onSelectionModelChange={ (ids) => console.log("datagrid select") }
/>
</Box>
</Box>
</LocalizationProvider>
</React.Fragment>
);
}
</Box>
<Box style={{ width: "80%", marginTop: "55px" }}>
<Box style={{ height: 400 }}>
<DataGrid
checkboxSelection={true}
rows={promocodeArrayConverted}
columns={columns}
sx={{
color: theme.palette.secondary.main,
"& .MuiDataGrid-iconSeparator": {
display: "none"
},
"& .css-levciy-MuiTablePagination-displayedRows": {
color: theme.palette.secondary.main
},
"& .MuiSvgIcon-root": {
color: theme.palette.secondary.main
},
"& .MuiTablePagination-selectLabel": {
color: theme.palette.secondary.main
},
"& .MuiInputBase-root": {
color: theme.palette.secondary.main
},
"& .MuiButton-text": {
color: theme.palette.secondary.main
},
}}
components={{ Toolbar: GridToolbar }}
onSelectionModelChange={(ids) => console.log("datagrid select")}
/>
</Box>
</Box>
</LocalizationProvider>
</React.Fragment>
);
};
export default Promocode;
export default Promocodes;

@ -1,13 +0,0 @@
export interface PrivilegesProps {
good: string,
discount: number
}
export interface PromocodeProps {
id: number
name: string
endless: boolean
from: string
dueTo: string
privileges: Array<PrivilegesProps>
}

@ -0,0 +1,235 @@
import { Avatar, Box, Checkbox, FormControlLabel, IconButton, List, ListItem, ListItemAvatar, ListItemText, TextField, Typography, useTheme } from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
import { useMemo, useState } from "react";
import { calcFitDiscounts, calcTotalAndRowData, separator } from "./utils";
import type { CartSummary, Promocode } from "../../../../model/cart";
import ShoppingCartIcon from "@mui/icons-material/ShoppingCart";
import { useDiscountStore } from "../../../../stores/discounts";
import { useCartStore } from "../../../../stores/cart";
interface Props {
promocode?: Promocode;
}
export default function Cart({ promocode }: Props) {
const theme = useTheme();
const discountsArray = useDiscountStore(state => state.discountsArray);
const discountsActiveArray = useDiscountStore(state => state.discountsActiveArray);
const cartRowsData = useCartStore(state => state.cartRowsData);
const cartRowsDataSet = useCartStore(state => state.setCartRowsData);
const [isNonCommercial, setIsNonCommercial] = useState(false);
const [addedValueField, setAddedValueField] = useState("");
const cartSummary = useMemo(() => {
const cartSum: { [key: string]: CartSummary; } = {};
cartRowsData.forEach((row) => {
const prev = cartSum[row.service];
cartSum[row.service] = {
mbs: row.disk + (prev?.mbs || 0),
points: row.points + (prev?.points || 0),
days: row.time + (prev?.days || 0),
};
});
return cartSum;
}, [cartRowsData]);
const fitDiscounts = useMemo(() =>
calcFitDiscounts(discountsArray, discountsActiveArray, cartSummary, addedValueField),
[addedValueField, cartSummary, discountsActiveArray, discountsArray]
);
const { totalPrice, calculatedCartRowData } = useMemo(() => calcTotalAndRowData(
cartRowsData,
isNonCommercial,
discountsArray,
discountsActiveArray,
fitDiscounts,
addedValueField,
cartSummary,
promocode,
), [addedValueField, cartRowsData, cartSummary, discountsActiveArray, discountsArray, fitDiscounts, isNonCommercial, promocode]);
const { resultPrice, discountText } = useMemo(() => {
const discounts: Array<number> = [];
let resultPrice = totalPrice;
if (isNonCommercial) {
resultPrice *= 0.2;
return { resultPrice, discountText: `80%` };
}
// применяем активные скидки за объем корзины
if (fitDiscounts.length >= 0 && !isNonCommercial && !promocode) {
fitDiscounts.forEach(activeDiscount => {
const discount = discountsArray[activeDiscount];
if ((discount.basketMore > 0 && totalPrice > discount.basketMore && discount.basketMore === fitDiscounts.reduce((a, e) => Math.max(a, discountsArray[e].basketMore), 0)) ||
(discount.incomeMore > 0 && parseInt(addedValueField) > discount.incomeMore)) {
resultPrice *= (1 - discount.privileges[0].discount);
discounts.push(discount.privileges[0].discount);
}
});
}
const discountText = discounts.map(e => `${(e * 100).toFixed(2)}%`).join(' × ') + ` = ${(100 - discounts.reduce((a: number, cv: number) => a * (1 - cv), 100)).toFixed(2)}%`;
return {
resultPrice,
discountText,
};
}, [addedValueField, discountsArray, fitDiscounts, isNonCommercial, promocode, totalPrice]);
const handleRemoveBasket = (id: number) => cartRowsDataSet(cartRowsData.filter((row) => row.id !== id));
return (
<Box sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
paddingBottom: "45px"
}}>
<Typography id="transition-modal-title" variant="caption">
Корзина
</Typography>
<Box sx={{
display: "flex",
marginTop: "15px",
marginBottom: "15px",
maxWidth: "350px",
width: '100%',
justifyContent: "space-between"
}}>
<FormControlLabel
label="НКО"
control={<Checkbox
sx={{
color: theme.palette.secondary.main,
"&.Mui-checked": {
color: theme.palette.secondary.main,
},
}}
onClick={() => setIsNonCommercial(prev => !prev)}
/>}
/>
<TextField
id="standard-basic"
label={"Внесено"}
variant="filled"
size="small"
color="secondary"
type="number"
sx={{
width: "200px"
}}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
}
}}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
}
}}
value={addedValueField}
onChange={e => setAddedValueField(Number(e.target.value) >= 0 ? e.target.value : "")}
/>
</Box>
<List sx={{
border: "1px solid",
borderColor: theme.palette.secondary.main,
maxWidth: '745px',
width: '100%',
}}>
<ListItem>
<ListItemText
primary="Название"
sx={{
textAlign: "center",
// minWidth: "250px",
maxWidth: "250px",
padding: '0 10px'
}}
/>
<ListItemText
primary="Цена"
sx={{
textAlign: "center",
// minWidth: "200px",
padding: '0 10px',
maxWidth: "200px"
}}
/>
<ListItemText
primary="Скидки"
sx={{
textAlign: "center",
// minWidth: "200px",
padding: '0 10px',
maxWidth: "400px"
}}
/>
<IconButton edge="end" aria-label="delete">
<DeleteIcon sx={{
color: theme.palette.grayDisabled.main,
display: "none"
}} />
</IconButton>
</ListItem>
{calculatedCartRowData.map((cartRow) => (
<ListItem key={cartRow.id}>
<ListItemAvatar>
<Avatar sx={{ backgroundColor: theme.palette.secondary.main }}>
<ShoppingCartIcon sx={{
color: theme.palette.content.main,
}} />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={cartRow.name}
sx={{ maxWidth: "250px" }}
/>
<ListItemText
primary={`${separator(cartRow.price)}`}
sx={{ textAlign: "center", maxWidth: "200px" }}
/>
<ListItemText
primary={`${(cartRow.appliedDiscounts.map(e => (e * 100).toFixed(2)).join(' × '))} = ${(100 - cartRow.appliedDiscounts.reduce((a: number, cv: number) => a * (1 - cv), 100)).toFixed(2)}%`}
sx={{ textAlign: "center", maxWidth: "400px" }}
/>
<IconButton edge="end" aria-label="delete" onClick={() => handleRemoveBasket(cartRow.id)}>
<DeleteIcon sx={{
color: theme.palette.secondary.main,
}} />
</IconButton>
</ListItem>
))}
<Typography id="transition-modal-title" variant="h6" sx={{
fontWeight: "normal",
textAlign: "center",
marginTop: "15px",
fontSize: "16px"
}}>
Скидки: &ensp; {discountText}
</Typography>
<Typography id="transition-modal-title" variant="h6" sx={{
fontWeight: "normal",
textAlign: "center",
marginTop: "10px"
}}>
ИТОГО: &ensp; {resultPrice}
</Typography>
</List>
</Box>
);
}

@ -1,81 +0,0 @@
import * as React from "react";
import { Box, Typography, Button } from "@mui/material";
import theme from "../../../../../theme";
export interface MWProps {
openModal: (type:number, num: number) => void
}
const Contractor: React.FC<MWProps> = ({ openModal }) => {
return (
<React.Fragment>
<Typography
variant="subtitle1"
sx={{
width: "90%",
height: "60px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
color: theme.palette.secondary.main
}}>
Сокращатель ссылок
</Typography>
<Box sx={{
marginTop: "35px",
display: "grid",
gridTemplateColumns: "repeat(2, 1fr)",
gridGap: "20px",
marginBottom: "120px",
}}>
<Button
variant = "contained"
onClick={ () => openModal(3, 1) }
sx={{
backgroundColor: theme.palette.menu.main,
padding: '11px 65px',
fontWeight: "normal",
fontSize: "17px",
"&:hover": {
backgroundColor: theme.palette.grayMedium.main
}
}}>
Создать тариф <br /> на аналитику время
</Button>
<Button
variant = "contained"
onClick={ () => openModal(3, 1) }
sx={{
backgroundColor: theme.palette.menu.main,
padding: '11px 65px',
fontWeight: "normal",
fontSize: "17px",
"&:hover": {
backgroundColor: theme.palette.grayMedium.main
}
}}>
Создать тариф <br /> на a/b тесты время
</Button>
<Button
variant = "contained"
sx={{
backgroundColor: theme.palette.menu.main,
padding: '11px 65px',
fontWeight: "normal",
fontSize: "17px",
"&:hover": {
backgroundColor: theme.palette.grayMedium.main
}
}}>
Изменить тариф
</Button>
</Box>
</React.Fragment>
);
}
export default Contractor;

@ -0,0 +1,32 @@
import { Button, SxProps, Theme, useTheme } from "@mui/material";
import { MouseEventHandler, ReactNode } from "react";
interface Props {
onClick?: MouseEventHandler<HTMLButtonElement>;
children: ReactNode;
sx?: SxProps<Theme>;
}
export default function CustomButton({ onClick, children, sx }: Props) {
const theme = useTheme();
return (
<Button
variant="contained"
onClick={onClick}
sx={{
backgroundColor: theme.palette.menu.main,
padding: '11px 25px',
fontWeight: "normal",
fontSize: "17px",
"&:hover": {
backgroundColor: theme.palette.grayMedium.main
},
...sx,
}}
>
{children}
</Button>
);
}

@ -0,0 +1,28 @@
import { Typography, useTheme } from "@mui/material";
import { ReactNode } from "react";
interface Props {
children: ReactNode;
}
export default function CustomHeader({ children }: Props) {
const theme = useTheme();
return (
<Typography
variant="subtitle1"
sx={{
width: "90%",
height: "60px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
color: theme.palette.secondary.main
}}
>
{children}
</Typography>
);
}

File diff suppressed because it is too large Load Diff

@ -1,185 +1,187 @@
import * as React from "react";
import { Box, Modal, Fade, Backdrop, Typography, Button, TextField } from "@mui/material";
import { ArrayProps } from "../types";
import useStore, { StoreState } from "../../../../../store";
import { Box, Modal, Fade, Backdrop, Button, TextField } from "@mui/material";
import theme from "../../../../../theme";
import { ArrayProps } from "../../../../../model/tariff";
import { useTariffStore } from "../../../../../stores/tariffs";
export interface MWProps {
open: boolean
type: number
variant: number
close: () => void
open: boolean;
type: number;
variant: number;
close: () => void;
}
const ModalMini = ({open, type, variant, close}: MWProps ) => {
let tariffsArray:Array<ArrayProps> = useStore((state) => state.tariffsArray);
const { tariffsArraySet } = useStore<StoreState>((state) => state);
const ModalMini = ({ open, type, variant, close }: MWProps) => {
let tariffsArray = useTariffStore(state => state.tariffs);
const tariffsArraySet = useTariffStore(state => state.setTariffs);
const types = [ "", "Шаблонизатор документов", "Опросник", "Сокращатель ссылок" ];
const variants = [ "Количество", "Срок (дней)", "Количество (гб)" ];
const types = ["", "Шаблонизатор документов", "Опросник", "Сокращатель ссылок"];
const variants = ["Количество", "Срок (дней)", "Количество (гб)"];
const fieldName = React.useRef<HTMLInputElement | null>(null);
const fieldTime = React.useRef<HTMLInputElement | null>(null);
const fieldPrice = React.useRef<HTMLInputElement | null>(null);
const fieldName = React.useRef<HTMLInputElement | null>(null);
const fieldTime = React.useRef<HTMLInputElement | null>(null);
const fieldPrice = React.useRef<HTMLInputElement | null>(null);
const checkTariff = () => {
if( fieldName.current != null && fieldTime.current != null && fieldPrice.current != null ) {
if( fieldName.current.value && fieldTime.current.value && fieldPrice.current.value ) {
const getData = localStorage.getItem("tariffs");
if( getData != null ) { tariffsArray = JSON.parse(getData); }
const checkTariff = () => {
if (fieldName.current != null && fieldTime.current != null && fieldPrice.current != null) {
if (fieldName.current.value && fieldTime.current.value && fieldPrice.current.value) {
const data = [ 0, 0, 0 ];
const data = [0, 0, 0];
if( variant == 0 ) { data[ 0 ] = parseInt(fieldTime.current.value); }
if( variant == 1 ) { data[ 1 ] = parseInt(fieldTime.current.value); }
if( variant == 2 ) { data[ 2 ] = parseInt(fieldTime.current.value); }
if (variant === 0) { data[0] = parseInt(fieldTime.current.value); }
if (variant === 1) { data[1] = parseInt(fieldTime.current.value); }
if (variant === 2) { data[2] = parseInt(fieldTime.current.value); }
const tariffsArrayNew = [...tariffsArray, {
"id": new Date().getTime(),
"name": fieldName.current.value,
"type": "tariff",
"service": types[ type ],
"disk": data[ 2 ],
"time": data[ 1 ],
"points": data[ 0 ],
"price": +fieldPrice.current.value
} ];
const tariffsArrayNew = [...tariffsArray, {
"id": new Date().getTime(),
"name": fieldName.current.value,
"type": "tariff",
"service": types[type],
"disk": data[2],
"time": data[1],
"points": data[0],
"price": +fieldPrice.current.value
} as ArrayProps];
tariffsArraySet( tariffsArrayNew );
localStorage.setItem( "tariffs", JSON.stringify( tariffsArrayNew ) );
close();
tariffsArraySet(tariffsArrayNew);
console.log( tariffsArrayNew );
}
}
}
close();
}
}
};
return (
<React.Fragment>
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
open={ open }
onClose={ () => close() }
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
>
<Fade in={open}>
<Box sx={{
position: "absolute" as "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "350px",
height: "350px",
bgcolor: theme.palette.menu.main,
boxShadow: 24,
color: theme.palette.secondary.main,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}>
<TextField
id = "standard-basic"
label = { types[ type ] }
disabled={ true }
variant = "filled"
color = "secondary"
sx = {{ width: "80%", marginTop: theme.spacing(1) }}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
} }}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
} }}
/>
return (
<React.Fragment>
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
open={open}
onClose={() => close()}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
>
<Fade in={open}>
<Box sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "350px",
height: "350px",
bgcolor: theme.palette.menu.main,
boxShadow: 24,
color: theme.palette.secondary.main,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}>
<TextField
id="standard-basic"
label={types[type]}
disabled={true}
variant="filled"
color="secondary"
sx={{ width: "80%", marginTop: theme.spacing(1) }}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
}
}}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
}
}}
/>
<TextField
id = "standard-basic"
label = { "Название тарифа" }
variant = "filled"
color = "secondary"
sx = {{ width: "80%", marginTop: theme.spacing(1) }}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
} }}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
} }}
inputRef={ fieldName }
/>
<TextField
id="standard-basic"
label={"Название тарифа"}
variant="filled"
color="secondary"
sx={{ width: "80%", marginTop: theme.spacing(1) }}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
}
}}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
}
}}
inputRef={fieldName}
/>
<TextField
id = "standard-basic"
label = { variants[ variant ] }
variant = "filled"
color = "secondary"
sx = {{ width: "80%", marginTop: theme.spacing(1) }}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
} }}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
} }}
inputRef={ fieldTime }
/>
<TextField
id="standard-basic"
label={variants[variant]}
variant="filled"
color="secondary"
sx={{ width: "80%", marginTop: theme.spacing(1) }}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
}
}}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
}
}}
inputRef={fieldTime}
/>
<TextField
id = "standard-basic"
label = "Цена"
variant = "filled"
color = "secondary"
sx = {{ width: "80%", marginTop: theme.spacing(1) }}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
} }}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
} }}
inputRef={ fieldPrice }
/>
<TextField
id="standard-basic"
label="Цена"
variant="filled"
color="secondary"
sx={{ width: "80%", marginTop: theme.spacing(1) }}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
}
}}
InputLabelProps={{
style: {
color: theme.palette.secondary.main
}
}}
inputRef={fieldPrice}
/>
<Button
variant = "contained"
onClick={ () => checkTariff() }
sx={{
backgroundColor: theme.palette.grayDark.main,
marginTop: "30px",
height: "42px",
fontWeight: "normal",
fontSize: "17px",
"&:hover": {
backgroundColor: theme.palette.grayMedium.main
}
}}>
Применить
</Button>
</Box>
</Fade>
</Modal>
</React.Fragment>
);
}
<Button
variant="contained"
onClick={() => checkTariff()}
sx={{
backgroundColor: theme.palette.grayDark.main,
marginTop: "30px",
height: "42px",
fontWeight: "normal",
fontSize: "17px",
"&:hover": {
backgroundColor: theme.palette.grayMedium.main
}
}}>
Применить
</Button>
</Box>
</Fade>
</Modal>
</React.Fragment>
);
};
export default ModalMini;

@ -1,81 +0,0 @@
import * as React from "react";
import { Box, Typography, Button } from "@mui/material";
import theme from "../../../../../theme";
export interface MWProps {
openModal: (type:number, num: number) => void
}
const Quiz: React.FC<MWProps> = ({ openModal }) => {
return (
<React.Fragment>
<Typography
variant="subtitle1"
sx={{
width: "90%",
height: "60px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
color: theme.palette.secondary.main
}}>
Опросник
</Typography>
<Box sx={{
marginTop: "35px",
display: "grid",
gridTemplateColumns: "repeat(2, 1fr)",
gridGap: "20px",
marginBottom: "120px",
}}>
<Button
variant = "contained"
onClick={ () => openModal(2, 1) }
sx={{
backgroundColor: theme.palette.menu.main,
padding: "11px 43px",
fontWeight: "normal",
fontSize: "17px",
"&:hover": {
backgroundColor: theme.palette.grayMedium.main
}
}}>
Создать тариф на время
</Button>
<Button
variant = "contained"
onClick={ () => openModal(2, 0) }
sx={{
backgroundColor: theme.palette.menu.main,
padding: '11px 43px',
fontWeight: "normal",
fontSize: "17px",
"&:hover": {
backgroundColor: theme.palette.grayMedium.main
}
}}>
Создать тариф на объем
</Button>
<Button
variant = "contained"
sx={{
backgroundColor: theme.palette.menu.main,
padding: '11px 43px',
fontWeight: "normal",
fontSize: "17px",
"&:hover": {
backgroundColor: theme.palette.grayMedium.main
}
}}>
Изменить тариф
</Button>
</Box>
</React.Fragment>
);
}
export default Quiz;

@ -1,95 +0,0 @@
import * as React from "react";
import { Box, Typography, Button } from "@mui/material";
import theme from "../../../../../theme";
export interface MWProps {
openModal: (type:number, num: number) => void
}
const Templater: React.FC<MWProps> = ({ openModal }) => {
return (
<React.Fragment>
<Typography
variant="subtitle1"
sx={{
width: "90%",
height: "60px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
color: theme.palette.secondary.main
}}>
Шаблонизатор документов
</Typography>
<Box sx={{
marginTop: "35px",
display: "grid",
gridTemplateColumns: "repeat(2, 1fr)",
gridGap: "20px",
marginBottom: "120px",
}}>
<Button
variant = "contained"
onClick={ () => openModal(1, 1) }
sx={{
backgroundColor: theme.palette.menu.main,
fontWeight: "normal",
fontSize: "17px",
padding: '11px 25px',
"&:hover": {
backgroundColor: theme.palette.grayMedium.main
}
}}>
Создать тариф на время
</Button>
<Button
variant = "contained"
onClick={ () => openModal(1, 0) }
sx={{
backgroundColor: theme.palette.menu.main,
padding: '11px 25px',
fontWeight: "normal",
fontSize: "17px",
"&:hover": {
backgroundColor: theme.palette.grayMedium.main
}
}}>
Создать тариф на объем
</Button>
<Button
variant = "contained"
onClick={ () => openModal(1, 2) }
sx={{
backgroundColor: theme.palette.menu.main,
padding: '11px 25px',
fontWeight: "normal",
fontSize: "17px",
"&:hover": {
backgroundColor: theme.palette.grayMedium.main
}
}}>
Создать тариф на гигабайты
</Button>
<Button
variant = "contained"
sx={{
backgroundColor: theme.palette.menu.main,
padding: '11px 25px',
fontWeight: "normal",
fontSize: "17px",
"&:hover": {
backgroundColor: theme.palette.grayMedium.main
}
}}>
Изменить тариф
</Button>
</Box>
</React.Fragment>
);
}
export default Templater;

@ -1,97 +1,127 @@
import * as React from "react";
import { Box } from "@mui/material";
import Templater from "./Templater";
import Quiz from "./Quiz";
import Contractor from "./Contractor";
import DataGridElement from "./DataGridElement";
import ModalMini from "./ModalMini";
import ModalPackage from "./ModalPackage";
import { ArrayProps, Tariff } from "./types";
import useStore from "../../../../store";
import theme from "../../../../theme";
import { ReactNode } from "react";
import CustomButton from "./CustomButton";
import CustomHeader from "./CustomHeader";
import { ArrayProps, Tariff } from "../../../../model/tariff";
import { useTariffStore } from "../../../../stores/tariffs";
const Tariffs: React.FC = () => {
const [openModalMini, setOpenModalMini] = React.useState(false);
const ButtonContainer: React.FC<{ children: ReactNode; }> = ({ children }) => {
return <Box sx={{
marginTop: "35px",
display: "grid",
gridTemplateColumns: "repeat(2, 1fr)",
gridGap: "20px",
marginBottom: "120px",
}}>
{children}
</Box>;
};
const handleOpenModalMini = () => { setOpenModalMini(true); };
const handleCloseModalMini = () => { setOpenModalMini(false); };
const Tariffs: React.FC = () => {
const [openModalMini, setOpenModalMini] = React.useState(false);
const [type, setType] = React.useState( 100 );
const [variant, setVariant] = React.useState( 100 );
const setUpModalMini = (type:number, num:number) => {
setType( type );
setVariant( num );
handleOpenModalMini();
}
const handleOpenModalMini = () => { setOpenModalMini(true); };
const handleCloseModalMini = () => { setOpenModalMini(false); };
const getData = localStorage.getItem("tariffs");
const store = useStore( (state) => state );
const [type, setType] = React.useState(100);
const [variant, setVariant] = React.useState(100);
const setUpModalMini = (type: number, num: number) => {
setType(type);
setVariant(num);
handleOpenModalMini();
};
if( getData && !store.tariffsArray.length ) {
const rows:Array<ArrayProps> = JSON.parse(getData);
if( rows.length ) { store.tariffsArraySet( rows ); };
}
const tariffsArray = useTariffStore(state => state.tariffs);
const tariffsArraySet = useTariffStore(state => state.setTariffs);
const tariffsSelectedRowsData = useTariffStore(state => state.tariffsSelectedRowsData);
const [openModalPackage, setOpenModalPackage] = React.useState(false);
const handleOpenModalPackage = () => { setOpenModalPackage(true); };
const handleCloseModalPackage = () => { setOpenModalPackage(false); };
const [openModalPackage, setOpenModalPackage] = React.useState(false);
const newPackage = ( name:string ) => {
const tariffs:Array<Tariff> = [];
const handleOpenModalPackage = () => { setOpenModalPackage(true); };
const handleCloseModalPackage = () => { setOpenModalPackage(false); };
store.tariffsSelectedRowsData.forEach( (item) => {
if( item.type === "package" && item.tariffs ) {
tariffs.push( ...item.tariffs );
} else {
tariffs.push( item );
}
} );
const newPackage = (name: string) => {
const tariffs: Array<Tariff> = [];
const uniqueArray:Array<Tariff> = [];
tariffs.forEach( (tariff) => {
if( uniqueArray.findIndex( (a) => a.id === tariff.id ) < 0 ) {
uniqueArray.push( tariff );
}
} );
tariffsSelectedRowsData.forEach((item) => {
if (item.type === "package" && item.tariffs) {
tariffs.push(...item.tariffs);
} else {
tariffs.push(item);
}
});
const packageCreated:ArrayProps = {
name,
id: new Date().getTime(),
type: "package",
tariffs: uniqueArray,
service: "",
disk: 0,
time: 0,
points: 0,
price: 0
}
const uniqueArray: Array<Tariff> = [];
tariffs.forEach((tariff) => {
if (uniqueArray.findIndex((a) => a.id === tariff.id) < 0) {
uniqueArray.push(tariff);
}
});
store.tariffsArraySet( [...store.tariffsArray, packageCreated] );
handleCloseModalPackage();
}
const packageCreated: ArrayProps = {
name,
id: new Date().getTime(),
type: "package",
tariffs: uniqueArray,
service: "",
disk: 0,
time: 0,
points: 0,
price: 0
};
return (
<React.Fragment>
<Box sx={{
width: "90%",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center"
}}>
<Templater openModal={ setUpModalMini } />
<Quiz openModal={ setUpModalMini } />
<Contractor openModal={ setUpModalMini } />
<DataGridElement openModal={ handleOpenModalPackage } />
</Box>
tariffsArraySet([...tariffsArray, packageCreated]);
handleCloseModalPackage();
};
<ModalMini open={ openModalMini } type={ type } variant={ variant } close={ handleCloseModalMini } />
<ModalPackage open={ openModalPackage } newPackage={ newPackage } close={ handleCloseModalPackage } />
</React.Fragment>
);
}
return (
<>
<Box sx={{
width: "90%",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center"
}}>
<CustomHeader>Шаблонизатор документов</CustomHeader>
<ButtonContainer>
<CustomButton onClick={() => setUpModalMini(1, 1)}>Создать тариф на время</CustomButton>
<CustomButton onClick={() => setUpModalMini(1, 0)}>Создать тариф на объем</CustomButton>
<CustomButton onClick={() => setUpModalMini(1, 2)}>Создать тариф на гигабайты</CustomButton>
<CustomButton>Изменить тариф</CustomButton>
</ButtonContainer>
<CustomHeader>Опросник</CustomHeader>
<ButtonContainer>
<CustomButton sx={{ padding: '11px 43px' }} onClick={() => setUpModalMini(2, 1)}>Создать тариф на время</CustomButton>
<CustomButton sx={{ padding: '11px 43px' }} onClick={() => setUpModalMini(2, 0)}>Создать тариф на объем</CustomButton>
<CustomButton sx={{ padding: '11px 43px' }}>Изменить тариф </CustomButton>
</ButtonContainer>
<CustomHeader>Сокращатель ссылок</CustomHeader>
<ButtonContainer>
<CustomButton onClick={() => setUpModalMini(3, 1)} sx={{ padding: '11px 65px' }}>
Создать тариф <br /> на аналитику время
</CustomButton>
<CustomButton onClick={() => setUpModalMini(3, 1)} sx={{ padding: '11px 65px' }}>
Создать тариф <br /> на a/b тесты время
</CustomButton>
<CustomButton sx={{ padding: '11px 65px' }}>
Изменить тариф
</CustomButton>
</ButtonContainer>
<DataGridElement openModal={handleOpenModalPackage} />
</Box>
<ModalMini open={openModalMini} type={type} variant={variant} close={handleCloseModalMini} />
<ModalPackage open={openModalPackage} newPackage={newPackage} close={handleCloseModalPackage} />
</>
);
};
export default Tariffs;

@ -1,28 +0,0 @@
export interface Tariff {
id: number
name: string
type: string
service: string
disk: number
time: number
points: number
price: number
}
export interface ArrayProps {
id: number
name: string
type: string
service: string
disk: number
time: number
points: number
price: number
tariffs?: Array<Tariff>
}
export interface CartSummary {
mbs: number
points: number
days: number
}

@ -0,0 +1,255 @@
import { CartSummary, Discount, Promocode } from "../../../../model/cart";
import { ArrayProps, Tariff } from "../../../../model/tariff";
export function calcFitDiscounts(discountsArray: Discount[], discountsActiveArray: number[], cartSummary: { [key: string]: CartSummary; }, fieldAddedValue: string) {
const result = discountsActiveArray.filter(e => {
const discount = discountsArray[e];
const summary = cartSummary[discount.privileges[0].good];
return (discount.incomeMore * 100 < parseInt(fieldAddedValue) && discount.incomeMore > 0) ||
(discount.toTime < (summary ? summary.days : 0) && discount.toTime > 0 && discount.toCapacity === 0) ||
(discount.toCapacity > 0 && discount.toCapacity < (summary ? summary.points : 0) && discount.toTime === 0) ||
(discount.toCapacity > 0 && discount.toTime > 0 && discount.toCapacity < (summary ? summary.points : 0) && discount.toTime < (summary ? summary.days : 0)) ||
(!discount.toCapacity && !discount.toTime && !discount.incomeMore && !discount.basketMore) ||
discount.basketMore;
}).filter((e, i, a) => {
const discount: Discount = discountsArray[e];
if (discount.incomeMore) {
return discount.incomeMore === a.reduce((a, e) => Math.max(a, discountsArray[e].incomeMore || 0), 0);
}
if (discount.toTime && discount.toCapacity) {
return discount.toTime === a.reduce((a, e) => Math.max(a, (discountsArray[e].toTime && discountsArray[e].toCapacity) ? discountsArray[e].toTime : 0), 0) && discount.toCapacity === a.reduce((a, e) => Math.max(a, (discountsArray[e].toCapacity && discountsArray[e].toTime) ? discountsArray[e].toCapacity : 0), 0);
}
if (discount.toTime && !discount.toCapacity) {
return discount.toTime === a.reduce((a, e) => Math.max(a, discountsArray[e].toTime && !discountsArray[e].toCapacity ? discountsArray[e].toTime : 0), 0);
}
if (!discount.toTime && discount.toCapacity) {
return discount.toCapacity === a.reduce((a, e) => Math.max(a, discountsArray[e].toCapacity && !discountsArray[e].toTime ? discountsArray[e].toCapacity : 0), 0);
}
return true;
});
return result;
}
export function separator(amount: number) {
if (String(amount).length < 4) { return amount; }
let result: Array<string> = [];
const arrs = String(amount).split('.');
const arr = arrs[0].split('').reverse();
arr.forEach((item, i: number) => {
result.push(String(arr[i]));
if (((i + 1) / 3) - Math.round((i + 1) / 3) === 0) result.push(" ");
});
if (arrs.length > 1) { return result.reverse().join("") + "." + arrs[1]; }
else { return result.reverse().join(""); }
};
export function formatPromocodePriveleges(promocode: Promocode) {
return promocode.privileges.map(privelege => `${privelege.good} - ${Math.round(privelege.discount * 100)}%`).join(", ");
}
export function calcTotalAndRowData(
cartRowsData: ArrayProps[],
isNonCommercial: boolean,
discountsArray: Discount[],
discountsActiveArray: number[],
fitDiscounts: number[],
addedValueField: string,
cartSummary: { [key: string]: CartSummary; },
promocode?: Promocode,
) {
let totalPrice = 0;
const calculatedCartRowData = cartRowsData.map(cartRow => {
let price = cartRow.price;
const appliedDiscounts: number[] = [];
if (!isNonCommercial) {
let percents = 0;
if (cartRow.type === "package") {
// считаем цену в ПАКЕТАХ
price = 0;
cartRow.tariffs?.forEach((tariff) => {
let tariffPrice = tariff.price;
percents = 0;
// применяем скидки по промокоду
if (promocode) {
promocode.privileges.forEach(privilege => {
if (tariff.service === privilege.good) {
percents = percents + privilege.discount;
}
});
} else {// применяем активные скидки
percents = applyActiveDiscounts(
percents,
tariff,
discountsArray,
discountsActiveArray,
addedValueField,
);
}
// применяем активные скидки по времени объему
if (!promocode) {
discountsActiveArray.forEach(activeDiscount => {
discountsArray.forEach((discount, i) => {
if (i === activeDiscount) {
if (tariff.time) {
const dTime = 0.1;
percents = percents + dTime;
}
if (tariff.points) {
//const cTime = discountCapacity( tariff.points );
//percents = percents + cTime;
//if( discounts ) discounts += " × ";
//if( cTime != 0 ) discounts += `${ Math.round(cTime * 100) }%`;
}
}
});
});
}
// применяем активные скидки на продукт
if (!promocode) {
discountsActiveArray.forEach(activeDiscount => {
discountsArray.forEach((discount, i) => {
if (i === activeDiscount) {
if (tariff.time && tariff.points) {
// const dProduct = discountProduct( tariff.time, tariff.points );
//percents = percents + dProduct;
//if( discounts ) discounts += " × ";
//if( dProduct != 0 ) discounts += `${ Math.round(dProduct * 100) }%`;
}
}
});
});
}
tariffPrice = tariffPrice - (tariffPrice * percents);
price += tariffPrice;
});
} else {
// считаем цену в ТАРИФАХ
price = cartRow.price;
percents = 0;
// применяем скидки по промокоду
if (promocode) {
promocode.privileges.forEach(privilege => {
if (cartRow.service === privilege.good) {
appliedDiscounts.push(privilege.discount);
price *= (1 - privilege.discount);
}
});
} else {
// применяем активные скидки
fitDiscounts.forEach(activeDiscount => {
const discount = discountsArray[activeDiscount];
discount.privileges.forEach((p) => {
const svcName = cartRow.service;
if (p.good === svcName) {
const summary = cartSummary[svcName] || { mbs: 0, points: 0, days: 0 };
if (
(discount.toCapacity === 0 && discount.toTime === 0 && discount.basketMore === 0 && !(discount.incomeMore)) ||
(discount.toCapacity > 0 && summary.points > discount.toCapacity && cartRow.points > 0 && discount.toTime === 0) ||
(discount.toTime > 0 && summary.days > discount.toTime * 100 && cartRow.time > 0 && discount.toCapacity === 0) ||
(discount.toTime > 0 && discount.toCapacity > 0 && summary.days > discount.toTime * 100 && summary.points > discount.toCapacity)
) {
price *= (1 - p.discount);
appliedDiscounts.push(p.discount);
}
}
});
});
}
percents = Number(percents.toFixed(2));
price = price - (price * percents);
}
}
totalPrice += price;
return {
...cartRow,
price,
appliedDiscounts,
};
});
return {
totalPrice,
calculatedCartRowData
};
}
function applyActiveDiscounts(
percents: number,
tariff: Tariff,
discountsArray: Discount[],
discountsActiveArray: number[],
addedValueField: string,
) {
discountsActiveArray.forEach(activeDiscountIndex => {
discountsArray[activeDiscountIndex].privileges.forEach((privilege) => {
if (privilege.discount !== 0) {
if (addedValueField) { // внесено
const addedValue = Number(addedValueField);
let minDiscount = 100;
let minI = -1;
discountsArray.forEach((discount, index) => {
discount.privileges.forEach((y) => {
if (
discount.active &&
addedValue - y.discount * 100 < minDiscount &&
addedValue - y.discount * 100 > 0
) {
minDiscount = addedValue - y.discount * 100;
minI = index;
}
});
});
if (minI >= 0) {
discountsArray[minI].privileges.forEach((y) => {
percents = percents + y.discount / discountsActiveArray.length; // костыль
});
}
} else { // не внесено
if (tariff.service === privilege.good) {
percents = percents + privilege.discount;
}
}
}
});
});
return percents;
}
export function convertTariffs(tariffsArray: ArrayProps[]) {
return tariffsArray.map((item) => {
if (item.type === "package" && item.tariffs) {
const result = item.tariffs.reduce((acc, tariff) => {
acc.service = acc.service ? `${acc.service}, ${tariff.service}` : tariff.service;
acc.disk = acc.disk + tariff.disk;
acc.time = acc.time + tariff.time;
acc.points = acc.points + tariff.points;
acc.price = acc.price + tariff.price;
return acc;
}, { service: "", disk: "", time: "", points: "", price: 0 });
return { id: item.id, name: item.name, type: item.type, ...result };
} else {
return item;
}
});
}

@ -4,7 +4,7 @@ import Users from "./Users";
import Entities from "./Entities";
import Tariffs from "./Tariffs";
import Discounts from "./Discounts";
import Promocode from "./Promocode";
import Promocodes from "./Promocode";
import Support from "./Support";
@ -22,7 +22,7 @@ const Content: React.FC<MWProps> = ({ section }) => {
<Entities />,
<Tariffs />,
<Discounts />,
<Promocode />,
<Promocodes />,
<Error404 />,
<Error404 />,
<Support />
@ -32,13 +32,13 @@ const Content: React.FC<MWProps> = ({ section }) => {
<React.Fragment>
<Box sx={{
width: "100%",
height: "calc(100vh - 85px)",
height: "100vh",
display: "flex",
flexDirection: "column",
alignItems: "center",
overflow: "auto",
overflowY: "auto",
padding: "60px 5px"
padding: "160px 5px"
}}>
{ componentsArray[ section ] }

@ -12,8 +12,6 @@ const Header: React.FC = () => {
backgroundColor: theme.palette.menu.main,
width: "100%",
minHeight: "85px",
borderBottom: "1px solid",
borderColor: theme.palette.grayDark.main,
display: "flex",
justifyContent: "space-between",
flexWrap: 'wrap',

@ -1,7 +1,5 @@
import * as React from "react";
import { Box } from "@mui/material";
import { useNavigate, useLocation } from "react-router-dom";
import MenuOutlinedIcon from '@mui/icons-material/MenuOutlined';
import PersonOutlineOutlinedIcon from '@mui/icons-material/PersonOutlineOutlined';
import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined';
import BathtubOutlinedIcon from '@mui/icons-material/BathtubOutlined';
@ -11,174 +9,234 @@ import SettingsIcon from '@mui/icons-material/Settings';
import CameraIcon from '@mui/icons-material/Camera';
import HeadsetMicOutlinedIcon from '@mui/icons-material/HeadsetMicOutlined';
import theme from "../../../theme";
import CssBaseline from "@mui/material/CssBaseline";
import Toolbar from "@mui/material/Toolbar";
import IconButton from "@mui/material/IconButton";
import MenuIcon from "@mui/icons-material/Menu";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
import Divider from "@mui/material/Divider";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import {CSSObject, styled, Theme, useTheme} from "@mui/material/styles";
import MuiDrawer from '@mui/material/Drawer';
import MuiAppBar, { AppBarProps as MuiAppBarProps } from '@mui/material/AppBar';
import Header from "../Header/index";
import {Link} from 'react-router-dom';
import useMediaQuery from '@mui/material/useMediaQuery';
import Paper from "@mui/material/Paper";
const drawerWidth = 240;
const openedMixin = (theme: Theme): CSSObject => ({
width: drawerWidth,
background: '#2f3339',
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
overflowX: 'hidden',
});
const closedMixin = (theme: Theme): CSSObject => ({
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
overflowX: 'hidden',
width: `calc(${theme.spacing(7)} + 1px)`,
background: '#2f3339',
// marginTop: '20px',
[theme.breakpoints.up('sm')]: {
width: `calc(${theme.spacing(8)} + 1px)`,
},
});
const DrawerHeader = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: theme.spacing(0, 1),
...theme.mixins.toolbar,
}));
interface AppBarProps extends MuiAppBarProps {
open?: boolean;
}
const AppBar = styled(MuiAppBar, {
shouldForwardProp: (prop) => prop !== 'open',
})<AppBarProps>(({ theme, open }) => ({
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
...(open && {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
}),
}));
const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })(
({ theme, open }) => ({
width: drawerWidth,
flexShrink: 0,
boxSizing: 'border-box',
...(open && {
...openedMixin(theme),
'& .MuiDrawer-paper': openedMixin(theme),
}),
...(!open && {
...closedMixin(theme),
'& .MuiDrawer-paper': closedMixin(theme),
}),
}),
);
const links: {path: string; element: JSX.Element; title: string} [] =[
{path: '/users', element: <PersonOutlineOutlinedIcon/>, title: 'Информация о проекте'},
{path: '/entities', element: <SettingsOutlinedIcon/>, title: 'Юридические лица'},
{path: '/tariffs', element: <BathtubOutlinedIcon/>, title: 'Шаблонизатор документов'},
{path: '/discounts', element: <AddPhotoAlternateOutlinedIcon/>, title: 'Скидки'},
{path: '/promocode', element: <NaturePeopleOutlinedIcon/>, title: 'Промокод'},
{path: '/kkk', element: <SettingsIcon/>, title: 'Настройки'},
{path: '/jjj', element: <CameraIcon/>, title: 'Камера' },
{path: '/support', element: <HeadsetMicOutlinedIcon/>, title: 'Служба поддержки'},
]
const Navigation = (props:any) => {
return (
<List
sx={{
background: '#2f3339'
}}
>
{links.map((e, i) => (
<ListItem key={i} disablePadding sx={{ display: 'block' }}>
<Link to={e.path} style={{textDecoration: 'none'}}>
<ListItemButton onClick={props.SladeMobileHC}
sx={{
minHeight: 48,
height: '50px',
justifyContent: props.visible ? 'initial' : 'center',
px: 2.5,
margin: '20px 0'
}}
>
<ListItemIcon
sx={{
minWidth: 0,
mr: props.visible ? 3 : 'auto',
justifyContent: 'center',
color: '#eaba5b',
transform: 'scale(1.2)'
}}
>
{e.element}
</ListItemIcon>
<ListItemText primary={e.title}
sx={{
opacity: props.visible ? 1 : 0,
color: '#eaba5b',
}} />
</ListItemButton>
</Link>
</ListItem>
))}
</List>
)
}
const Menu: React.FC = () => {
const navigate = useNavigate();
const pages = [ "null", "users", "entities", "tariffs", "discounts", "promocode", "null", "null", "support" ];
const colors = [
"null",
theme.palette.golden.main,
theme.palette.golden.main,
theme.palette.golden.main,
theme.palette.golden.main,
theme.palette.golden.main,
theme.palette.golden.main,
theme.palette.golden.main,
theme.palette.golden.main ];
const tablet = useMediaQuery('(max-width:600px)');
const location = useLocation();
pages.forEach( (item, i) => {
if( location.pathname == `/${pages[i]}` ) colors[ i ] = theme.palette.secondary.main
} );
const mobile = useMediaQuery('(max-width:340px)');
const changePage = (page:number) => { navigate(`/${pages[ page ]}`); }
const theme = useTheme();
const [open, setOpen] = React.useState(tablet? false : true);
const handleDrawerOpen = () => {
if (!mobile) {setOpen(true)}
else {SladeMobileHC()}
};
const handleDrawerClose = () => {
setOpen(false);
};
const [sladeMobile, setSladeMobile] = React.useState(false)
const SladeMobileHC = () => {
if (mobile) {setSladeMobile(old=>!old)}
};
return (
<React.Fragment>
<Box sx={{
backgroundColor: theme.palette.menu.main,
width: "96px",
height: "100vh",
borderRight: "1px solid #45494c",
overflow: "auto"
}}>
<Box sx={{
width: "100%",
height: "85px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
cursor: "pointer"
}}>
<MenuOutlinedIcon sx={{
transform: "scale(1.4)"
}} />
</Box>
<Box sx={{
height: "calc( 100vh - 85px )",
overflowY: "auto",
'&::-webkit-scrollbar': {
display: "none"
}
}}>
<Box sx={{
width: "100%",
height: "85px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
cursor: "pointer"
}} onClick={ () => changePage( 1 ) }>
<PersonOutlineOutlinedIcon sx={{
color: colors[ 1 ],
transform: "scale(1.2)"
}} />
</Box>
<Box sx={{
width: "100%",
height: "85px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
cursor: "pointer"
}} onClick={ () => changePage( 2 ) }>
<SettingsOutlinedIcon sx={{
color: colors[ 2 ],
transform: "scale(1.2)"
}} />
</Box>
<Box sx={{
width: "100%",
height: "85px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
cursor: "pointer"
}} onClick={ () => changePage( 3 ) }>
<BathtubOutlinedIcon sx={{
color: colors[ 3 ],
transform: "scale(1.2)"
}} />
</Box>
<Box sx={{
width: "100%",
height: "85px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
cursor: "pointer"
}} onClick={ () => changePage( 4 ) }>
<AddPhotoAlternateOutlinedIcon sx={{
color: colors[ 4 ],
transform: "scale(1.2)"
}} />
</Box>
<Box sx={{
width: "100%",
height: "85px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
cursor: "pointer"
}} onClick={ () => changePage( 5 ) }>
<NaturePeopleOutlinedIcon sx={{
color: colors[ 5 ],
transform: "scale(1.2)"
}} />
</Box>
<Box sx={{
width: "100%",
height: "85px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
cursor: "pointer"
}} onClick={ () => changePage( 6 ) }>
<SettingsIcon sx={{
color: colors[ 6 ],
transform: "scale(1.2)"
}} />
</Box>
<Box sx={{
width: "100%",
height: "85px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
cursor: "pointer"
}} onClick={ () => changePage( 7 ) }>
<CameraIcon sx={{
color: colors[ 7 ],
transform: "scale(1.2)"
}} />
</Box>
<Box sx={{
width: "100%",
height: "85px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
cursor: "pointer"
}} onClick={ () => changePage( 8 ) }>
<HeadsetMicOutlinedIcon sx={{
color: colors[ 8 ],
transform: "scale(1.2)"
}} />
</Box>
</Box>
<Box sx={{ display: 'flex' }}>
<CssBaseline />
<AppBar open={open}>
<Toolbar
sx={{
background: '#2f3339',
borderBottom: '1px solid',
borderColor: '#45494c',
}}
>
<IconButton
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
sx={{
marginRight: 5,
...(open && { display: 'none' }),
color: "#eaba5b",
background: '#2f3339'
}}
>
<MenuIcon />
</IconButton>
<Header />
</Toolbar>
</AppBar>
{!mobile ? <Drawer variant="permanent" open={open}>
<DrawerHeader style={{minHeight: '86px',}}>
<IconButton onClick={handleDrawerClose} sx={{color: "#eaba5b"}}>
<ChevronLeftIcon />
</IconButton>
</DrawerHeader>
<Divider />
<Navigation visible={open}/>
</Drawer> : null}
</Box>
{sladeMobile ? <Paper
sx={{
position: 'absolute',
width: '100%',
background: '#2f3339',
zIndex: theme.zIndex.drawer + 3
}}
>
<Box
sx={{display: 'flex',
justifyContent: 'end',
padding: '10px'
}}
>
<IconButton onClick={SladeMobileHC} sx={{color: "#eaba5b"}}>
<ChevronLeftIcon />
</IconButton>
</Box>
<Divider />
<Navigation visible={true} SladeMobileHC={SladeMobileHC}/></Paper> : null}
</React.Fragment>
);
}

@ -41,7 +41,7 @@ const LoggedIn: React.FC<MWProps> = ({ section }) => {
justifyContent: "space-between",
alignItems: "center"
}}>
<Header />
{/*<Header />*/}
<Content section={ section } />
</Box>
</Box>

@ -1,63 +0,0 @@
import create from "zustand";
import { persist } from "zustand/middleware"
import { ArrayProps } from "./pages/dashboard/Content/Tariffs/types";
import { PromocodeProps } from "./pages/dashboard/Content/Promocode/types";
import { DiscountProps } from "./pages/dashboard/Content/Discounts/types";
const useStore = create<StoreState>()(
persist(
(set, get) => ({
tariffsArray: [],
tariffsArraySet: (array:Array<ArrayProps>) => set({ tariffsArray: array }),
tariffsSelectedRowsData: [],
tariffsSelectedRowsDataSet: (array:Array<ArrayProps>) => set({ tariffsSelectedRowsData: array }),
cartRowsData: [],
cartRowsDataSet: (array:Array<ArrayProps>) => set({ cartRowsData: array }),
promocodeArray: [],
promocodeArraySet: (array:Array<PromocodeProps>) => set({ promocodeArray: array }),
discountsArray: [],
discountsArraySet: (array:Array<DiscountProps>) => set({ discountsArray: array }),
discountsActiveArray: [],
discountsActiveArraySet: (array:Array<number>) => set({ discountsActiveArray: array }),
discountsSelectedRowsData: [],
discountsSelectedRowsDataSet: (array:Array<DiscountProps>) => set({ discountsSelectedRowsData: array }),
}),
{
name: "arrays-storage",
getStorage: () => localStorage,
}
)
);
export interface StoreState {
tariffsArray: Array<ArrayProps>,
tariffsArraySet: (array:Array<ArrayProps>) => void,
tariffsSelectedRowsData: Array<ArrayProps>,
tariffsSelectedRowsDataSet: (array:Array<ArrayProps>) => void,
cartRowsData: Array<ArrayProps>,
cartRowsDataSet: (array:Array<ArrayProps>) => void,
promocodeArray: Array<PromocodeProps>,
promocodeArraySet: (array:Array<PromocodeProps>) => void,
discountsArray: Array<DiscountProps>,
discountsArraySet: (array:Array<DiscountProps>) => void,
discountsActiveArray: Array<number>,
discountsActiveArraySet: (array:Array<number>) => void,
discountsSelectedRowsData: Array<DiscountProps>,
discountsSelectedRowsDataSet: (array:Array<DiscountProps>) => void,
}
export default useStore;

27
src/stores/cart.ts Normal file

@ -0,0 +1,27 @@
import create from "zustand";
import { devtools, persist } from "zustand/middleware";
import { ArrayProps } from "../model/tariff";
interface CartStore {
cartRowsData: Array<ArrayProps>,
setCartRowsData: (array: Array<ArrayProps>) => void,
}
export const useCartStore = create<CartStore>()(
devtools(
persist(
(set, get) => ({
cartRowsData: [],
setCartRowsData: (array: Array<ArrayProps>) => set({ cartRowsData: array }),
}),
{
name: "cart-storage",
getStorage: () => localStorage,
}
),
{
name: "Cart store"
}
)
);

36
src/stores/discounts.ts Normal file

@ -0,0 +1,36 @@
import create from "zustand";
import { devtools, persist } from "zustand/middleware";
import { Discount } from "../model/cart";
import { testDiscounts } from "./mocks/discounts";
interface DiscountStore {
discountsArray: Array<Discount>,
setDiscountsArray: (array: Array<Discount>) => void,
discountsActiveArray: Array<number>,
setDiscountsActiveArray: (array: Array<number>) => void,
discountsSelectedRowsData: Array<Discount>,
setDiscountsSelectedRowsData: (array: Array<Discount>) => void,
}
export const useDiscountStore = create<DiscountStore>()(
devtools(
persist(
(set, get) => ({
discountsArray: testDiscounts,
setDiscountsArray: (array: Array<Discount>) => set({ discountsArray: array }),
discountsActiveArray: [],
setDiscountsActiveArray: (array: Array<number>) => set({ discountsActiveArray: array }),
discountsSelectedRowsData: [],
setDiscountsSelectedRowsData: (array: Array<Discount>) => set({ discountsSelectedRowsData: array }),
}),
{
name: "discount-storage",
getStorage: () => localStorage,
}
),
{
name: "Discount store"
}
)
);

@ -0,0 +1,52 @@
import { Discount } from "../../model/cart";
export const testDiscounts: Discount[] = [
{
id: 1,
name: "Скидка 1",
endless: false,
from: "",
dueTo: "",
privileges: [
{ good: "Опросник", discount: 0.3 },
{ good: "Опросник", discount: 0.2 },
],
active: false,
incomeMore: 1,
basketMore: 10,
toTime: 20,
toCapacity: 30,
},
{
id: 2,
name: "Скидка 2",
endless: false,
from: "",
dueTo: "",
privileges: [
{ good: "Опросник", discount: 0.3 },
{ good: "Опросник", discount: 0.2 },
],
active: true,
incomeMore: 1,
basketMore: 10,
toTime: 20,
toCapacity: 30,
},
{
id: 3,
name: "Скидка 3",
endless: false,
from: "",
dueTo: "",
privileges: [
{ good: "Опросник", discount: 0.3 },
{ good: "Опросник", discount: 0.2 },
],
active: false,
incomeMore: 1,
basketMore: 10,
toTime: 20,
toCapacity: 30,
},
];

@ -0,0 +1,17 @@
import { Promocode } from "../../model/cart";
export const testPromocodes: Promocode[] = [
{
id: 1, name: "Промокод 1", endless: false, from: "", dueTo: "", privileges: [
{ good: "Шаблонизатор документов", discount: 0.15 },
{ good: "Опросник", discount: 0.3 }
]
},
{
id: 1, name: "Промокод 2", endless: false, from: "", dueTo: "", privileges: [
{ good: "Шаблонизатор документов", discount: 0.4 },
{ good: "Опросник", discount: 0.6 }
]
}
];

28
src/stores/promocodes.ts Normal file

@ -0,0 +1,28 @@
import create from "zustand";
import { devtools, persist } from "zustand/middleware";
import { Promocode } from "../model/cart";
import { testPromocodes } from "./mocks/promocodes";
interface PromocodeStore {
promocodeArray: Array<Promocode>,
setPromocodeArray: (array: Array<Promocode>) => void,
}
export const usePromocodeStore = create<PromocodeStore>()(
devtools(
persist(
(set, get) => ({
promocodeArray: testPromocodes,
setPromocodeArray: (array: Array<Promocode>) => set({ promocodeArray: array }),
}),
{
name: "promocode-storage",
getStorage: () => localStorage,
}
),
{
name: "Promocode store"
}
)
);

31
src/stores/tariffs.ts Normal file

@ -0,0 +1,31 @@
import create from "zustand";
import { devtools, persist } from "zustand/middleware";
import { ArrayProps } from "../model/tariff";
interface TariffStore {
tariffs: Array<ArrayProps>;
setTariffs: (array: Array<ArrayProps>) => void;
tariffsSelectedRowsData: Array<ArrayProps>;
setTariffsSelectedRowsData: (array: Array<ArrayProps>) => void;
}
export const useTariffStore = create<TariffStore>()(
devtools(
persist(
(set, get) => ({
tariffs: [],
setTariffs: (array: Array<ArrayProps>) => set({ tariffs: array }),
tariffsSelectedRowsData: [],
setTariffsSelectedRowsData: (array: Array<ArrayProps>) => set({ tariffsSelectedRowsData: array }),
}),
{
name: "tariff-storage",
getStorage: () => localStorage,
}
),
{
name: "Tariff store"
}
)
);

10483
yarn.lock Normal file

File diff suppressed because it is too large Load Diff