feat: eslint and format code
This commit is contained in:
parent
b0e1b4a4a6
commit
16901608af
@ -24,7 +24,6 @@ deploy-to-staging:
|
|||||||
- if: "$CI_COMMIT_BRANCH == $STAGING_BRANCH"
|
- if: "$CI_COMMIT_BRANCH == $STAGING_BRANCH"
|
||||||
extends: .deploy_template
|
extends: .deploy_template
|
||||||
|
|
||||||
|
|
||||||
deploy-to-prod:
|
deploy-to-prod:
|
||||||
tags:
|
tags:
|
||||||
- front
|
- front
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
presets: [
|
presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-typescript"],
|
||||||
["@babel/preset-env", { targets: { node: "current" } }],
|
|
||||||
"@babel/preset-typescript",
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
@ -10,8 +10,8 @@ module.exports = {
|
|||||||
// plugin does not take it from tsconfig
|
// plugin does not take it from tsconfig
|
||||||
baseUrl: "./src",
|
baseUrl: "./src",
|
||||||
// tsConfigPath should point to the file where "baseUrl" and "paths" are specified
|
// tsConfigPath should point to the file where "baseUrl" and "paths" are specified
|
||||||
tsConfigPath: "./tsconfig.extend.json"
|
tsConfigPath: "./tsconfig.extend.json",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
};
|
};
|
9
eslint.config.js
Normal file
9
eslint.config.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import eslint from "@eslint/js";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended, {
|
||||||
|
rules: {
|
||||||
|
semi: "error",
|
||||||
|
"prefer-const": "error",
|
||||||
|
},
|
||||||
|
});
|
15
package.json
15
package.json
@ -2,6 +2,7 @@
|
|||||||
"name": "adminka",
|
"name": "adminka",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@date-io/dayjs": "^2.15.0",
|
"@date-io/dayjs": "^2.15.0",
|
||||||
"@emotion/react": "^11.10.4",
|
"@emotion/react": "^11.10.4",
|
||||||
@ -57,13 +58,8 @@
|
|||||||
"test:cypress": "start-server-and-test start http://localhost:3000 cypress",
|
"test:cypress": "start-server-and-test start http://localhost:3000 cypress",
|
||||||
"cypress": "cypress open",
|
"cypress": "cypress open",
|
||||||
"eject": "craco eject",
|
"eject": "craco eject",
|
||||||
"format": "prettier . --write"
|
"format": "prettier . --write",
|
||||||
},
|
"lint": "eslint ./src --fix"
|
||||||
"eslintConfig": {
|
|
||||||
"extends": [
|
|
||||||
"react-app",
|
|
||||||
"react-app/jest"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
@ -78,7 +74,10 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.3.0",
|
||||||
"craco-alias": "^3.0.1",
|
"craco-alias": "^3.0.1",
|
||||||
"prettier": "^3.2.5"
|
"eslint": "^9.3.0",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"typescript-eslint": "^7.10.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "GilroyRegular";
|
font-family: "GilroyRegular";
|
||||||
src: local("GilroyRegular"), url(fonts/GilroyRegular.woff) format("woff");
|
src:
|
||||||
|
local("GilroyRegular"),
|
||||||
|
url(fonts/GilroyRegular.woff) format("woff");
|
||||||
}
|
}
|
@ -1,14 +1,11 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta name="description" content="Web site created using create-react-app" />
|
||||||
name="description"
|
|
||||||
content="Web site created using create-react-app"
|
|
||||||
/>
|
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<link rel="stylesheet" href="fonts.css" />
|
<link rel="stylesheet" href="fonts.css" />
|
||||||
<!--
|
<!--
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
const puppeteer = require('puppeteer');
|
const puppeteer = require("puppeteer");
|
||||||
const url = "http://localhost:3000/users";
|
const url = "http://localhost:3000/users";
|
||||||
const urlMass = ['/users','/tariffs','/discounts','/promocode','/support', '/entities'];
|
const urlMass = ["/users", "/tariffs", "/discounts", "/promocode", "/support", "/entities"];
|
||||||
|
|
||||||
jest.setTimeout(1000 * 60 * 5);
|
jest.setTimeout(1000 * 60 * 5);
|
||||||
|
|
||||||
let browser;
|
let browser;
|
||||||
let page;
|
let page;
|
||||||
|
|
||||||
describe('Тест', (() => {
|
describe("Тест", () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
browser = puppeteer.launch({ headless: true });
|
browser = puppeteer.launch({ headless: true });
|
||||||
page = browser.newPage();
|
page = browser.newPage();
|
||||||
@ -15,30 +15,21 @@
|
|||||||
page.goto(url);
|
page.goto(url);
|
||||||
// Set screen size
|
// Set screen size
|
||||||
page.setViewport({ width: 1080, height: 1024 });
|
page.setViewport({ width: 1080, height: 1024 });
|
||||||
|
});
|
||||||
})
|
|
||||||
afterAll(() => browser.quit());
|
afterAll(() => browser.quit());
|
||||||
test('Тест меню',async () => {
|
test("Тест меню", async () => {
|
||||||
|
|
||||||
// Ждем загрузки менюшек
|
// Ждем загрузки менюшек
|
||||||
page.waitForSelector('.menu')
|
page.waitForSelector(".menu");
|
||||||
|
|
||||||
// Берем все ссылки с кнопок, у которых есть класс menu и вставляем в массив
|
// Берем все ссылки с кнопок, у которых есть класс menu и вставляем в массив
|
||||||
let menuLink = page.evaluate(()=>{
|
const menuLink = page.evaluate(() => {
|
||||||
let menuArray = document.querySelectorAll('.menu')
|
const menuArray = document.querySelectorAll(".menu");
|
||||||
let Urls = Object.values(menuArray).map(
|
const Urls = Object.values(menuArray).map((menuItem) => menuItem.href.slice(menuItem.href.lastIndexOf("/")));
|
||||||
menuItem => (
|
return Urls;
|
||||||
menuItem.href.slice(menuItem.href.lastIndexOf('/'))
|
});
|
||||||
)
|
|
||||||
)
|
|
||||||
return Urls
|
|
||||||
})
|
|
||||||
// Проверяем, какие ссылки есть в нашем массиве, а каких нет
|
// Проверяем, какие ссылки есть в нашем массиве, а каких нет
|
||||||
for (let i = 0; i < menuLink.length; i++) {
|
for (let i = 0; i < menuLink.length; i++) {
|
||||||
expect(urlMass.find((elem)=>elem===menuLink[i])).toBe(true)
|
expect(urlMass.find((elem) => elem === menuLink[i])).toBe(true);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
})
|
});
|
||||||
}))
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
const puppeteer = require('puppeteer');
|
const puppeteer = require("puppeteer");
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const browser = await puppeteer.launch();
|
const browser = await puppeteer.launch();
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
await page.goto('https://news.ycombinator.com', {
|
await page.goto("https://news.ycombinator.com", {
|
||||||
waitUntil: 'networkidle2',
|
waitUntil: "networkidle2",
|
||||||
});
|
});
|
||||||
await page.pdf({ path: 'hn.pdf', format: 'a4' });
|
await page.pdf({ path: "hn.pdf", format: "a4" });
|
||||||
|
|
||||||
await browser.close();
|
await browser.close();
|
||||||
})();
|
})();
|
@ -30,11 +30,9 @@ export type Account = {
|
|||||||
wallet: Wallet;
|
wallet: Wallet;
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseUrl = process.env.REACT_APP_DOMAIN + "/customer"
|
const baseUrl = process.env.REACT_APP_DOMAIN + "/customer";
|
||||||
|
|
||||||
export const getAccountInfo = async (
|
export const getAccountInfo = async (id: string): Promise<[Account | null, string?]> => {
|
||||||
id: string
|
|
||||||
): Promise<[Account | null, string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const accountInfoResponse = await makeRequest<never, Account>({
|
const accountInfoResponse = await makeRequest<never, Account>({
|
||||||
url: `${baseUrl}/account/${id}`,
|
url: `${baseUrl}/account/${id}`,
|
||||||
|
@ -2,18 +2,11 @@ import makeRequest from "@root/api/makeRequest";
|
|||||||
|
|
||||||
import { parseAxiosError } from "@root/utils/parse-error";
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
|
|
||||||
import type {
|
import type { LoginRequest, RegisterRequest, RegisterResponse } from "@frontend/kitui";
|
||||||
LoginRequest,
|
|
||||||
RegisterRequest,
|
|
||||||
RegisterResponse,
|
|
||||||
} from "@frontend/kitui";
|
|
||||||
|
|
||||||
const baseUrl = process.env.REACT_APP_DOMAIN + "/auth"
|
const baseUrl = process.env.REACT_APP_DOMAIN + "/auth";
|
||||||
|
|
||||||
export const signin = async (
|
export const signin = async (login: string, password: string): Promise<[RegisterResponse | null, string?]> => {
|
||||||
login: string,
|
|
||||||
password: string
|
|
||||||
): Promise<[RegisterResponse | null, string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const signinResponse = await makeRequest<LoginRequest, RegisterResponse>({
|
const signinResponse = await makeRequest<LoginRequest, RegisterResponse>({
|
||||||
url: baseUrl + "/login",
|
url: baseUrl + "/login",
|
||||||
@ -24,7 +17,7 @@ export const signin = async (
|
|||||||
return [signinResponse];
|
return [signinResponse];
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
const [error] = parseAxiosError(nativeError);
|
const [error] = parseAxiosError(nativeError);
|
||||||
console.error(error)
|
console.error(error);
|
||||||
return [null, `Ошибка авторизации. ${error}`];
|
return [null, `Ошибка авторизации. ${error}`];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -35,10 +28,7 @@ export const register = async (
|
|||||||
phoneNumber: string = "--"
|
phoneNumber: string = "--"
|
||||||
): Promise<[RegisterResponse | null, string?]> => {
|
): Promise<[RegisterResponse | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const registerResponse = await makeRequest<
|
const registerResponse = await makeRequest<RegisterRequest, RegisterResponse>({
|
||||||
RegisterRequest,
|
|
||||||
RegisterResponse
|
|
||||||
>({
|
|
||||||
url: baseUrl + "/register",
|
url: baseUrl + "/register",
|
||||||
body: { login, password, phoneNumber },
|
body: { login, password, phoneNumber },
|
||||||
useToken: false,
|
useToken: false,
|
||||||
|
@ -3,15 +3,11 @@ import makeRequest from "@root/api/makeRequest";
|
|||||||
import { parseAxiosError } from "@root/utils/parse-error";
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
|
|
||||||
import type { Discount } from "@frontend/kitui";
|
import type { Discount } from "@frontend/kitui";
|
||||||
import type {
|
import type { CreateDiscountBody, DiscountType, GetDiscountResponse } from "@root/model/discount";
|
||||||
CreateDiscountBody,
|
|
||||||
DiscountType,
|
|
||||||
GetDiscountResponse,
|
|
||||||
} from "@root/model/discount";
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
|
||||||
const baseUrl = process.env.REACT_APP_DOMAIN + "/price"
|
const baseUrl = process.env.REACT_APP_DOMAIN + "/price";
|
||||||
|
|
||||||
interface CreateDiscountParams {
|
interface CreateDiscountParams {
|
||||||
purchasesAmount: number;
|
purchasesAmount: number;
|
||||||
@ -109,10 +105,7 @@ export function createDiscountObject({
|
|||||||
return discount;
|
return discount;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const changeDiscount = async (
|
export const changeDiscount = async (discountId: string, discount: Discount): Promise<[unknown, string?]> => {
|
||||||
discountId: string,
|
|
||||||
discount: Discount
|
|
||||||
): Promise<[unknown, string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const changeDiscountResponse = await makeRequest<Discount, unknown>({
|
const changeDiscountResponse = await makeRequest<Discount, unknown>({
|
||||||
url: baseUrl + "/discount/" + discountId,
|
url: baseUrl + "/discount/" + discountId,
|
||||||
@ -129,16 +122,11 @@ export const changeDiscount = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createDiscount = async (
|
export const createDiscount = async (discountParams: CreateDiscountParams): Promise<[Discount | null, string?]> => {
|
||||||
discountParams: CreateDiscountParams
|
|
||||||
): Promise<[Discount | null, string?]> => {
|
|
||||||
const discount = createDiscountObject(discountParams);
|
const discount = createDiscountObject(discountParams);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const createdDiscountResponse = await makeRequest<
|
const createdDiscountResponse = await makeRequest<CreateDiscountBody, Discount>({
|
||||||
CreateDiscountBody,
|
|
||||||
Discount
|
|
||||||
>({
|
|
||||||
url: baseUrl + "/discount",
|
url: baseUrl + "/discount",
|
||||||
method: "post",
|
method: "post",
|
||||||
useToken: true,
|
useToken: true,
|
||||||
@ -153,9 +141,7 @@ export const createDiscount = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteDiscount = async (
|
export const deleteDiscount = async (discountId: string): Promise<[Discount | null, string?]> => {
|
||||||
discountId: string
|
|
||||||
): Promise<[Discount | null, string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const deleteDiscountResponse = await makeRequest<never, Discount>({
|
const deleteDiscountResponse = await makeRequest<never, Discount>({
|
||||||
url: baseUrl + "/discount/" + discountId,
|
url: baseUrl + "/discount/" + discountId,
|
||||||
@ -178,10 +164,7 @@ export const patchDiscount = async (
|
|||||||
const discount = createDiscountObject(discountParams);
|
const discount = createDiscountObject(discountParams);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const patchDiscountResponse = await makeRequest<
|
const patchDiscountResponse = await makeRequest<CreateDiscountBody, Discount>({
|
||||||
CreateDiscountBody,
|
|
||||||
Discount
|
|
||||||
>({
|
|
||||||
url: baseUrl + "/discount/" + discountId,
|
url: baseUrl + "/discount/" + discountId,
|
||||||
method: "patch",
|
method: "patch",
|
||||||
useToken: true,
|
useToken: true,
|
||||||
@ -196,9 +179,7 @@ export const patchDiscount = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requestDiscounts = async (): Promise<
|
export const requestDiscounts = async (): Promise<[GetDiscountResponse | null, string?]> => {
|
||||||
[GetDiscountResponse | null, string?]
|
|
||||||
> => {
|
|
||||||
try {
|
try {
|
||||||
const discountsResponse = await makeRequest<never, GetDiscountResponse>({
|
const discountsResponse = await makeRequest<never, GetDiscountResponse>({
|
||||||
url: baseUrl + "/discounts",
|
url: baseUrl + "/discounts",
|
||||||
@ -238,7 +219,7 @@ export function useDiscounts() {
|
|||||||
if (!(error instanceof Error)) return;
|
if (!(error instanceof Error)) return;
|
||||||
|
|
||||||
enqueueSnackbar(error.message, { variant: "error" });
|
enqueueSnackbar(error.message, { variant: "error" });
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
@ -25,16 +25,11 @@ type HistoryResponse = {
|
|||||||
|
|
||||||
const baseUrl = process.env.REACT_APP_DOMAIN + "/customer";
|
const baseUrl = process.env.REACT_APP_DOMAIN + "/customer";
|
||||||
|
|
||||||
const getUserHistory = async (
|
const getUserHistory = async (accountId: string, page: number): Promise<[HistoryResponse | null, string?]> => {
|
||||||
accountId: string,
|
|
||||||
page: number
|
|
||||||
): Promise<[HistoryResponse | null, string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const historyResponse = await makeRequest<never, HistoryResponse>({
|
const historyResponse = await makeRequest<never, HistoryResponse>({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url:
|
url: baseUrl + `/history?page=${page}&limit=${100}&accountID=${accountId}&type=payCart`,
|
||||||
baseUrl +
|
|
||||||
`/history?page=${page}&limit=${100}&accountID=${accountId}&type=payCart`,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return [historyResponse];
|
return [historyResponse];
|
||||||
|
@ -10,10 +10,7 @@ export function useHistory(accountId: string) {
|
|||||||
const swrResponse = useSWRInfinite(
|
const swrResponse = useSWRInfinite(
|
||||||
() => `history-${currentPage}`,
|
() => `history-${currentPage}`,
|
||||||
async () => {
|
async () => {
|
||||||
const [historyResponse, error] = await historyApi.getUserHistory(
|
const [historyResponse, error] = await historyApi.getUserHistory(accountId, currentPage);
|
||||||
accountId,
|
|
||||||
currentPage
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
|
@ -3,21 +3,30 @@ import { Method, ResponseType, AxiosError } from "axios";
|
|||||||
import { clearAuthToken } from "@frontend/kitui";
|
import { clearAuthToken } from "@frontend/kitui";
|
||||||
import { redirect } from "react-router-dom";
|
import { redirect } from "react-router-dom";
|
||||||
|
|
||||||
interface MakeRequest { method?: Method | undefined; url: string; body?: unknown; useToken?: boolean | undefined; contentType?: boolean | undefined; responseType?: ResponseType | undefined; signal?: AbortSignal | undefined; withCredentials?: boolean | undefined; }
|
interface MakeRequest {
|
||||||
|
method?: Method | undefined;
|
||||||
|
url: string;
|
||||||
|
body?: unknown;
|
||||||
|
useToken?: boolean | undefined;
|
||||||
|
contentType?: boolean | undefined;
|
||||||
|
responseType?: ResponseType | undefined;
|
||||||
|
signal?: AbortSignal | undefined;
|
||||||
|
withCredentials?: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
async function makeRequest<TRequest = unknown, TResponse = unknown>(data: MakeRequest): Promise<TResponse> {
|
async function makeRequest<TRequest = unknown, TResponse = unknown>(data: MakeRequest): Promise<TResponse> {
|
||||||
try {
|
try {
|
||||||
const response = await KIT.makeRequest<unknown>(data)
|
const response = await KIT.makeRequest<unknown>(data);
|
||||||
|
|
||||||
return response as TResponse
|
return response as TResponse;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = e as AxiosError;
|
const error = e as AxiosError;
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
if (error.response?.status === 400 && error.response?.data?.message === "refreshToken is empty") {
|
if (error.response?.status === 400 && error.response?.data?.message === "refreshToken is empty") {
|
||||||
clearAuthToken()
|
clearAuthToken();
|
||||||
redirect("/");
|
redirect("/");
|
||||||
}
|
}
|
||||||
throw e
|
throw e;
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
export default makeRequest;
|
export default makeRequest;
|
@ -11,7 +11,7 @@ type SeverPrivilegesResponse = {
|
|||||||
squiz: CustomPrivilege[];
|
squiz: CustomPrivilege[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseUrl = process.env.REACT_APP_DOMAIN + "/strator"
|
const baseUrl = process.env.REACT_APP_DOMAIN + "/strator";
|
||||||
|
|
||||||
export const getRoles = async (): Promise<[TMockData | null, string?]> => {
|
export const getRoles = async (): Promise<[TMockData | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
@ -28,14 +28,9 @@ export const getRoles = async (): Promise<[TMockData | null, string?]> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const putPrivilege = async (
|
export const putPrivilege = async (body: Omit<Privilege, "_id" | "updatedAt">): Promise<[unknown, string?]> => {
|
||||||
body: Omit<Privilege, "_id" | "updatedAt">
|
|
||||||
): Promise<[unknown, string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const putedPrivilege = await makeRequest<
|
const putedPrivilege = await makeRequest<Omit<Privilege, "_id" | "updatedAt">, unknown>({
|
||||||
Omit<Privilege, "_id" | "updatedAt">,
|
|
||||||
unknown
|
|
||||||
>({
|
|
||||||
url: baseUrl + "/privilege",
|
url: baseUrl + "/privilege",
|
||||||
method: "put",
|
method: "put",
|
||||||
body,
|
body,
|
||||||
@ -49,14 +44,9 @@ export const putPrivilege = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requestServicePrivileges = async (): Promise<
|
export const requestServicePrivileges = async (): Promise<[SeverPrivilegesResponse | null, string?]> => {
|
||||||
[SeverPrivilegesResponse | null, string?]
|
|
||||||
> => {
|
|
||||||
try {
|
try {
|
||||||
const privilegesResponse = await makeRequest<
|
const privilegesResponse = await makeRequest<never, SeverPrivilegesResponse>({
|
||||||
never,
|
|
||||||
SeverPrivilegesResponse
|
|
||||||
>({
|
|
||||||
url: baseUrl + "/privilege/service",
|
url: baseUrl + "/privilege/service",
|
||||||
method: "get",
|
method: "get",
|
||||||
});
|
});
|
||||||
@ -69,18 +59,14 @@ export const requestServicePrivileges = async (): Promise<
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requestPrivileges = async (
|
export const requestPrivileges = async (signal: AbortSignal | undefined): Promise<[CustomPrivilege[], string?]> => {
|
||||||
signal: AbortSignal | undefined
|
|
||||||
): Promise<[CustomPrivilege[], string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const privilegesResponse = await makeRequest<never, CustomPrivilege[]>(
|
const privilegesResponse = await makeRequest<never, CustomPrivilege[]>({
|
||||||
{
|
|
||||||
url: baseUrl + "/privilege",
|
url: baseUrl + "/privilege",
|
||||||
method: "get",
|
method: "get",
|
||||||
useToken: true,
|
useToken: true,
|
||||||
signal,
|
signal,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return [privilegesResponse];
|
return [privilegesResponse];
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
|
@ -15,10 +15,7 @@ const baseUrl = process.env.REACT_APP_DOMAIN + "/codeword/promocode";
|
|||||||
|
|
||||||
const getPromocodeList = async (body: GetPromocodeListBody) => {
|
const getPromocodeList = async (body: GetPromocodeListBody) => {
|
||||||
try {
|
try {
|
||||||
const promocodeListResponse = await makeRequest<
|
const promocodeListResponse = await makeRequest<GetPromocodeListBody, PromocodeList>({
|
||||||
GetPromocodeListBody,
|
|
||||||
PromocodeList
|
|
||||||
>({
|
|
||||||
url: baseUrl + "/getList",
|
url: baseUrl + "/getList",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body,
|
body,
|
||||||
@ -73,10 +70,7 @@ export const getAllPromocodes = async () => {
|
|||||||
|
|
||||||
const createPromocode = async (body: CreatePromocodeBody) => {
|
const createPromocode = async (body: CreatePromocodeBody) => {
|
||||||
try {
|
try {
|
||||||
const createPromocodeResponse = await makeRequest<
|
const createPromocodeResponse = await makeRequest<CreatePromocodeBody, Promocode>({
|
||||||
CreatePromocodeBody,
|
|
||||||
Promocode
|
|
||||||
>({
|
|
||||||
url: baseUrl + "/create",
|
url: baseUrl + "/create",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body,
|
body,
|
||||||
@ -85,10 +79,7 @@ const createPromocode = async (body: CreatePromocodeBody) => {
|
|||||||
|
|
||||||
return createPromocodeResponse;
|
return createPromocodeResponse;
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
if (
|
if (isAxiosError(nativeError) && nativeError.response?.data.error === "Duplicate Codeword") {
|
||||||
isAxiosError(nativeError) &&
|
|
||||||
nativeError.response?.data.error === "Duplicate Codeword"
|
|
||||||
) {
|
|
||||||
throw new Error(`Промокод уже существует`);
|
throw new Error(`Промокод уже существует`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,10 +103,7 @@ const deletePromocode = async (id: string): Promise<void> => {
|
|||||||
|
|
||||||
const getPromocodeStatistics = async (id: string, from: number, to: number) => {
|
const getPromocodeStatistics = async (id: string, from: number, to: number) => {
|
||||||
try {
|
try {
|
||||||
const promocodeStatisticsResponse = await makeRequest<
|
const promocodeStatisticsResponse = await makeRequest<unknown, PromocodeStatistics>({
|
||||||
unknown,
|
|
||||||
PromocodeStatistics
|
|
||||||
>({
|
|
||||||
url: baseUrl + `/stats`,
|
url: baseUrl + `/stats`,
|
||||||
body: {
|
body: {
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -3,18 +3,9 @@ import useSwr, { mutate } from "swr";
|
|||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { promocodeApi } from "./requests";
|
import { promocodeApi } from "./requests";
|
||||||
|
|
||||||
import type {
|
import type { CreatePromocodeBody, PromocodeList } from "@root/model/promocodes";
|
||||||
CreatePromocodeBody,
|
|
||||||
PromocodeList,
|
|
||||||
} from "@root/model/promocodes";
|
|
||||||
|
|
||||||
export function usePromocodes(
|
export function usePromocodes(page: number, pageSize: number, promocodeId: string, to: number, from: number) {
|
||||||
page: number,
|
|
||||||
pageSize: number,
|
|
||||||
promocodeId: string,
|
|
||||||
to: number,
|
|
||||||
from: number
|
|
||||||
) {
|
|
||||||
const promocodesCountRef = useRef<number>(0);
|
const promocodesCountRef = useRef<number>(0);
|
||||||
const swrResponse = useSwr(
|
const swrResponse = useSwr(
|
||||||
["promocodes", page, pageSize],
|
["promocodes", page, pageSize],
|
||||||
@ -47,8 +38,7 @@ export function usePromocodes(
|
|||||||
mutate(["promocodes", page, pageSize]);
|
mutate(["promocodes", page, pageSize]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating promocode", error);
|
console.error("Error creating promocode", error);
|
||||||
if (error instanceof Error)
|
if (error instanceof Error) enqueueSnackbar(error.message, { variant: "error" });
|
||||||
enqueueSnackbar(error.message, { variant: "error" });
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[page, pageSize]
|
[page, pageSize]
|
||||||
@ -82,8 +72,7 @@ export function usePromocodes(
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error deleting promocode", error);
|
console.error("Error deleting promocode", error);
|
||||||
if (error instanceof Error)
|
if (error instanceof Error) enqueueSnackbar(error.message, { variant: "error" });
|
||||||
enqueueSnackbar(error.message, { variant: "error" });
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[page, pageSize]
|
[page, pageSize]
|
||||||
@ -96,8 +85,7 @@ export function usePromocodes(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const promocodeStatisticsResponse =
|
const promocodeStatisticsResponse = await promocodeApi.getPromocodeStatistics(id, from, to);
|
||||||
await promocodeApi.getPromocodeStatistics(id, from, to);
|
|
||||||
|
|
||||||
return promocodeStatisticsResponse;
|
return promocodeStatisticsResponse;
|
||||||
},
|
},
|
||||||
@ -118,8 +106,7 @@ export function usePromocodes(
|
|||||||
mutate(["promocodes", page, pageSize]);
|
mutate(["promocodes", page, pageSize]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating fast link", error);
|
console.error("Error creating fast link", error);
|
||||||
if (error instanceof Error)
|
if (error instanceof Error) enqueueSnackbar(error.message, { variant: "error" });
|
||||||
enqueueSnackbar(error.message, { variant: "error" });
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[page, pageSize]
|
[page, pageSize]
|
||||||
|
@ -18,10 +18,7 @@ type TRequest = {
|
|||||||
from: number;
|
from: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStatistic = async (
|
export const getStatistic = async (to: number, from: number): Promise<QuizStatisticResponse> => {
|
||||||
to: number,
|
|
||||||
from: number
|
|
||||||
): Promise<QuizStatisticResponse> => {
|
|
||||||
try {
|
try {
|
||||||
const generalResponse = await makeRequest<TRequest, QuizStatisticResponse>({
|
const generalResponse = await makeRequest<TRequest, QuizStatisticResponse>({
|
||||||
url: `${process.env.REACT_APP_DOMAIN}/squiz/statistic`,
|
url: `${process.env.REACT_APP_DOMAIN}/squiz/statistic`,
|
||||||
@ -33,15 +30,9 @@ export const getStatistic = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStatisticSchild = async (
|
export const getStatisticSchild = async (from: number, to: number): Promise<QuizStatisticsItem[]> => {
|
||||||
from: number,
|
|
||||||
to: number
|
|
||||||
): Promise<QuizStatisticsItem[]> => {
|
|
||||||
try {
|
try {
|
||||||
const StatisticResponse = await makeRequest<
|
const StatisticResponse = await makeRequest<GetStatisticSchildBody, QuizStatisticsItem[]>({
|
||||||
GetStatisticSchildBody,
|
|
||||||
QuizStatisticsItem[]
|
|
||||||
>({
|
|
||||||
url: process.env.REACT_APP_DOMAIN + "/customer/quizlogo/stat",
|
url: process.env.REACT_APP_DOMAIN + "/customer/quizlogo/stat",
|
||||||
method: "post",
|
method: "post",
|
||||||
useToken: true,
|
useToken: true,
|
||||||
@ -70,10 +61,7 @@ export const getStatisticPromocode = async (
|
|||||||
to: number
|
to: number
|
||||||
): Promise<Record<string, AllPromocodeStatistics>> => {
|
): Promise<Record<string, AllPromocodeStatistics>> => {
|
||||||
try {
|
try {
|
||||||
const StatisticPromo = await makeRequest<
|
const StatisticPromo = await makeRequest<GetPromocodeStatisticsBody, Record<string, AllPromocodeStatistics>>({
|
||||||
GetPromocodeStatisticsBody,
|
|
||||||
Record<string, AllPromocodeStatistics>
|
|
||||||
>({
|
|
||||||
url: process.env.REACT_APP_DOMAIN + "/customer/promocode/ltv",
|
url: process.env.REACT_APP_DOMAIN + "/customer/promocode/ltv",
|
||||||
method: "post",
|
method: "post",
|
||||||
useToken: true,
|
useToken: true,
|
||||||
|
@ -35,7 +35,7 @@ export type UserType = {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseUrl =process.env.REACT_APP_DOMAIN + "/role"
|
const baseUrl = process.env.REACT_APP_DOMAIN + "/role";
|
||||||
|
|
||||||
export const getRoles_mock = (): Promise<TMockData> => {
|
export const getRoles_mock = (): Promise<TMockData> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
@ -20,19 +20,15 @@ type GetTariffsResponse = {
|
|||||||
tariffs: Tariff[];
|
tariffs: Tariff[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseUrl =process.env.REACT_APP_DOMAIN + "/strator"
|
const baseUrl = process.env.REACT_APP_DOMAIN + "/strator";
|
||||||
|
|
||||||
export const createTariff = async (
|
export const createTariff = async (body: CreateTariffBackendRequest): Promise<[unknown, string?]> => {
|
||||||
body: CreateTariffBackendRequest
|
|
||||||
): Promise<[unknown, string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const createdTariffResponse = await makeRequest<CreateTariffBackendRequest>(
|
const createdTariffResponse = await makeRequest<CreateTariffBackendRequest>({
|
||||||
{
|
|
||||||
url: baseUrl + "/tariff/",
|
url: baseUrl + "/tariff/",
|
||||||
method: "post",
|
method: "post",
|
||||||
body,
|
body,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return [createdTariffResponse];
|
return [createdTariffResponse];
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
@ -65,9 +61,7 @@ export const putTariff = async (tariff: Tariff): Promise<[null, string?]> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteTariff = async (
|
export const deleteTariff = async (tariffId: string): Promise<[Tariff | null, string?]> => {
|
||||||
tariffId: string
|
|
||||||
): Promise<[Tariff | null, string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const deletedTariffResponse = await makeRequest<{ id: string }, Tariff>({
|
const deletedTariffResponse = await makeRequest<{ id: string }, Tariff>({
|
||||||
method: "delete",
|
method: "delete",
|
||||||
@ -83,9 +77,7 @@ export const deleteTariff = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requestTariffs = async (
|
export const requestTariffs = async (page: number): Promise<[GetTariffsResponse | null, string?]> => {
|
||||||
page: number
|
|
||||||
): Promise<[GetTariffsResponse | null, string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const tariffsResponse = await makeRequest<never, GetTariffsResponse>({
|
const tariffsResponse = await makeRequest<never, GetTariffsResponse>({
|
||||||
url: baseUrl + `/tariff/?page=${page}&limit=${100}`,
|
url: baseUrl + `/tariff/?page=${page}&limit=${100}`,
|
||||||
|
@ -4,16 +4,11 @@ import { parseAxiosError } from "@root/utils/parse-error";
|
|||||||
|
|
||||||
import type { SendTicketMessageRequest } from "@root/model/ticket";
|
import type { SendTicketMessageRequest } from "@root/model/ticket";
|
||||||
|
|
||||||
const baseUrl = process.env.REACT_APP_DOMAIN + "/heruvym"
|
const baseUrl = process.env.REACT_APP_DOMAIN + "/heruvym";
|
||||||
|
|
||||||
export const sendTicketMessage = async (
|
export const sendTicketMessage = async (body: SendTicketMessageRequest): Promise<[unknown, string?]> => {
|
||||||
body: SendTicketMessageRequest
|
|
||||||
): Promise<[unknown, string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const sendTicketMessageResponse = await makeRequest<
|
const sendTicketMessageResponse = await makeRequest<SendTicketMessageRequest, unknown>({
|
||||||
SendTicketMessageRequest,
|
|
||||||
unknown
|
|
||||||
>({
|
|
||||||
url: `${baseUrl}/send`,
|
url: `${baseUrl}/send`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
useToken: true,
|
useToken: true,
|
||||||
|
@ -27,10 +27,7 @@ const getUserInfo = async (id: string): Promise<[UserType | null, string?]> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUserList = async (
|
const getUserList = async (page = 1, limit = 10): Promise<[UsersListResponse | null, string?]> => {
|
||||||
page = 1,
|
|
||||||
limit = 10
|
|
||||||
): Promise<[UsersListResponse | null, string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const userResponse = await makeRequest<never, UsersListResponse>({
|
const userResponse = await makeRequest<never, UsersListResponse>({
|
||||||
method: "get",
|
method: "get",
|
||||||
@ -45,10 +42,7 @@ const getUserList = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getManagerList = async (
|
const getManagerList = async (page = 1, limit = 10): Promise<[UsersListResponse | null, string?]> => {
|
||||||
page = 1,
|
|
||||||
limit = 10
|
|
||||||
): Promise<[UsersListResponse | null, string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const managerResponse = await makeRequest<never, UsersListResponse>({
|
const managerResponse = await makeRequest<never, UsersListResponse>({
|
||||||
method: "get",
|
method: "get",
|
||||||
@ -63,10 +57,7 @@ const getManagerList = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAdminList = async (
|
const getAdminList = async (page = 1, limit = 10): Promise<[UsersListResponse | null, string?]> => {
|
||||||
page = 1,
|
|
||||||
limit = 10
|
|
||||||
): Promise<[UsersListResponse | null, string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const adminResponse = await makeRequest<never, UsersListResponse>({
|
const adminResponse = await makeRequest<never, UsersListResponse>({
|
||||||
method: "get",
|
method: "get",
|
||||||
|
@ -10,10 +10,7 @@ export function useAdmins(page: number, pageSize: number) {
|
|||||||
const swrResponse = useSwr(
|
const swrResponse = useSwr(
|
||||||
["admin", page, pageSize],
|
["admin", page, pageSize],
|
||||||
async ([_, page, pageSize]) => {
|
async ([_, page, pageSize]) => {
|
||||||
const [adminResponse, error] = await userApi.getManagerList(
|
const [adminResponse, error] = await userApi.getManagerList(page, pageSize);
|
||||||
page,
|
|
||||||
pageSize
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
@ -44,10 +41,7 @@ export function useManagers(page: number, pageSize: number) {
|
|||||||
const swrResponse = useSwr(
|
const swrResponse = useSwr(
|
||||||
["manager", page, pageSize],
|
["manager", page, pageSize],
|
||||||
async ([_, page, pageSize]) => {
|
async ([_, page, pageSize]) => {
|
||||||
const [managerResponse, error] = await userApi.getManagerList(
|
const [managerResponse, error] = await userApi.getManagerList(page, pageSize);
|
||||||
page,
|
|
||||||
pageSize
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
|
@ -25,11 +25,9 @@ type PatchVerificationBody = {
|
|||||||
taxnumber?: string;
|
taxnumber?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseUrl = process.env.REACT_APP_DOMAIN + "/verification"
|
const baseUrl = process.env.REACT_APP_DOMAIN + "/verification";
|
||||||
|
|
||||||
export const verification = async (
|
export const verification = async (userId: string): Promise<[Verification | null, string?]> => {
|
||||||
userId: string
|
|
||||||
): Promise<[Verification | null, string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const verificationResponse = await makeRequest<never, Verification>({
|
const verificationResponse = await makeRequest<never, Verification>({
|
||||||
method: "get",
|
method: "get",
|
||||||
@ -44,14 +42,9 @@ export const verification = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const patchVerification = async (
|
export const patchVerification = async (body: PatchVerificationBody): Promise<[unknown, string?]> => {
|
||||||
body: PatchVerificationBody
|
|
||||||
): Promise<[unknown, string?]> => {
|
|
||||||
try {
|
try {
|
||||||
const patchedVerificationResponse = await makeRequest<
|
const patchedVerificationResponse = await makeRequest<PatchVerificationBody, unknown>({
|
||||||
PatchVerificationBody,
|
|
||||||
unknown
|
|
||||||
>({
|
|
||||||
method: "patch",
|
method: "patch",
|
||||||
useToken: true,
|
useToken: true,
|
||||||
url: baseUrl + `/verification`,
|
url: baseUrl + `/verification`,
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "GilroyRegular";
|
font-family: "GilroyRegular";
|
||||||
src: local("GilroyRegular"), url(./fonts/GilroyRegular.woff) format("woff");
|
src:
|
||||||
|
local("GilroyRegular"),
|
||||||
|
url(./fonts/GilroyRegular.woff) format("woff");
|
||||||
}
|
}
|
@ -14,7 +14,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
Typography,
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useDiscounts } from "@root/api/discounts";
|
import { useDiscounts } from "@root/api/discounts";
|
||||||
import { useAllPromocodes } from "@root/api/promocode/swr";
|
import { useAllPromocodes } from "@root/api/promocode/swr";
|
||||||
@ -27,42 +27,40 @@ import { currencyFormatter } from "@root/utils/currencyFormatter";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import CartItemRow from "./CartItemRow";
|
import CartItemRow from "./CartItemRow";
|
||||||
|
|
||||||
|
|
||||||
export default function Cart() {
|
export default function Cart() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const mobile = useMediaQuery(theme.breakpoints.down(400));
|
const mobile = useMediaQuery(theme.breakpoints.down(400));
|
||||||
const discounts = useDiscounts();
|
const discounts = useDiscounts();
|
||||||
const promocodes = useAllPromocodes();
|
const promocodes = useAllPromocodes();
|
||||||
const cartData = useCartStore((store) => store.cartData);
|
const cartData = useCartStore((store) => store.cartData);
|
||||||
const tariffs = useTariffStore(state => state.tariffs);
|
const tariffs = useTariffStore((state) => state.tariffs);
|
||||||
const [couponField, setCouponField] = useState<string>("");
|
const [couponField, setCouponField] = useState<string>("");
|
||||||
const [loyaltyField, setLoyaltyField] = useState<string>("");
|
const [loyaltyField, setLoyaltyField] = useState<string>("");
|
||||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||||
const [isNonCommercial, setIsNonCommercial] = useState<boolean>(false);
|
const [isNonCommercial, setIsNonCommercial] = useState<boolean>(false);
|
||||||
const selectedTariffIds = useTariffStore(state => state.selectedTariffIds);
|
const selectedTariffIds = useTariffStore((state) => state.selectedTariffIds);
|
||||||
|
|
||||||
async function handleCalcCartClick() {
|
async function handleCalcCartClick() {
|
||||||
await requestPrivileges();
|
await requestPrivileges();
|
||||||
await requestDiscounts();
|
await requestDiscounts();
|
||||||
|
|
||||||
const cartTariffs = tariffs.filter(tariff => selectedTariffIds.includes(tariff._id));
|
const cartTariffs = tariffs.filter((tariff) => selectedTariffIds.includes(tariff._id));
|
||||||
|
|
||||||
let loyaltyValue = parseInt(loyaltyField);
|
let loyaltyValue = parseInt(loyaltyField);
|
||||||
|
|
||||||
if (!isFinite(loyaltyValue)) loyaltyValue = 0;
|
if (!isFinite(loyaltyValue)) loyaltyValue = 0;
|
||||||
|
|
||||||
const promocode = promocodes.find(promocode => {
|
const promocode = promocodes.find((promocode) => {
|
||||||
if (promocode.dueTo < (Date.now() / 1000)) return false;
|
if (promocode.dueTo < Date.now() / 1000) return false;
|
||||||
|
|
||||||
return promocode.codeword === couponField.trim();
|
return promocode.codeword === couponField.trim();
|
||||||
});
|
});
|
||||||
|
|
||||||
const userId = crypto.randomUUID();
|
const userId = crypto.randomUUID();
|
||||||
|
|
||||||
const discountsWithPromocodeDiscount = promocode ? [
|
const discountsWithPromocodeDiscount = promocode
|
||||||
...discounts,
|
? [...discounts, createDiscountFromPromocode(promocode, userId)]
|
||||||
createDiscountFromPromocode(promocode, userId),
|
: discounts;
|
||||||
] : discounts;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cartData = calcCart(cartTariffs, discountsWithPromocodeDiscount, loyaltyValue, userId);
|
const cartData = calcCart(cartTariffs, discountsWithPromocodeDiscount, loyaltyValue, userId);
|
||||||
@ -96,7 +94,7 @@ export default function Cart() {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
gap: "20px",
|
gap: "20px",
|
||||||
flexDirection: mobile ? "column" : undefined
|
flexDirection: mobile ? "column" : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
@ -201,44 +199,33 @@ export default function Cart() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Typography
|
<Typography variant="h4" sx={{ color: theme.palette.secondary.main }}>
|
||||||
variant="h4"
|
|
||||||
sx={{ color: theme.palette.secondary.main }}
|
|
||||||
>
|
|
||||||
Имя
|
Имя
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Typography
|
<Typography variant="h4" sx={{ color: theme.palette.secondary.main }}>
|
||||||
variant="h4"
|
|
||||||
sx={{ color: theme.palette.secondary.main }}
|
|
||||||
>
|
|
||||||
Описание
|
Описание
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Typography
|
<Typography variant="h4" sx={{ color: theme.palette.secondary.main }}>
|
||||||
variant="h4"
|
|
||||||
sx={{ color: theme.palette.secondary.main }}
|
|
||||||
>
|
|
||||||
Скидки
|
Скидки
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Typography
|
<Typography variant="h4" sx={{ color: theme.palette.secondary.main }}>
|
||||||
variant="h4"
|
|
||||||
sx={{ color: theme.palette.secondary.main }}
|
|
||||||
>
|
|
||||||
стоимость
|
стоимость
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{cartData.services.flatMap(service => service.tariffs.map(tariffCartData => {
|
{cartData.services.flatMap((service) =>
|
||||||
const appliedDiscounts = tariffCartData.privileges.flatMap(
|
service.tariffs.map((tariffCartData) => {
|
||||||
privilege => Array.from(privilege.appliedDiscounts)
|
const appliedDiscounts = tariffCartData.privileges
|
||||||
).sort((a, b) => a.Layer - b.Layer);
|
.flatMap((privilege) => Array.from(privilege.appliedDiscounts))
|
||||||
|
.sort((a, b) => a.Layer - b.Layer);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CartItemRow
|
<CartItemRow
|
||||||
@ -247,7 +234,8 @@ export default function Cart() {
|
|||||||
appliedDiscounts={appliedDiscounts}
|
appliedDiscounts={appliedDiscounts}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}))}
|
})
|
||||||
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import { Discount, TariffCartData } from "@frontend/kitui";
|
|||||||
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
tariffCartData: TariffCartData;
|
tariffCartData: TariffCartData;
|
||||||
appliedDiscounts: Discount[];
|
appliedDiscounts: Discount[];
|
||||||
@ -27,28 +26,20 @@ export default function CartItemRow({ tariffCartData, appliedDiscounts }: Props)
|
|||||||
borderColor: theme.palette.grayLight.main,
|
borderColor: theme.palette.grayLight.main,
|
||||||
".MuiTableCell-root": {
|
".MuiTableCell-root": {
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TableCell>
|
<TableCell>{tariffCartData.name}</TableCell>
|
||||||
{tariffCartData.name}
|
<TableCell>{tariffCartData.privileges[0].description}</TableCell>
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{tariffCartData.privileges[0].description}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{appliedDiscounts.map((discount, index, arr) => (
|
{appliedDiscounts.map((discount, index, arr) => (
|
||||||
<span key={discount.ID}>
|
<span key={discount.ID}>
|
||||||
<DiscountTooltip discount={discount} />
|
<DiscountTooltip discount={discount} />
|
||||||
{index < arr.length - 1 && (
|
{index < arr.length - 1 && <span>, </span>}
|
||||||
<span>, </span>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>{currencyFormatter.format(tariffCartData.price / 100)}</TableCell>
|
||||||
{currencyFormatter.format(tariffCartData.price / 100)}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import { Tooltip, Typography } from "@mui/material";
|
|||||||
import { Discount, findDiscountFactor } from "@frontend/kitui";
|
import { Discount, findDiscountFactor } from "@frontend/kitui";
|
||||||
import { formatDiscountFactor } from "@root/utils/formatDiscountFactor";
|
import { formatDiscountFactor } from "@root/utils/formatDiscountFactor";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
discount: Discount;
|
discount: Discount;
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,22 @@ import { SxProps, TextField, Theme, useTheme } from "@mui/material";
|
|||||||
import { HTMLInputTypeAttribute, ChangeEvent } from "react";
|
import { HTMLInputTypeAttribute, ChangeEvent } from "react";
|
||||||
import { InputBaseProps } from "@mui/material/InputBase";
|
import { InputBaseProps } from "@mui/material/InputBase";
|
||||||
|
|
||||||
|
export function CustomTextField({
|
||||||
export function CustomTextField({ id, label, value, name, onBlur,error, type, sx, onChange: setValue }: {
|
id,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
name,
|
||||||
|
onBlur,
|
||||||
|
error,
|
||||||
|
type,
|
||||||
|
sx,
|
||||||
|
onChange: setValue,
|
||||||
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
value: number | string | null;
|
value: number | string | null;
|
||||||
name?: string;
|
name?: string;
|
||||||
onBlur?: InputBaseProps['onBlur'];
|
onBlur?: InputBaseProps["onBlur"];
|
||||||
error?: boolean;
|
error?: boolean;
|
||||||
type?: HTMLInputTypeAttribute;
|
type?: HTMLInputTypeAttribute;
|
||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
@ -32,12 +41,12 @@ export function CustomTextField({ id, label, value, name, onBlur,error, type, sx
|
|||||||
style: {
|
style: {
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
style: {
|
style: {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
value={value ? value : ""}
|
value={value ? value : ""}
|
||||||
onChange={setValue}
|
onChange={setValue}
|
||||||
|
@ -4,23 +4,18 @@ const BeautifulButton = styled(Button)(({ theme }) => ({
|
|||||||
width: "250px",
|
width: "250px",
|
||||||
margin: "15px auto",
|
margin: "15px auto",
|
||||||
padding: "20px 30px",
|
padding: "20px 30px",
|
||||||
fontSize: 18
|
fontSize: 18,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isReady: boolean
|
isReady: boolean;
|
||||||
text:string
|
text: string;
|
||||||
type?: "button" | "reset" | "submit"
|
type?: "button" | "reset" | "submit";
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ({
|
export default ({ isReady = true, text, type = "button" }: Props) => {
|
||||||
isReady = true,
|
|
||||||
text,
|
|
||||||
type = "button"
|
|
||||||
}:Props) => {
|
|
||||||
|
|
||||||
if (isReady) {
|
if (isReady) {
|
||||||
return <BeautifulButton type={type}>{text}</BeautifulButton>
|
return <BeautifulButton type={type}>{text}</BeautifulButton>;
|
||||||
}
|
|
||||||
return <Skeleton>{text}</Skeleton>
|
|
||||||
}
|
}
|
||||||
|
return <Skeleton>{text}</Skeleton>;
|
||||||
|
};
|
||||||
|
@ -1,28 +1,27 @@
|
|||||||
import { DataGrid } from "@mui/x-data-grid";
|
import { DataGrid } from "@mui/x-data-grid";
|
||||||
import { styled } from "@mui/material/styles";
|
import { styled } from "@mui/material/styles";
|
||||||
|
|
||||||
|
|
||||||
export default styled(DataGrid)(({ theme }) => ({
|
export default styled(DataGrid)(({ theme }) => ({
|
||||||
width: "100%",
|
width: "100%",
|
||||||
minHeight: "400px",
|
minHeight: "400px",
|
||||||
margin: "10px 0",
|
margin: "10px 0",
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
"& .MuiDataGrid-iconSeparator": {
|
"& .MuiDataGrid-iconSeparator": {
|
||||||
display: "none"
|
display: "none",
|
||||||
},
|
},
|
||||||
"& .css-levciy-MuiTablePagination-displayedRows": {
|
"& .css-levciy-MuiTablePagination-displayedRows": {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
},
|
},
|
||||||
"& .MuiSvgIcon-root": {
|
"& .MuiSvgIcon-root": {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
},
|
},
|
||||||
"& .MuiTablePagination-selectLabel": {
|
"& .MuiTablePagination-selectLabel": {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
},
|
},
|
||||||
"& .MuiInputBase-root": {
|
"& .MuiInputBase-root": {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
},
|
},
|
||||||
"& .MuiButton-text": {
|
"& .MuiButton-text": {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
},
|
},
|
||||||
})) as typeof DataGrid;
|
})) as typeof DataGrid;
|
||||||
|
@ -12,5 +12,5 @@ export default styled(TextField)(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
"& .Mui-focused": {
|
"& .Mui-focused": {
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}));
|
}));
|
@ -14,4 +14,4 @@ export default function PrivateRoute({ children }: Props) {
|
|||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
return <Navigate to="/" state={{ from: location }} />;
|
return <Navigate to="/" state={{ from: location }} />;
|
||||||
};
|
}
|
||||||
|
@ -2,7 +2,6 @@ import { useLocation, Navigate } from "react-router-dom";
|
|||||||
|
|
||||||
import { useToken } from "@frontend/kitui";
|
import { useToken } from "@frontend/kitui";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import { Discount } from "@frontend/kitui";
|
import { Discount } from "@frontend/kitui";
|
||||||
|
|
||||||
|
|
||||||
export type GetDiscountResponse = {
|
export type GetDiscountResponse = {
|
||||||
Discounts: Discount[];
|
Discounts: Discount[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const discountTypes = {
|
export const discountTypes = {
|
||||||
"purchasesAmount": "Лояльность",
|
purchasesAmount: "Лояльность",
|
||||||
"cartPurchasesAmount": "Корзина",
|
cartPurchasesAmount: "Корзина",
|
||||||
"service": "Сервис",
|
service: "Сервис",
|
||||||
"privilege": "Товар",
|
privilege: "Товар",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type DiscountType = keyof typeof discountTypes;
|
export type DiscountType = keyof typeof discountTypes;
|
||||||
@ -41,10 +40,12 @@ export type CreateDiscountBody = {
|
|||||||
TargetScope: "Sum" | "Group" | "Each";
|
TargetScope: "Sum" | "Group" | "Each";
|
||||||
Overhelm: boolean;
|
Overhelm: boolean;
|
||||||
TargetGroup: string;
|
TargetGroup: string;
|
||||||
Products: [{
|
Products: [
|
||||||
|
{
|
||||||
ID: string;
|
ID: string;
|
||||||
Factor: number;
|
Factor: number;
|
||||||
Overhelm: false;
|
Overhelm: false;
|
||||||
}];
|
},
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
|
|
||||||
|
|
||||||
export interface CreateTicketRequest {
|
export interface CreateTicketRequest {
|
||||||
Title: string;
|
Title: string;
|
||||||
Message: string;
|
Message: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface CreateTicketResponse {
|
export interface CreateTicketResponse {
|
||||||
Ticket: string;
|
Ticket: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface SendTicketMessageRequest {
|
export interface SendTicketMessageRequest {
|
||||||
message: string;
|
message: string;
|
||||||
ticket: string;
|
ticket: string;
|
||||||
lang: string;
|
lang: string;
|
||||||
files: string[];
|
files: string[];
|
||||||
};
|
}
|
||||||
|
|
||||||
export type TicketStatus = "open";
|
export type TicketStatus = "open";
|
||||||
|
|
||||||
@ -29,16 +27,16 @@ export interface Ticket {
|
|||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
rate: number;
|
rate: number;
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface TicketMessage {
|
export interface TicketMessage {
|
||||||
id: string;
|
id: string;
|
||||||
ticket_id: string;
|
ticket_id: string;
|
||||||
user_id: string,
|
user_id: string;
|
||||||
session_id: string;
|
session_id: string;
|
||||||
message: string;
|
message: string;
|
||||||
files: string[],
|
files: string[];
|
||||||
shown: { [key: string]: number; },
|
shown: { [key: string]: number };
|
||||||
request_screenshot: string,
|
request_screenshot: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
};
|
}
|
||||||
|
@ -3,13 +3,7 @@ import { enqueueSnackbar } from "notistack";
|
|||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
import { Formik, Field, Form, FormikHelpers } from "formik";
|
import { Formik, Field, Form, FormikHelpers } from "formik";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import {
|
import { Box, Checkbox, Typography, FormControlLabel, Button, useMediaQuery } from "@mui/material";
|
||||||
Box,
|
|
||||||
Checkbox,
|
|
||||||
Typography,
|
|
||||||
FormControlLabel,
|
|
||||||
Button, useMediaQuery,
|
|
||||||
} from "@mui/material";
|
|
||||||
import Logo from "@pages/Logo";
|
import Logo from "@pages/Logo";
|
||||||
import OutlinedInput from "@kitUI/outlinedInput";
|
import OutlinedInput from "@kitUI/outlinedInput";
|
||||||
import EmailOutlinedIcon from "@mui/icons-material/EmailOutlined";
|
import EmailOutlinedIcon from "@mui/icons-material/EmailOutlined";
|
||||||
@ -50,10 +44,7 @@ const SigninForm = () => {
|
|||||||
password: "",
|
password: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSignFormSubmit = async (
|
const onSignFormSubmit = async (values: Values, formikHelpers: FormikHelpers<Values>) => {
|
||||||
values: Values,
|
|
||||||
formikHelpers: FormikHelpers<Values>
|
|
||||||
) => {
|
|
||||||
formikHelpers.setSubmitting(true);
|
formikHelpers.setSubmitting(true);
|
||||||
|
|
||||||
const [_, signinError] = await signin(values.email, values.password);
|
const [_, signinError] = await signin(values.email, values.password);
|
||||||
@ -68,11 +59,7 @@ const SigninForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Formik initialValues={initialValues} validate={validate} onSubmit={onSignFormSubmit}>
|
||||||
initialValues={initialValues}
|
|
||||||
validate={validate}
|
|
||||||
onSubmit={onSignFormSubmit}
|
|
||||||
>
|
|
||||||
{(props) => (
|
{(props) => (
|
||||||
<Form>
|
<Form>
|
||||||
<Box
|
<Box
|
||||||
@ -99,7 +86,7 @@ const SigninForm = () => {
|
|||||||
"> *": {
|
"> *": {
|
||||||
marginTop: "15px",
|
marginTop: "15px",
|
||||||
},
|
},
|
||||||
padding: isMobile ? "0 16px" : undefined
|
padding: isMobile ? "0 16px" : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Logo />
|
<Logo />
|
||||||
@ -185,9 +172,7 @@ const SigninForm = () => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Link to="/restore" style={{ textDecoration: "none" }}>
|
<Link to="/restore" style={{ textDecoration: "none" }}>
|
||||||
<Typography color={theme.palette.golden.main}>
|
<Typography color={theme.palette.golden.main}>Забыли пароль?</Typography>
|
||||||
Забыли пароль?
|
|
||||||
</Typography>
|
|
||||||
</Link>
|
</Link>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
@ -206,13 +191,9 @@ const SigninForm = () => {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography color={theme.palette.secondary.main}>
|
<Typography color={theme.palette.secondary.main}>У вас нет аккаунта? </Typography>
|
||||||
У вас нет аккаунта?
|
|
||||||
</Typography>
|
|
||||||
<Link to="/signup" style={{ textDecoration: "none" }}>
|
<Link to="/signup" style={{ textDecoration: "none" }}>
|
||||||
<Typography color={theme.palette.golden.main}>
|
<Typography color={theme.palette.golden.main}>Зарегестрируйтесь</Typography>
|
||||||
Зарегестрируйтесь
|
|
||||||
</Typography>
|
|
||||||
</Link>
|
</Link>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -53,10 +53,7 @@ const SignUp = () => {
|
|||||||
onSubmit={async (values, formikHelpers) => {
|
onSubmit={async (values, formikHelpers) => {
|
||||||
formikHelpers.setSubmitting(true);
|
formikHelpers.setSubmitting(true);
|
||||||
|
|
||||||
const [_, registerError] = await register(
|
const [_, registerError] = await register(values.email, values.repeatPassword);
|
||||||
values.email,
|
|
||||||
values.repeatPassword
|
|
||||||
);
|
|
||||||
|
|
||||||
formikHelpers.setSubmitting(false);
|
formikHelpers.setSubmitting(false);
|
||||||
|
|
||||||
@ -172,14 +169,10 @@ const SignUp = () => {
|
|||||||
variant="filled"
|
variant="filled"
|
||||||
label="Повторите пароль"
|
label="Повторите пароль"
|
||||||
id="repeatPassword"
|
id="repeatPassword"
|
||||||
error={
|
error={props.touched.repeatPassword && !!props.errors.repeatPassword}
|
||||||
props.touched.repeatPassword &&
|
|
||||||
!!props.errors.repeatPassword
|
|
||||||
}
|
|
||||||
helperText={
|
helperText={
|
||||||
<Typography sx={{ fontSize: "12px", width: "200px" }}>
|
<Typography sx={{ fontSize: "12px", width: "200px" }}>
|
||||||
{props.touched.repeatPassword &&
|
{props.touched.repeatPassword && props.errors.repeatPassword}
|
||||||
props.errors.repeatPassword}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -197,9 +190,7 @@ const SignUp = () => {
|
|||||||
Войти
|
Войти
|
||||||
</Button>
|
</Button>
|
||||||
<Link to="/signin" style={{ textDecoration: "none" }}>
|
<Link to="/signin" style={{ textDecoration: "none" }}>
|
||||||
<Typography color={theme.palette.golden.main}>
|
<Typography color={theme.palette.golden.main}>У меня уже есть аккаунт</Typography>
|
||||||
У меня уже есть аккаунт
|
|
||||||
</Typography>
|
|
||||||
</Link>
|
</Link>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -2,64 +2,72 @@ import * as React from "react";
|
|||||||
import { Box, Button, Typography } from "@mui/material";
|
import { Box, Button, Typography } from "@mui/material";
|
||||||
import { ThemeProvider } from "@mui/material";
|
import { ThemeProvider } from "@mui/material";
|
||||||
import theme from "../../theme";
|
import theme from "../../theme";
|
||||||
import CssBaseline from '@mui/material/CssBaseline';
|
import CssBaseline from "@mui/material/CssBaseline";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
|
||||||
const Error404: React.FC = () => {
|
const Error404: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
height: "100%"
|
height: "100%",
|
||||||
}}>
|
}}
|
||||||
<Box sx={{
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
width: "100vw",
|
width: "100vw",
|
||||||
height: "100vh",
|
height: "100vh",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center"
|
alignItems: "center",
|
||||||
}}>
|
}}
|
||||||
<Box sx={{
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
width: "150px",
|
width: "150px",
|
||||||
height: "120px",
|
height: "120px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "row"
|
flexDirection: "row",
|
||||||
}}>
|
}}
|
||||||
<Typography sx={{
|
>
|
||||||
fontSize: "80px"
|
<Typography
|
||||||
}}>
|
sx={{
|
||||||
|
fontSize: "80px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
4
|
4
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography sx={{
|
<Typography
|
||||||
|
sx={{
|
||||||
color: theme.palette.golden.main,
|
color: theme.palette.golden.main,
|
||||||
fontSize: "80px"
|
fontSize: "80px",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
0
|
0
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography sx={{
|
<Typography
|
||||||
fontSize: "80px"
|
sx={{
|
||||||
}}>
|
fontSize: "80px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
4
|
4
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Link
|
<Link to="/users">
|
||||||
to="/users">
|
|
||||||
<Button>На главную</Button>
|
<Button>На главную</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export default Error404;
|
export default Error404;
|
@ -2,13 +2,14 @@ import * as React from "react";
|
|||||||
import { Box, Typography } from "@mui/material";
|
import { Box, Typography } from "@mui/material";
|
||||||
import theme from "../../theme";
|
import theme from "../../theme";
|
||||||
|
|
||||||
|
|
||||||
const Authorization: React.FC = () => {
|
const Authorization: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Box sx={{
|
<Box
|
||||||
display: "flex"
|
sx={{
|
||||||
}}>
|
display: "flex",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Typography
|
<Typography
|
||||||
variant="subtitle1"
|
variant="subtitle1"
|
||||||
sx={{
|
sx={{
|
||||||
@ -16,8 +17,9 @@ const Authorization: React.FC = () => {
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
cursor: "default"
|
cursor: "default",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
PENA
|
PENA
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
@ -28,7 +30,7 @@ const Authorization: React.FC = () => {
|
|||||||
backgroundColor: theme.palette.goldenDark.main,
|
backgroundColor: theme.palette.goldenDark.main,
|
||||||
borderRadius: "5px",
|
borderRadius: "5px",
|
||||||
margin: theme.spacing(0.5),
|
margin: theme.spacing(0.5),
|
||||||
cursor: "default"
|
cursor: "default",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
HUB
|
HUB
|
||||||
@ -36,7 +38,6 @@ const Authorization: React.FC = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export default Authorization;
|
export default Authorization;
|
@ -2,72 +2,99 @@ import * as React from "react";
|
|||||||
import { Box, Typography, Button } from "@mui/material";
|
import { Box, Typography, Button } from "@mui/material";
|
||||||
import { ThemeProvider } from "@mui/material";
|
import { ThemeProvider } from "@mui/material";
|
||||||
import theme from "../../theme";
|
import theme from "../../theme";
|
||||||
import CssBaseline from '@mui/material/CssBaseline';
|
import CssBaseline from "@mui/material/CssBaseline";
|
||||||
import Logo from "../Logo";
|
import Logo from "../Logo";
|
||||||
|
|
||||||
|
|
||||||
const Authorization: React.FC = () => {
|
const Authorization: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
height: "100%"
|
height: "100%",
|
||||||
}}>
|
}}
|
||||||
<Box sx={{
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
width: "100vw",
|
width: "100vw",
|
||||||
height: "100vh",
|
height: "100vh",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center"
|
alignItems: "center",
|
||||||
}}>
|
}}
|
||||||
<Box sx={{
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
width: "350px",
|
width: "350px",
|
||||||
height: "700px",
|
height: "700px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Logo />
|
<Logo />
|
||||||
|
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100px",
|
height: "100px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center"
|
alignItems: "center",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h6">
|
<Typography variant="h6">Все пользователи</Typography>
|
||||||
Все пользователи
|
</Box>
|
||||||
</Typography>
|
|
||||||
|
<Button variant="enter">ВОЙТИ</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h6">Общая статистика</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant = 'enter'
|
variant="enter"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.content.main,
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: theme.palette.menu.main,
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
ВОЙТИ
|
ВОЙТИ
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100px",
|
height: "100px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center"
|
alignItems: "center",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h6">
|
<Typography variant="h6">Шаблонизатор документов</Typography>
|
||||||
Общая статистика
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@ -75,25 +102,26 @@ const Authorization: React.FC = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.menu.main
|
backgroundColor: theme.palette.menu.main,
|
||||||
}
|
},
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
ВОЙТИ
|
ВОЙТИ
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100px",
|
height: "100px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center"
|
alignItems: "center",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h6">
|
<Typography variant="h6">Конструктор опросов</Typography>
|
||||||
Шаблонизатор документов
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@ -101,25 +129,26 @@ const Authorization: React.FC = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.menu.main
|
backgroundColor: theme.palette.menu.main,
|
||||||
}
|
},
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
ВОЙТИ
|
ВОЙТИ
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100px",
|
height: "100px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center"
|
alignItems: "center",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h6">
|
<Typography variant="h6">Сокращатель ссылок</Typography>
|
||||||
Конструктор опросов
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@ -127,46 +156,19 @@ const Authorization: React.FC = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.menu.main
|
backgroundColor: theme.palette.menu.main,
|
||||||
}
|
},
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
ВОЙТИ
|
ВОЙТИ
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100px",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center"
|
|
||||||
}}>
|
|
||||||
<Box>
|
|
||||||
<Typography variant="h6">
|
|
||||||
Сокращатель ссылок
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant = "enter"
|
|
||||||
sx={{
|
|
||||||
backgroundColor: theme.palette.content.main,
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: theme.palette.menu.main
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
ВОЙТИ
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export default Authorization;
|
export default Authorization;
|
@ -4,10 +4,9 @@ import {Box, IconButton, TextField, Tooltip, Typography, useMediaQuery, useTheme
|
|||||||
import ModeEditOutlineOutlinedIcon from "@mui/icons-material/ModeEditOutlineOutlined";
|
import ModeEditOutlineOutlinedIcon from "@mui/icons-material/ModeEditOutlineOutlined";
|
||||||
import { CustomPrivilege } from "@frontend/kitui";
|
import { CustomPrivilege } from "@frontend/kitui";
|
||||||
import { putPrivilege } from "@root/api/privilegies";
|
import { putPrivilege } from "@root/api/privilegies";
|
||||||
import SaveIcon from '@mui/icons-material/Save';
|
import SaveIcon from "@mui/icons-material/Save";
|
||||||
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
||||||
|
|
||||||
|
|
||||||
interface CardPrivilege {
|
interface CardPrivilege {
|
||||||
privilege: CustomPrivilege;
|
privilege: CustomPrivilege;
|
||||||
}
|
}
|
||||||
@ -26,7 +25,6 @@ export const СardPrivilege = ({ privilege }: CardPrivilege) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const putPrivileges = async () => {
|
const putPrivileges = async () => {
|
||||||
|
|
||||||
const [, putedPrivilegeError] = await putPrivilege({
|
const [, putedPrivilegeError] = await putPrivilege({
|
||||||
name: privilege.name,
|
name: privilege.name,
|
||||||
privilegeId: privilege.privilegeId,
|
privilegeId: privilege.privilegeId,
|
||||||
@ -123,7 +121,17 @@ export const СardPrivilege = ({ privilege }: CardPrivilege) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ maxWidth: "600px", width: "100%", display: "flex", alignItems: mobile ? "center" : undefined, justifyContent: "space-around", flexDirection: mobile ? "column" : "row", gap: "5px" }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: "600px",
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: mobile ? "center" : undefined,
|
||||||
|
justifyContent: "space-around",
|
||||||
|
flexDirection: mobile ? "column" : "row",
|
||||||
|
gap: "5px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{inputOpen ? (
|
{inputOpen ? (
|
||||||
<TextField
|
<TextField
|
||||||
type="number"
|
type="number"
|
||||||
@ -154,7 +162,7 @@ export const СardPrivilege = ({ privilege }: CardPrivilege) => {
|
|||||||
<IconButton onClick={handleSavePrice}>
|
<IconButton onClick={handleSavePrice}>
|
||||||
<SaveIcon />
|
<SaveIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
@ -8,7 +8,9 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
Select,
|
Select,
|
||||||
SelectChangeEvent,
|
SelectChangeEvent,
|
||||||
TextField, useMediaQuery, useTheme,
|
TextField,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { MOCK_DATA_USERS } from "@root/api/roles";
|
import { MOCK_DATA_USERS } from "@root/api/roles";
|
||||||
|
|
||||||
|
@ -44,10 +44,7 @@ export default function DeleteForm() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button onClick={() => rolesDelete(roleId)} sx={{ mr: "5px", bgcolor: "#fe9903", color: "white" }}>
|
||||||
onClick={() => rolesDelete(roleId)}
|
|
||||||
sx={{ mr: "5px", bgcolor: "#fe9903", color: "white" }}
|
|
||||||
>
|
|
||||||
Удалить
|
Удалить
|
||||||
</Button>
|
</Button>
|
||||||
<TextField
|
<TextField
|
||||||
@ -83,10 +80,7 @@ export default function DeleteForm() {
|
|||||||
>
|
>
|
||||||
{MOCK_DATA_USERS.map(({ name, id }) => (
|
{MOCK_DATA_USERS.map(({ name, id }) => (
|
||||||
<MenuItem key={id} value={name}>
|
<MenuItem key={id} value={name}>
|
||||||
<Checkbox
|
<Checkbox onClick={() => setRoleId(id)} checked={personName.indexOf(name) > -1} />
|
||||||
onClick={() => setRoleId(id)}
|
|
||||||
checked={personName.indexOf(name) > -1}
|
|
||||||
/>
|
|
||||||
<ListItemText primary={name} />
|
<ListItemText primary={name} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
|
@ -14,13 +14,9 @@ export default function ListPrivilege() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{privileges.map(privilege => (
|
{privileges.map((privilege) => (
|
||||||
<СardPrivilege
|
<СardPrivilege key={privilege._id} privilege={privilege} />
|
||||||
key={privilege._id}
|
))}
|
||||||
privilege={privilege}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
Typography,
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import { CustomWrapper } from "@root/kitUI/CustomWrapper";
|
import { CustomWrapper } from "@root/kitUI/CustomWrapper";
|
||||||
@ -22,8 +22,7 @@ export const SettingRoles = (): JSX.Element => {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const mobile = useMediaQuery(theme.breakpoints.down(600));
|
const mobile = useMediaQuery(theme.breakpoints.down(600));
|
||||||
return (
|
return (
|
||||||
<AccordionDetails sx={{ maxWidth: "890px",
|
<AccordionDetails sx={{ maxWidth: "890px", width: "100%" }}>
|
||||||
width: "100%", }}>
|
|
||||||
<CustomWrapper
|
<CustomWrapper
|
||||||
text="Роли"
|
text="Роли"
|
||||||
children={
|
children={
|
||||||
@ -69,7 +68,7 @@ export const SettingRoles = (): JSX.Element => {
|
|||||||
height: mobile ? undefined : "100px",
|
height: mobile ? undefined : "100px",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
flexDirection: mobile ? "column" : "row",
|
flexDirection: mobile ? "column" : "row",
|
||||||
gap: "5px"
|
gap: "5px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormCreateRoles />
|
<FormCreateRoles />
|
||||||
@ -118,7 +117,7 @@ export const SettingRoles = (): JSX.Element => {
|
|||||||
height: mobile ? undefined : "100px",
|
height: mobile ? undefined : "100px",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
flexDirection: mobile ? "column" : "row",
|
flexDirection: mobile ? "column" : "row",
|
||||||
gap: "5px"
|
gap: "5px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormDeleteRoles />
|
<FormDeleteRoles />
|
||||||
@ -129,8 +128,7 @@ export const SettingRoles = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PrivilegesWrapper text="Привилегии" sx={{ mt: "50px", maxWidth: "890px",
|
<PrivilegesWrapper text="Привилегии" sx={{ mt: "50px", maxWidth: "890px", width: "100%" }} />
|
||||||
width: "100%", }} />
|
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -56,7 +56,9 @@ export default function DiscountDataGrid({ selectedRows }: Props) {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
flexDirection: mobile ? "column" : undefined,
|
flexDirection: mobile ? "column" : undefined,
|
||||||
gap: "10px"}}>
|
gap: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Button onClick={() => changeData(false)}>Активировать</Button>
|
<Button onClick={() => changeData(false)}>Активировать</Button>
|
||||||
<Button onClick={() => changeData(true)}>Деактивировать</Button>
|
<Button onClick={() => changeData(true)}>Деактивировать</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -8,15 +8,13 @@ import {
|
|||||||
RadioGroup,
|
RadioGroup,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
Radio,
|
Radio,
|
||||||
InputLabel, TextField,
|
InputLabel,
|
||||||
|
TextField,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||||
import { SERVICE_LIST, ServiceType } from "@root/model/tariff";
|
import { SERVICE_LIST, ServiceType } from "@root/model/tariff";
|
||||||
import {
|
import { resetPrivilegeArray, usePrivilegeStore } from "@root/stores/privilegesStore";
|
||||||
resetPrivilegeArray,
|
|
||||||
usePrivilegeStore,
|
|
||||||
} from "@root/stores/privilegesStore";
|
|
||||||
import { addDiscount } from "@root/stores/discounts";
|
import { addDiscount } from "@root/stores/discounts";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { DiscountType, discountTypes } from "@root/model/discount";
|
import { DiscountType, discountTypes } from "@root/model/discount";
|
||||||
@ -26,15 +24,15 @@ import { Formik, Field, Form, FormikHelpers } from "formik";
|
|||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
|
|
||||||
interface Values {
|
interface Values {
|
||||||
discountNameField: string,
|
discountNameField: string;
|
||||||
discountDescriptionField: string,
|
discountDescriptionField: string;
|
||||||
discountFactorField: string,
|
discountFactorField: string;
|
||||||
serviceType: string,
|
serviceType: string;
|
||||||
discountType: DiscountType,
|
discountType: DiscountType;
|
||||||
purchasesAmountField: string,
|
purchasesAmountField: string;
|
||||||
cartPurchasesAmountField: string,
|
cartPurchasesAmountField: string;
|
||||||
discountMinValueField: string,
|
discountMinValueField: string;
|
||||||
privilegeIdField: string,
|
privilegeIdField: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CreateDiscount() {
|
export default function CreateDiscount() {
|
||||||
@ -53,25 +51,16 @@ export default function CreateDiscount() {
|
|||||||
cartPurchasesAmountField: "",
|
cartPurchasesAmountField: "",
|
||||||
discountMinValueField: "",
|
discountMinValueField: "",
|
||||||
privilegeIdField: "",
|
privilegeIdField: "",
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleCreateDiscount = async(
|
const handleCreateDiscount = async (values: Values, formikHelpers: FormikHelpers<Values>) => {
|
||||||
values: Values,
|
|
||||||
formikHelpers: FormikHelpers<Values>
|
|
||||||
) => {
|
|
||||||
const purchasesAmount = Number(parseFloat(values.purchasesAmountField.replace(",", "."))) * 100;
|
const purchasesAmount = Number(parseFloat(values.purchasesAmountField.replace(",", "."))) * 100;
|
||||||
|
|
||||||
const discountFactor =
|
const discountFactor = (100 - parseFloat(values.discountFactorField.replace(",", "."))) / 100;
|
||||||
(100 - parseFloat(values.discountFactorField.replace(",", "."))) / 100;
|
const cartPurchasesAmount = Number(parseFloat(values.cartPurchasesAmountField.replace(",", ".")) * 100);
|
||||||
const cartPurchasesAmount = Number(parseFloat(
|
const discountMinValue = Number(parseFloat(values.discountMinValueField.replace(",", ".")) * 100);
|
||||||
values.cartPurchasesAmountField.replace(",", ".")) * 100
|
|
||||||
);
|
|
||||||
const discountMinValue = Number(parseFloat(
|
|
||||||
values.discountMinValueField.replace(",", ".")) * 100
|
|
||||||
);
|
|
||||||
|
|
||||||
const [createdDiscountResponse, createdDiscountError] =
|
const [createdDiscountResponse, createdDiscountError] = await createDiscount({
|
||||||
await createDiscount({
|
|
||||||
cartPurchasesAmount,
|
cartPurchasesAmount,
|
||||||
discountFactor,
|
discountFactor,
|
||||||
discountMinValue,
|
discountMinValue,
|
||||||
@ -95,48 +84,52 @@ export default function CreateDiscount() {
|
|||||||
mutate("discounts");
|
mutate("discounts");
|
||||||
addDiscount(createdDiscountResponse);
|
addDiscount(createdDiscountResponse);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const validateFulledFields = (values: Values) => {
|
const validateFulledFields = (values: Values) => {
|
||||||
const errors = {} as any;
|
const errors = {} as any;
|
||||||
if (values.discountNameField.length === 0) {
|
if (values.discountNameField.length === 0) {
|
||||||
errors.discountNameField = 'Поле "Имя" пустое'
|
errors.discountNameField = 'Поле "Имя" пустое';
|
||||||
}
|
}
|
||||||
if (values.discountDescriptionField.length === 0) {
|
if (values.discountDescriptionField.length === 0) {
|
||||||
errors.discountDescriptionField = 'Поле "Описание" пустое'
|
errors.discountDescriptionField = 'Поле "Описание" пустое';
|
||||||
}
|
}
|
||||||
if (((100 - parseFloat(values.discountFactorField.replace(",", "."))) / 100) < 0) {
|
if ((100 - parseFloat(values.discountFactorField.replace(",", "."))) / 100 < 0) {
|
||||||
errors.discountFactorField = "Процент скидки не может быть больше 100"
|
errors.discountFactorField = "Процент скидки не может быть больше 100";
|
||||||
}
|
}
|
||||||
if (!isFinite(((100 - parseFloat(values.discountFactorField.replace(",", "."))) / 100))) {
|
if (!isFinite((100 - parseFloat(values.discountFactorField.replace(",", "."))) / 100)) {
|
||||||
errors.discountFactorField = 'Поле "Процент скидки" не число'
|
errors.discountFactorField = 'Поле "Процент скидки" не число';
|
||||||
}
|
}
|
||||||
if (values.discountType === "privilege" && !values.privilegeIdField) {
|
if (values.discountType === "privilege" && !values.privilegeIdField) {
|
||||||
errors.privilegeIdField = "Привилегия не выбрана"
|
errors.privilegeIdField = "Привилегия не выбрана";
|
||||||
}
|
}
|
||||||
if (values.discountType === "service" && !values.serviceType) {
|
if (values.discountType === "service" && !values.serviceType) {
|
||||||
errors.serviceType = "Сервис не выбран"
|
errors.serviceType = "Сервис не выбран";
|
||||||
}
|
}
|
||||||
if (values.discountType === "purchasesAmount" && !isFinite(parseFloat(values.purchasesAmountField.replace(",", ".")))) {
|
if (
|
||||||
errors.purchasesAmountField = 'Поле "Внесено больше" не число'
|
values.discountType === "purchasesAmount" &&
|
||||||
|
!isFinite(parseFloat(values.purchasesAmountField.replace(",", ".")))
|
||||||
|
) {
|
||||||
|
errors.purchasesAmountField = 'Поле "Внесено больше" не число';
|
||||||
}
|
}
|
||||||
if (values.discountType === "cartPurchasesAmount" && !isFinite(parseFloat(values.cartPurchasesAmountField.replace(",", ".")))) {
|
if (
|
||||||
errors.cartPurchasesAmountField = 'Поле "Объём в корзине" не число'
|
values.discountType === "cartPurchasesAmount" &&
|
||||||
|
!isFinite(parseFloat(values.cartPurchasesAmountField.replace(",", ".")))
|
||||||
|
) {
|
||||||
|
errors.cartPurchasesAmountField = 'Поле "Объём в корзине" не число';
|
||||||
}
|
}
|
||||||
if (values.discountType === ("service" || "privilege") && !isFinite(parseFloat(values.discountMinValueField.replace(",", ".")))) {
|
if (
|
||||||
errors.discountMinValueField = 'Поле "Минимальное значение" не число'
|
values.discountType === ("service" || "privilege") &&
|
||||||
|
!isFinite(parseFloat(values.discountMinValueField.replace(",", ".")))
|
||||||
|
) {
|
||||||
|
errors.discountMinValueField = 'Поле "Минимальное значение" не число';
|
||||||
}
|
}
|
||||||
console.error(errors)
|
console.error(errors);
|
||||||
return errors;
|
return errors;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Formik initialValues={initialValues} onSubmit={handleCreateDiscount} validate={validateFulledFields}>
|
||||||
initialValues={initialValues}
|
|
||||||
onSubmit={handleCreateDiscount}
|
|
||||||
validate={validateFulledFields}
|
|
||||||
>
|
|
||||||
{(props) => (
|
{(props) => (
|
||||||
<Form style={{ width: "100%", display: "flex", justifyContent: "center" }}>
|
<Form style={{ width: "100%", display: "flex", justifyContent: "center" }}>
|
||||||
<Box
|
<Box
|
||||||
@ -160,20 +153,18 @@ export default function CreateDiscount() {
|
|||||||
name="discountNameField"
|
name="discountNameField"
|
||||||
error={props.touched.discountNameField && !!props.errors.discountNameField}
|
error={props.touched.discountNameField && !!props.errors.discountNameField}
|
||||||
helperText={
|
helperText={
|
||||||
<Typography sx={{fontSize: "12px", width: "200px"}}>
|
<Typography sx={{ fontSize: "12px", width: "200px" }}>{props.errors.discountNameField}</Typography>
|
||||||
{props.errors.discountNameField}
|
|
||||||
</Typography>
|
|
||||||
}
|
}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
style: {
|
style: {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Field
|
<Field
|
||||||
@ -193,12 +184,12 @@ export default function CreateDiscount() {
|
|||||||
style: {
|
style: {
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
style: {
|
style: {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Typography
|
<Typography
|
||||||
@ -221,20 +212,18 @@ export default function CreateDiscount() {
|
|||||||
error={props.touched.discountFactorField && !!props.errors.discountFactorField}
|
error={props.touched.discountFactorField && !!props.errors.discountFactorField}
|
||||||
value={props.values.discountFactorField}
|
value={props.values.discountFactorField}
|
||||||
helperText={
|
helperText={
|
||||||
<Typography sx={{fontSize: "12px", width: "200px"}}>
|
<Typography sx={{ fontSize: "12px", width: "200px" }}>{props.errors.discountFactorField}</Typography>
|
||||||
{props.errors.discountFactorField}
|
|
||||||
</Typography>
|
|
||||||
}
|
}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
style: {
|
style: {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@ -254,9 +243,7 @@ export default function CreateDiscount() {
|
|||||||
aria-labelledby="discount-type"
|
aria-labelledby="discount-type"
|
||||||
name="discountType"
|
name="discountType"
|
||||||
value={props.values.discountType}
|
value={props.values.discountType}
|
||||||
onChange={(
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
event: React.ChangeEvent<HTMLInputElement>
|
|
||||||
) => {
|
|
||||||
props.setFieldValue("discountType", event.target.value as DiscountType);
|
props.setFieldValue("discountType", event.target.value as DiscountType);
|
||||||
}}
|
}}
|
||||||
onBlur={props.handleBlur}
|
onBlur={props.handleBlur}
|
||||||
@ -279,7 +266,7 @@ export default function CreateDiscount() {
|
|||||||
error={props.touched.purchasesAmountField && !!props.errors.purchasesAmountField}
|
error={props.touched.purchasesAmountField && !!props.errors.purchasesAmountField}
|
||||||
label="Внесено больше"
|
label="Внесено больше"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
props.setFieldValue("purchasesAmountField", e.target.value.replace(/[^\d]/g, ''))
|
props.setFieldValue("purchasesAmountField", e.target.value.replace(/[^\d]/g, ""));
|
||||||
}}
|
}}
|
||||||
value={props.values.purchasesAmountField}
|
value={props.values.purchasesAmountField}
|
||||||
onBlur={props.handleBlur}
|
onBlur={props.handleBlur}
|
||||||
@ -287,20 +274,18 @@ export default function CreateDiscount() {
|
|||||||
marginTop: "15px",
|
marginTop: "15px",
|
||||||
}}
|
}}
|
||||||
helperText={
|
helperText={
|
||||||
<Typography sx={{fontSize: "12px", width: "200px"}}>
|
<Typography sx={{ fontSize: "12px", width: "200px" }}>{props.errors.purchasesAmountField}</Typography>
|
||||||
{props.errors.purchasesAmountField}
|
|
||||||
</Typography>
|
|
||||||
}
|
}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
style: {
|
style: {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -312,7 +297,7 @@ export default function CreateDiscount() {
|
|||||||
variant="filled"
|
variant="filled"
|
||||||
error={props.touched.cartPurchasesAmountField && !!props.errors.cartPurchasesAmountField}
|
error={props.touched.cartPurchasesAmountField && !!props.errors.cartPurchasesAmountField}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
props.setFieldValue("cartPurchasesAmountField", e.target.value.replace(/[^\d]/g, ''))
|
props.setFieldValue("cartPurchasesAmountField", e.target.value.replace(/[^\d]/g, ""));
|
||||||
}}
|
}}
|
||||||
value={props.values.cartPurchasesAmountField}
|
value={props.values.cartPurchasesAmountField}
|
||||||
onBlur={props.handleBlur}
|
onBlur={props.handleBlur}
|
||||||
@ -328,12 +313,12 @@ export default function CreateDiscount() {
|
|||||||
style: {
|
style: {
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
style: {
|
style: {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -372,7 +357,7 @@ export default function CreateDiscount() {
|
|||||||
onBlur={props.handleBlur}
|
onBlur={props.handleBlur}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
props.setFieldValue("discountMinValueField", e.target.value.replace(/[^\d]/g, ''))
|
props.setFieldValue("discountMinValueField", e.target.value.replace(/[^\d]/g, ""));
|
||||||
}}
|
}}
|
||||||
value={props.values.discountMinValueField}
|
value={props.values.discountMinValueField}
|
||||||
error={props.touched.discountMinValueField && !!props.errors.discountMinValueField}
|
error={props.touched.discountMinValueField && !!props.errors.discountMinValueField}
|
||||||
@ -388,12 +373,12 @@ export default function CreateDiscount() {
|
|||||||
style: {
|
style: {
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
style: {
|
style: {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@ -458,7 +443,7 @@ export default function CreateDiscount() {
|
|||||||
onBlur={props.handleBlur}
|
onBlur={props.handleBlur}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
props.setFieldValue("discountMinValueField", e.target.value.replace(/[^\d]/g, ''))
|
props.setFieldValue("discountMinValueField", e.target.value.replace(/[^\d]/g, ""));
|
||||||
}}
|
}}
|
||||||
value={props.values.discountMinValueField}
|
value={props.values.discountMinValueField}
|
||||||
error={props.touched.discountMinValueField && !!props.errors.discountMinValueField}
|
error={props.touched.discountMinValueField && !!props.errors.discountMinValueField}
|
||||||
@ -474,12 +459,12 @@ export default function CreateDiscount() {
|
|||||||
style: {
|
style: {
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
style: {
|
style: {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@ -496,7 +481,7 @@ export default function CreateDiscount() {
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
type='submit'
|
type="submit"
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.menu.main,
|
backgroundColor: theme.palette.menu.main,
|
||||||
height: "52px",
|
height: "52px",
|
||||||
@ -514,6 +499,5 @@ export default function CreateDiscount() {
|
|||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Box, IconButton, useTheme, Tooltip } from "@mui/material";
|
import { Box, IconButton, useTheme, Tooltip } from "@mui/material";
|
||||||
import {
|
import { DataGrid, GridColDef, GridRowsProp, GridToolbar } from "@mui/x-data-grid";
|
||||||
DataGrid,
|
|
||||||
GridColDef,
|
|
||||||
GridRowsProp,
|
|
||||||
GridToolbar,
|
|
||||||
} from "@mui/x-data-grid";
|
|
||||||
import {
|
import {
|
||||||
openEditDiscountDialog,
|
openEditDiscountDialog,
|
||||||
setSelectedDiscountIds,
|
setSelectedDiscountIds,
|
||||||
@ -33,9 +28,7 @@ const columns: GridColDef[] = [
|
|||||||
headerName: "Название скидки",
|
headerName: "Название скидки",
|
||||||
width: 150,
|
width: 150,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
renderCell: ({ row, value }) => (
|
renderCell: ({ row, value }) => <Box color={row.deleted && "#ff4545"}>{value}</Box>,
|
||||||
<Box color={row.deleted && "#ff4545"}>{value}</Box>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "description",
|
field: "description",
|
||||||
@ -108,21 +101,13 @@ const columns: GridColDef[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const layerTranslate = ["", "Товар", "Сервис", "корзина", "лояльность"];
|
const layerTranslate = ["", "Товар", "Сервис", "корзина", "лояльность"];
|
||||||
const layerValue = [
|
const layerValue = ["", "Term", "PriceFrom", "CartPurchasesAmount", "PurchasesAmount"];
|
||||||
"",
|
|
||||||
"Term",
|
|
||||||
"PriceFrom",
|
|
||||||
"CartPurchasesAmount",
|
|
||||||
"PurchasesAmount",
|
|
||||||
];
|
|
||||||
interface Props {
|
interface Props {
|
||||||
selectedRowsHC: (array: GridSelectionModel) => void;
|
selectedRowsHC: (array: GridSelectionModel) => void;
|
||||||
}
|
}
|
||||||
export default function DiscountDataGrid({ selectedRowsHC }: Props) {
|
export default function DiscountDataGrid({ selectedRowsHC }: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const selectedDiscountIds = useDiscountStore(
|
const selectedDiscountIds = useDiscountStore((state) => state.selectedDiscountIds);
|
||||||
(state) => state.selectedDiscountIds
|
|
||||||
);
|
|
||||||
const realDiscounts = useDiscountStore((state) => state.discounts);
|
const realDiscounts = useDiscountStore((state) => state.discounts);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -138,26 +123,19 @@ export default function DiscountDataGrid({ selectedRowsHC }: Props) {
|
|||||||
description: discount.Description,
|
description: discount.Description,
|
||||||
conditionType: layerTranslate[discount.Layer],
|
conditionType: layerTranslate[discount.Layer],
|
||||||
factor: formatDiscountFactor(discount.Target.Factor),
|
factor: formatDiscountFactor(discount.Target.Factor),
|
||||||
value: (discount.Layer === 1) ?
|
value:
|
||||||
discount.Condition[
|
discount.Layer === 1
|
||||||
layerValue[discount.Layer] as keyof typeof discount.Condition
|
? discount.Condition[layerValue[discount.Layer] as keyof typeof discount.Condition]
|
||||||
]: Number(discount.Condition[
|
: Number(discount.Condition[layerValue[discount.Layer] as keyof typeof discount.Condition]) / 100,
|
||||||
layerValue[discount.Layer] as keyof typeof discount.Condition
|
|
||||||
])/100,
|
|
||||||
active: discount.Deprecated ? "🚫" : "✅",
|
active: discount.Deprecated ? "🚫" : "✅",
|
||||||
deleted: discount.Audit.Deleted,
|
deleted: discount.Audit.Deleted,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box sx={{ width: "100%", marginTop: "55px", p: "16px", maxWidth: "1000px" }}>
|
||||||
sx={{ width: "100%", marginTop: "55px", p: "16px", maxWidth: "1000px" }}
|
|
||||||
>
|
|
||||||
<Tooltip title="обновить список привилегий">
|
<Tooltip title="обновить список привилегий">
|
||||||
<IconButton
|
<IconButton onClick={requestDiscounts} style={{ display: "block", margin: "0 auto" }}>
|
||||||
onClick={requestDiscounts}
|
|
||||||
style={{ display: "block", margin: "0 auto" }}
|
|
||||||
>
|
|
||||||
<AutorenewIcon sx={{ color: "white" }} />
|
<AutorenewIcon sx={{ color: "white" }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -8,13 +8,12 @@ import ControlPanel from "./ControlPanel";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { GridSelectionModel } from "@mui/x-data-grid";
|
import { GridSelectionModel } from "@mui/x-data-grid";
|
||||||
|
|
||||||
|
|
||||||
const DiscountManagement: React.FC = () => {
|
const DiscountManagement: React.FC = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [selectedRows, setSelectedRows] = useState<GridSelectionModel>([])
|
const [selectedRows, setSelectedRows] = useState<GridSelectionModel>([]);
|
||||||
const selectedRowsHC = (array: GridSelectionModel) => {
|
const selectedRowsHC = (array: GridSelectionModel) => {
|
||||||
setSelectedRows(array)
|
setSelectedRows(array);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
@ -27,19 +26,17 @@ const DiscountManagement: React.FC = () => {
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
СКИДКИ
|
СКИДКИ
|
||||||
</Typography>
|
</Typography>
|
||||||
<CreateDiscount />
|
<CreateDiscount />
|
||||||
<DiscountDataGrid
|
<DiscountDataGrid selectedRowsHC={selectedRowsHC} />
|
||||||
selectedRowsHC={selectedRowsHC}
|
|
||||||
/>
|
|
||||||
<EditDiscountDialog />
|
<EditDiscountDialog />
|
||||||
<ControlPanel selectedRows={selectedRows} />
|
<ControlPanel selectedRows={selectedRows} />
|
||||||
</LocalizationProvider>
|
</LocalizationProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default DiscountManagement;
|
export default DiscountManagement;
|
||||||
|
@ -18,15 +18,8 @@ import { patchDiscount } from "@root/api/discounts";
|
|||||||
import { CustomTextField } from "@root/kitUI/CustomTextField";
|
import { CustomTextField } from "@root/kitUI/CustomTextField";
|
||||||
import { DiscountType, discountTypes } from "@root/model/discount";
|
import { DiscountType, discountTypes } from "@root/model/discount";
|
||||||
import { ServiceType, SERVICE_LIST } from "@root/model/tariff";
|
import { ServiceType, SERVICE_LIST } from "@root/model/tariff";
|
||||||
import {
|
import { closeEditDiscountDialog, updateDiscount, useDiscountStore } from "@root/stores/discounts";
|
||||||
closeEditDiscountDialog,
|
import { resetPrivilegeArray, usePrivilegeStore } from "@root/stores/privilegesStore";
|
||||||
updateDiscount,
|
|
||||||
useDiscountStore,
|
|
||||||
} from "@root/stores/discounts";
|
|
||||||
import {
|
|
||||||
resetPrivilegeArray,
|
|
||||||
usePrivilegeStore,
|
|
||||||
} from "@root/stores/privilegesStore";
|
|
||||||
import { getDiscountTypeFromLayer } from "@root/utils/discount";
|
import { getDiscountTypeFromLayer } from "@root/utils/discount";
|
||||||
import usePrivileges from "@root/utils/hooks/usePrivileges";
|
import usePrivileges from "@root/utils/hooks/usePrivileges";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
@ -39,18 +32,14 @@ export default function EditDiscountDialog() {
|
|||||||
const discounts = useDiscountStore((state) => state.discounts);
|
const discounts = useDiscountStore((state) => state.discounts);
|
||||||
const privileges = usePrivilegeStore((state) => state.privileges);
|
const privileges = usePrivilegeStore((state) => state.privileges);
|
||||||
const [serviceType, setServiceType] = useState<string>("templategen");
|
const [serviceType, setServiceType] = useState<string>("templategen");
|
||||||
const [discountType, setDiscountType] =
|
const [discountType, setDiscountType] = useState<DiscountType>("purchasesAmount");
|
||||||
useState<DiscountType>("purchasesAmount");
|
|
||||||
const [discountNameField, setDiscountNameField] = useState<string>("");
|
const [discountNameField, setDiscountNameField] = useState<string>("");
|
||||||
const [discountDescriptionField, setDiscountDescriptionField] =
|
const [discountDescriptionField, setDiscountDescriptionField] = useState<string>("");
|
||||||
useState<string>("");
|
|
||||||
const [privilegeIdField, setPrivilegeIdField] = useState<string | "">("");
|
const [privilegeIdField, setPrivilegeIdField] = useState<string | "">("");
|
||||||
const [discountFactorField, setDiscountFactorField] = useState<string>("0");
|
const [discountFactorField, setDiscountFactorField] = useState<string>("0");
|
||||||
const [purchasesAmountField, setPurchasesAmountField] = useState<string>("0");
|
const [purchasesAmountField, setPurchasesAmountField] = useState<string>("0");
|
||||||
const [cartPurchasesAmountField, setCartPurchasesAmountField] =
|
const [cartPurchasesAmountField, setCartPurchasesAmountField] = useState<string>("0");
|
||||||
useState<string>("0");
|
const [discountMinValueField, setDiscountMinValueField] = useState<string>("0");
|
||||||
const [discountMinValueField, setDiscountMinValueField] =
|
|
||||||
useState<string>("0");
|
|
||||||
|
|
||||||
const discount = discounts.find((discount) => discount.ID === editDiscountId);
|
const discount = discounts.find((discount) => discount.ID === editDiscountId);
|
||||||
|
|
||||||
@ -67,17 +56,13 @@ export default function EditDiscountDialog() {
|
|||||||
setPrivilegeIdField(discount.Condition.Product ?? "");
|
setPrivilegeIdField(discount.Condition.Product ?? "");
|
||||||
setDiscountFactorField(((1 - discount.Target.Factor) * 100).toFixed(2));
|
setDiscountFactorField(((1 - discount.Target.Factor) * 100).toFixed(2));
|
||||||
setPurchasesAmountField(discount.Condition.PurchasesAmount ?? "");
|
setPurchasesAmountField(discount.Condition.PurchasesAmount ?? "");
|
||||||
setCartPurchasesAmountField(
|
setCartPurchasesAmountField(discount.Condition.CartPurchasesAmount ?? "");
|
||||||
discount.Condition.CartPurchasesAmount ?? ""
|
|
||||||
);
|
|
||||||
setDiscountMinValueField(discount.Condition.PriceFrom ?? "");
|
setDiscountMinValueField(discount.Condition.PriceFrom ?? "");
|
||||||
},
|
},
|
||||||
[discount]
|
[discount]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDiscountTypeChange = (
|
const handleDiscountTypeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
event: React.ChangeEvent<HTMLInputElement>
|
|
||||||
) => {
|
|
||||||
setDiscountType(event.target.value as DiscountType);
|
setDiscountType(event.target.value as DiscountType);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,34 +74,20 @@ export default function EditDiscountDialog() {
|
|||||||
if (!discount) return;
|
if (!discount) return;
|
||||||
|
|
||||||
const purchasesAmount = parseFloat(purchasesAmountField.replace(",", "."));
|
const purchasesAmount = parseFloat(purchasesAmountField.replace(",", "."));
|
||||||
const discountFactor =
|
const discountFactor = (100 - parseFloat(discountFactorField.replace(",", "."))) / 100;
|
||||||
(100 - parseFloat(discountFactorField.replace(",", "."))) / 100;
|
const cartPurchasesAmount = parseFloat(cartPurchasesAmountField.replace(",", "."));
|
||||||
const cartPurchasesAmount = parseFloat(
|
const discountMinValue = parseFloat(discountMinValueField.replace(",", "."));
|
||||||
cartPurchasesAmountField.replace(",", ".")
|
|
||||||
);
|
|
||||||
const discountMinValue = parseFloat(
|
|
||||||
discountMinValueField.replace(",", ".")
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isFinite(purchasesAmount))
|
if (!isFinite(purchasesAmount)) return enqueueSnackbar("Поле purchasesAmount не число");
|
||||||
return enqueueSnackbar("Поле purchasesAmount не число");
|
if (!isFinite(discountFactor)) return enqueueSnackbar("Поле discountFactor не число");
|
||||||
if (!isFinite(discountFactor))
|
if (!isFinite(cartPurchasesAmount)) return enqueueSnackbar("Поле cartPurchasesAmount не число");
|
||||||
return enqueueSnackbar("Поле discountFactor не число");
|
if (!isFinite(discountMinValue)) return enqueueSnackbar("Поле discountMinValue не число");
|
||||||
if (!isFinite(cartPurchasesAmount))
|
if (discountType === "privilege" && !privilegeIdField) return enqueueSnackbar("Привилегия не выбрана");
|
||||||
return enqueueSnackbar("Поле cartPurchasesAmount не число");
|
|
||||||
if (!isFinite(discountMinValue))
|
|
||||||
return enqueueSnackbar("Поле discountMinValue не число");
|
|
||||||
if (discountType === "privilege" && !privilegeIdField)
|
|
||||||
return enqueueSnackbar("Привилегия не выбрана");
|
|
||||||
if (!discountNameField) return enqueueSnackbar('Поле "Имя" пустое');
|
if (!discountNameField) return enqueueSnackbar('Поле "Имя" пустое');
|
||||||
if (!discountDescriptionField)
|
if (!discountDescriptionField) return enqueueSnackbar('Поле "Описание" пустое');
|
||||||
return enqueueSnackbar('Поле "Описание" пустое');
|
if (discountFactor < 0) return enqueueSnackbar("Процент скидки не может быть больше 100");
|
||||||
if (discountFactor < 0)
|
|
||||||
return enqueueSnackbar("Процент скидки не может быть больше 100");
|
|
||||||
|
|
||||||
const [patchedDiscountResponse, patchedDiscountError] = await patchDiscount(
|
const [patchedDiscountResponse, patchedDiscountError] = await patchDiscount(discount.ID, {
|
||||||
discount.ID,
|
|
||||||
{
|
|
||||||
cartPurchasesAmount,
|
cartPurchasesAmount,
|
||||||
discountFactor,
|
discountFactor,
|
||||||
discountMinValue,
|
discountMinValue,
|
||||||
@ -128,8 +99,7 @@ export default function EditDiscountDialog() {
|
|||||||
serviceType,
|
serviceType,
|
||||||
discountType,
|
discountType,
|
||||||
privilegeId: privilegeIdField,
|
privilegeId: privilegeIdField,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (patchedDiscountError) {
|
if (patchedDiscountError) {
|
||||||
console.error("Error patching discount", patchedDiscountError);
|
console.error("Error patching discount", patchedDiscountError);
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Typography } from "@mui/material";
|
import { Typography } from "@mui/material";
|
||||||
import Table from '@mui/material/Table';
|
import Table from "@mui/material/Table";
|
||||||
import TableHead from '@mui/material/TableHead';
|
import TableHead from "@mui/material/TableHead";
|
||||||
import TableBody from '@mui/material/TableBody';
|
import TableBody from "@mui/material/TableBody";
|
||||||
import TableCell from '@mui/material/TableCell';
|
import TableCell from "@mui/material/TableCell";
|
||||||
import TableRow from '@mui/material/TableRow';
|
import TableRow from "@mui/material/TableRow";
|
||||||
import theme from "../../../theme";
|
import theme from "../../../theme";
|
||||||
|
|
||||||
|
|
||||||
const Users: React.FC = () => {
|
const Users: React.FC = () => {
|
||||||
const [selectedValue, setSelectedValue] = React.useState('a');
|
const [selectedValue, setSelectedValue] = React.useState("a");
|
||||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setSelectedValue(event.target.value);
|
setSelectedValue(event.target.value);
|
||||||
};
|
};
|
||||||
@ -28,29 +27,36 @@ const Users: React.FC = () => {
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Юридические лица
|
Юридические лица
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Table sx={{
|
<Table
|
||||||
|
sx={{
|
||||||
width: "90%",
|
width: "90%",
|
||||||
border: "2px solid",
|
border: "2px solid",
|
||||||
borderColor: theme.palette.grayLight.main,
|
borderColor: theme.palette.grayLight.main,
|
||||||
marginTop: "35px",
|
marginTop: "35px",
|
||||||
}} aria-label="simple table">
|
}}
|
||||||
|
aria-label="simple table"
|
||||||
|
>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow sx={{
|
<TableRow
|
||||||
|
sx={{
|
||||||
borderBottom: "2px solid",
|
borderBottom: "2px solid",
|
||||||
borderColor: theme.palette.grayLight.main,
|
borderColor: theme.palette.grayLight.main,
|
||||||
height: "100px"
|
height: "100px",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<TableCell sx={{ textAlign: "center" }}>
|
<TableCell sx={{ textAlign: "center" }}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h4"
|
variant="h4"
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
ID
|
ID
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@ -59,7 +65,8 @@ const Users: React.FC = () => {
|
|||||||
variant="h4"
|
variant="h4"
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Дата / время регистрации
|
Дата / время регистрации
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@ -67,67 +74,88 @@ const Users: React.FC = () => {
|
|||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<TableRow sx={{
|
<TableRow
|
||||||
|
sx={{
|
||||||
borderBottom: "2px solid",
|
borderBottom: "2px solid",
|
||||||
borderColor: theme.palette.grayLight.main,
|
borderColor: theme.palette.grayLight.main,
|
||||||
height: "100px",
|
height: "100px",
|
||||||
cursor: "pointer"
|
cursor: "pointer",
|
||||||
}} onClick={ () => navigate("/modalEntities") }>
|
}}
|
||||||
|
onClick={() => navigate("/modalEntities")}
|
||||||
|
>
|
||||||
<TableCell sx={{ textAlign: "center" }}>
|
<TableCell sx={{ textAlign: "center" }}>
|
||||||
<Typography sx={{
|
<Typography
|
||||||
color: theme.palette.secondary.main
|
sx={{
|
||||||
}}>
|
color: theme.palette.secondary.main,
|
||||||
|
}}
|
||||||
|
>
|
||||||
1
|
1
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell sx={{ textAlign: "center" }}>
|
<TableCell sx={{ textAlign: "center" }}>
|
||||||
<Typography sx={{
|
<Typography
|
||||||
color: theme.palette.secondary.main
|
sx={{
|
||||||
}}>
|
color: theme.palette.secondary.main,
|
||||||
|
}}
|
||||||
|
>
|
||||||
2022
|
2022
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
||||||
<TableRow sx={{
|
<TableRow
|
||||||
|
sx={{
|
||||||
borderBottom: "2px solid",
|
borderBottom: "2px solid",
|
||||||
borderColor: theme.palette.grayLight.main,
|
borderColor: theme.palette.grayLight.main,
|
||||||
height: "100px",
|
height: "100px",
|
||||||
cursor: "pointer"
|
cursor: "pointer",
|
||||||
}} onClick={ () => navigate("/modalEntities") }>
|
}}
|
||||||
|
onClick={() => navigate("/modalEntities")}
|
||||||
|
>
|
||||||
<TableCell sx={{ textAlign: "center" }}>
|
<TableCell sx={{ textAlign: "center" }}>
|
||||||
<Typography sx={{
|
<Typography
|
||||||
color: theme.palette.secondary.main
|
sx={{
|
||||||
}}>
|
color: theme.palette.secondary.main,
|
||||||
|
}}
|
||||||
|
>
|
||||||
2
|
2
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell sx={{ textAlign: "center" }}>
|
<TableCell sx={{ textAlign: "center" }}>
|
||||||
<Typography sx={{
|
<Typography
|
||||||
color: theme.palette.secondary.main
|
sx={{
|
||||||
}}>
|
color: theme.palette.secondary.main,
|
||||||
|
}}
|
||||||
|
>
|
||||||
2021
|
2021
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
||||||
<TableRow sx={{
|
<TableRow
|
||||||
|
sx={{
|
||||||
borderBottom: "1px solid",
|
borderBottom: "1px solid",
|
||||||
border: theme.palette.secondary.main,
|
border: theme.palette.secondary.main,
|
||||||
height: "100px",
|
height: "100px",
|
||||||
cursor: "pointer"
|
cursor: "pointer",
|
||||||
}} onClick={ () => navigate("/modalEntities") }>
|
}}
|
||||||
|
onClick={() => navigate("/modalEntities")}
|
||||||
|
>
|
||||||
<TableCell sx={{ textAlign: "center" }}>
|
<TableCell sx={{ textAlign: "center" }}>
|
||||||
<Typography sx={{
|
<Typography
|
||||||
color: theme.palette.secondary.main
|
sx={{
|
||||||
}}>
|
color: theme.palette.secondary.main,
|
||||||
|
}}
|
||||||
|
>
|
||||||
3
|
3
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell sx={{ textAlign: "center" }}>
|
<TableCell sx={{ textAlign: "center" }}>
|
||||||
<Typography sx={{
|
<Typography
|
||||||
color: theme.palette.secondary.main
|
sx={{
|
||||||
}}>
|
color: theme.palette.secondary.main,
|
||||||
|
}}
|
||||||
|
>
|
||||||
2020
|
2020
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@ -136,7 +164,6 @@ const Users: React.FC = () => {
|
|||||||
</Table>
|
</Table>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export default Users;
|
export default Users;
|
@ -1,13 +1,4 @@
|
|||||||
import {
|
import { Button, FormControlLabel, MenuItem, Radio, RadioGroup, Select, TextField, Typography } from "@mui/material";
|
||||||
Button,
|
|
||||||
FormControlLabel,
|
|
||||||
MenuItem,
|
|
||||||
Radio,
|
|
||||||
RadioGroup,
|
|
||||||
Select,
|
|
||||||
TextField,
|
|
||||||
Typography,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { DesktopDatePicker } from "@mui/x-date-pickers/DesktopDatePicker";
|
import { DesktopDatePicker } from "@mui/x-date-pickers/DesktopDatePicker";
|
||||||
import { Field, Form, Formik } from "formik";
|
import { Field, Form, Formik } from "formik";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
@ -75,16 +66,11 @@ export const CreatePromocodeForm = ({ createPromocode }: Props) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const submitForm = (values: FormValues) => {
|
const submitForm = (values: FormValues) => {
|
||||||
const currentPrivilege = privileges.find(
|
const currentPrivilege = privileges.find((item) => item.privilegeId === values.privilegeId);
|
||||||
(item) => item.privilegeId === values.privilegeId
|
|
||||||
);
|
|
||||||
|
|
||||||
const body = { ...values };
|
const body = { ...values };
|
||||||
|
|
||||||
if (
|
if ((body.layer === 1 && bonusType === "discount") || bonusType === "privilege") {
|
||||||
(body.layer === 1 && bonusType === "discount") ||
|
|
||||||
bonusType === "privilege"
|
|
||||||
) {
|
|
||||||
if (currentPrivilege === undefined) {
|
if (currentPrivilege === undefined) {
|
||||||
enqueueSnackbar("Привилегия не выбрана");
|
enqueueSnackbar("Привилегия не выбрана");
|
||||||
|
|
||||||
@ -138,24 +124,9 @@ export const CreatePromocodeForm = ({ createPromocode }: Props) => {
|
|||||||
padding: "0 10px",
|
padding: "0 10px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CustomTextField
|
<CustomTextField name="codeword" label="Кодовое слово" required onChange={handleChange} />
|
||||||
name="codeword"
|
<CustomTextField name="description" label="Описание" required onChange={handleChange} />
|
||||||
label="Кодовое слово"
|
<CustomTextField name="greetings" label="Приветственное сообщение" required onChange={handleChange} />
|
||||||
required
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
<CustomTextField
|
|
||||||
name="description"
|
|
||||||
label="Описание"
|
|
||||||
required
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
<CustomTextField
|
|
||||||
name="greetings"
|
|
||||||
label="Приветственное сообщение"
|
|
||||||
required
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="h4"
|
variant="h4"
|
||||||
sx={{
|
sx={{
|
||||||
@ -190,12 +161,7 @@ export const CreatePromocodeForm = ({ createPromocode }: Props) => {
|
|||||||
name="activationCount"
|
name="activationCount"
|
||||||
label="Количество активаций промокода"
|
label="Количество активаций промокода"
|
||||||
required
|
required
|
||||||
onChange={({ target }) =>
|
onChange={({ target }) => setFieldValue("activationCount", Number(target.value.replace(/\D/g, "")))}
|
||||||
setFieldValue(
|
|
||||||
"activationCount",
|
|
||||||
Number(target.value.replace(/\D/g, ""))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
row
|
row
|
||||||
@ -207,16 +173,8 @@ export const CreatePromocodeForm = ({ createPromocode }: Props) => {
|
|||||||
}}
|
}}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel value="discount" control={<Radio color="secondary" />} label="Скидка" />
|
||||||
value="discount"
|
<FormControlLabel value="privilege" control={<Radio color="secondary" />} label="Привилегия" />
|
||||||
control={<Radio color="secondary" />}
|
|
||||||
label="Скидка"
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
value="privilege"
|
|
||||||
control={<Radio color="secondary" />}
|
|
||||||
label="Привилегия"
|
|
||||||
/>
|
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
{bonusType === "discount" && (
|
{bonusType === "discount" && (
|
||||||
<>
|
<>
|
||||||
@ -231,26 +189,15 @@ export const CreatePromocodeForm = ({ createPromocode }: Props) => {
|
|||||||
}}
|
}}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel value="1" control={<Radio color="secondary" />} label="Привилегия" />
|
||||||
value="1"
|
<FormControlLabel value="2" control={<Radio color="secondary" />} label="Сервис" />
|
||||||
control={<Radio color="secondary" />}
|
|
||||||
label="Привилегия"
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
value="2"
|
|
||||||
control={<Radio color="secondary" />}
|
|
||||||
label="Сервис"
|
|
||||||
/>
|
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
<CustomTextField
|
<CustomTextField
|
||||||
name="factor"
|
name="factor"
|
||||||
label="Процент скидки"
|
label="Процент скидки"
|
||||||
required
|
required
|
||||||
onChange={({ target }) => {
|
onChange={({ target }) => {
|
||||||
setFieldValue(
|
setFieldValue("factor", Number(target.value.replace(/\D/g, "")));
|
||||||
"factor",
|
|
||||||
Number(target.value.replace(/\D/g, ""))
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Typography
|
<Typography
|
||||||
@ -319,12 +266,7 @@ export const CreatePromocodeForm = ({ createPromocode }: Props) => {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
name="threshold"
|
name="threshold"
|
||||||
label="При каком значении применяется скидка"
|
label="При каком значении применяется скидка"
|
||||||
onChange={({ target }) =>
|
onChange={({ target }) => setFieldValue("threshold", Number(target.value.replace(/\D/g, "")))}
|
||||||
setFieldValue(
|
|
||||||
"threshold",
|
|
||||||
Number(target.value.replace(/\D/g, ""))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -366,12 +308,7 @@ export const CreatePromocodeForm = ({ createPromocode }: Props) => {
|
|||||||
name="amount"
|
name="amount"
|
||||||
label="Количество"
|
label="Количество"
|
||||||
required
|
required
|
||||||
onChange={({ target }) =>
|
onChange={({ target }) => setFieldValue("amount", Number(target.value.replace(/\D/g, "")))}
|
||||||
setFieldValue(
|
|
||||||
"amount",
|
|
||||||
Number(target.value.replace(/\D/g, ""))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -403,12 +340,7 @@ type CustomTextFieldProps = {
|
|||||||
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CustomTextField = ({
|
const CustomTextField = ({ name, label, required = false, onChange }: CustomTextFieldProps) => (
|
||||||
name,
|
|
||||||
label,
|
|
||||||
required = false,
|
|
||||||
onChange,
|
|
||||||
}: CustomTextFieldProps) => (
|
|
||||||
<Field
|
<Field
|
||||||
name={name}
|
name={name}
|
||||||
label={label}
|
label={label}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
import Box from '@mui/material/Box';
|
import Box from "@mui/material/Box";
|
||||||
import Modal from '@mui/material/Modal';
|
import Modal from "@mui/material/Modal";
|
||||||
import Button from '@mui/material/Button';
|
import Button from "@mui/material/Button";
|
||||||
import { Typography } from '@mui/material';
|
import { Typography } from "@mui/material";
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
position: 'absolute' as 'absolute',
|
position: "absolute" as const,
|
||||||
top: '50%',
|
top: "50%",
|
||||||
left: '50%',
|
left: "50%",
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: "translate(-50%, -50%)",
|
||||||
width: 400,
|
width: 400,
|
||||||
bgcolor: '#c1c1c1',
|
bgcolor: "#c1c1c1",
|
||||||
border: '2px solid #000',
|
border: "2px solid #000",
|
||||||
boxShadow: 24,
|
boxShadow: 24,
|
||||||
pt: 2,
|
pt: 2,
|
||||||
px: 4,
|
px: 4,
|
||||||
@ -24,34 +24,29 @@ interface Props {
|
|||||||
deletePromocode: (id: string) => Promise<void>;
|
deletePromocode: (id: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ({
|
export default function ({ id, setModal, deletePromocode }: Props) {
|
||||||
id,
|
|
||||||
setModal,
|
|
||||||
deletePromocode
|
|
||||||
}: Props) {
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal open={Boolean(id)} onClose={() => setModal("")}>
|
||||||
open={Boolean(id)}
|
|
||||||
onClose={() => setModal("")}
|
|
||||||
>
|
|
||||||
<Box sx={{ ...style, width: 400 }}>
|
<Box sx={{ ...style, width: 400 }}>
|
||||||
<Typography
|
<Typography variant="h5" textAlign="center">
|
||||||
variant='h5'
|
Точно удалить промокод?
|
||||||
textAlign="center"
|
</Typography>
|
||||||
>Точно удалить промокод?</Typography>
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "space-evenly",
|
justifyContent: "space-evenly",
|
||||||
mt: "15px"
|
mt: "15px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => { deletePromocode(id); setModal("") }}
|
onClick={() => {
|
||||||
>Да</Button>
|
deletePromocode(id);
|
||||||
<Button
|
setModal("");
|
||||||
onClick={() => setModal("")}
|
}}
|
||||||
>Нет</Button>
|
>
|
||||||
|
Да
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => setModal("")}>Нет</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -1,14 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import { Box, Button, Typography, Modal, TextField, useTheme, useMediaQuery, IconButton } from "@mui/material";
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Typography,
|
|
||||||
Modal,
|
|
||||||
TextField,
|
|
||||||
useTheme,
|
|
||||||
useMediaQuery,
|
|
||||||
IconButton,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { DataGrid, GridLoadingOverlay, GridToolbar } from "@mui/x-data-grid";
|
import { DataGrid, GridLoadingOverlay, GridToolbar } from "@mui/x-data-grid";
|
||||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||||
|
|
||||||
@ -21,7 +12,7 @@ import type { GridColDef } from "@mui/x-data-grid";
|
|||||||
import type { Promocode, PromocodeStatistics } from "@root/model/promocodes";
|
import type { Promocode, PromocodeStatistics } from "@root/model/promocodes";
|
||||||
|
|
||||||
const host = window.location.hostname;
|
const host = window.location.hostname;
|
||||||
let isTest = host.includes("s");
|
const isTest = host.includes("s");
|
||||||
|
|
||||||
type StatisticsModalProps = {
|
type StatisticsModalProps = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -51,7 +42,9 @@ const COLUMNS: GridColDef<Row, string>[] = [
|
|||||||
renderCell: (params) => {
|
renderCell: (params) => {
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => navigator.clipboard.writeText(`https://${isTest ? "s" : ""}quiz.pena.digital/?fl=${params.row.link}`)}
|
onClick={() =>
|
||||||
|
navigator.clipboard.writeText(`https://${isTest ? "s" : ""}quiz.pena.digital/?fl=${params.row.link}`)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<ContentCopyIcon />
|
<ContentCopyIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@ -64,8 +57,7 @@ const COLUMNS: GridColDef<Row, string>[] = [
|
|||||||
width: 320,
|
width: 320,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
valueGetter: ({ row }) => row.link,
|
valueGetter: ({ row }) => row.link,
|
||||||
renderCell: ({ value }) =>
|
renderCell: ({ value }) => value?.split("|").map((link) => <Typography>{link}</Typography>),
|
||||||
value?.split("|").map((link) => <Typography>{link}</Typography>),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "useCount",
|
field: "useCount",
|
||||||
@ -100,11 +92,8 @@ export const StatisticsModal = ({
|
|||||||
const isMobile = useMediaQuery(theme.breakpoints.down(550));
|
const isMobile = useMediaQuery(theme.breakpoints.down(550));
|
||||||
const [rows, setRows] = useState<Row[]>([]);
|
const [rows, setRows] = useState<Row[]>([]);
|
||||||
const { privileges } = usePrivilegeStore();
|
const { privileges } = usePrivilegeStore();
|
||||||
const currentPrivilegeId = promocodes.find((promocode) => promocode.id === id)
|
const currentPrivilegeId = promocodes.find((promocode) => promocode.id === id)?.bonus.privilege.privilegeID;
|
||||||
?.bonus.privilege.privilegeID;
|
const privilege = privileges.find((item) => item.privilegeId === currentPrivilegeId);
|
||||||
const privilege = privileges.find(
|
|
||||||
(item) => item.privilegeId === currentPrivilegeId
|
|
||||||
);
|
|
||||||
const promocode = promocodes.find((item) => item.id === id);
|
const promocode = promocodes.find((item) => item.id === id);
|
||||||
const createFastlink = async () => {
|
const createFastlink = async () => {
|
||||||
await createFastLink(id);
|
await createFastLink(id);
|
||||||
@ -193,19 +182,12 @@ export const StatisticsModal = ({
|
|||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ minWidth: "200px" }}>
|
<Box sx={{ minWidth: "200px" }}>
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
||||||
<Typography sx={{ minWidth: "20px", color: "#FFFFFF" }}>
|
<Typography sx={{ minWidth: "20px", color: "#FFFFFF" }}>от</Typography>
|
||||||
от
|
|
||||||
</Typography>
|
|
||||||
<DatePicker
|
<DatePicker
|
||||||
inputFormat="DD/MM/YYYY"
|
inputFormat="DD/MM/YYYY"
|
||||||
value={startDate}
|
value={startDate}
|
||||||
onChange={(date) => date && setStartDate(date)}
|
onChange={(date) => date && setStartDate(date)}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => <TextField {...params} sx={{ background: "#1F2126", borderRadius: "5px" }} />}
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
sx={{ background: "#1F2126", borderRadius: "5px" }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
InputProps={{
|
InputProps={{
|
||||||
sx: {
|
sx: {
|
||||||
height: "40px",
|
height: "40px",
|
||||||
@ -227,19 +209,12 @@ export const StatisticsModal = ({
|
|||||||
marginTop: "10px",
|
marginTop: "10px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography sx={{ minWidth: "20px", color: "#FFFFFF" }}>
|
<Typography sx={{ minWidth: "20px", color: "#FFFFFF" }}>до</Typography>
|
||||||
до
|
|
||||||
</Typography>
|
|
||||||
<DatePicker
|
<DatePicker
|
||||||
inputFormat="DD/MM/YYYY"
|
inputFormat="DD/MM/YYYY"
|
||||||
value={endDate}
|
value={endDate}
|
||||||
onChange={(date) => date && setEndDate(date)}
|
onChange={(date) => date && setEndDate(date)}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => <TextField {...params} sx={{ background: "#1F2126", borderRadius: "5px" }} />}
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
sx={{ background: "#1F2126", borderRadius: "5px" }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
InputProps={{
|
InputProps={{
|
||||||
sx: {
|
sx: {
|
||||||
height: "40px",
|
height: "40px",
|
||||||
@ -311,24 +286,15 @@ export const StatisticsModal = ({
|
|||||||
>
|
>
|
||||||
<Typography>название привилегии: {privilege.name}</Typography>
|
<Typography>название привилегии: {privilege.name}</Typography>
|
||||||
<Typography>
|
<Typography>
|
||||||
{promocode?.activationCount} активаций из{" "}
|
{promocode?.activationCount} активаций из {promocode?.activationLimit}
|
||||||
{promocode?.activationLimit}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography>приветствие: "{promocode?.greetings}"</Typography>
|
<Typography>приветствие: "{promocode?.greetings}"</Typography>
|
||||||
{promocode?.bonus?.discount?.factor !== undefined && (
|
{promocode?.bonus?.discount?.factor !== undefined && (
|
||||||
<Typography>
|
<Typography>скидка: {100 - promocode?.bonus?.discount?.factor * 100}%</Typography>
|
||||||
скидка: {100 - promocode?.bonus?.discount?.factor * 100}%
|
|
||||||
</Typography>
|
|
||||||
)}
|
)}
|
||||||
{
|
{<Typography>количество привилегии: {promocode?.bonus?.privilege?.amount}</Typography>}
|
||||||
<Typography>
|
|
||||||
количество привилегии: {promocode?.bonus?.privilege?.amount}
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
{promocode?.dueTo !== undefined && promocode.dueTo > 0 && (
|
{promocode?.dueTo !== undefined && promocode.dueTo > 0 && (
|
||||||
<Typography>
|
<Typography>действует до: {new Date(promocode.dueTo).toLocaleString()}</Typography>
|
||||||
действует до: {new Date(promocode.dueTo).toLocaleString()}
|
|
||||||
</Typography>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
@ -16,8 +16,7 @@ export const PromocodeManagement = () => {
|
|||||||
const [deleteModal, setDeleteModal] = useState<string>("");
|
const [deleteModal, setDeleteModal] = useState<string>("");
|
||||||
const deleteModalHC = (id: string) => setDeleteModal(id);
|
const deleteModalHC = (id: string) => setDeleteModal(id);
|
||||||
|
|
||||||
const [showStatisticsModalId, setShowStatisticsModalId] =
|
const [showStatisticsModalId, setShowStatisticsModalId] = useState<string>("");
|
||||||
useState<string>("");
|
|
||||||
const [page, setPage] = useState<number>(0);
|
const [page, setPage] = useState<number>(0);
|
||||||
const [to, setTo] = useState(0);
|
const [to, setTo] = useState(0);
|
||||||
const [from, setFrom] = useState(0);
|
const [from, setFrom] = useState(0);
|
||||||
@ -32,10 +31,7 @@ export const PromocodeManagement = () => {
|
|||||||
createPromocode,
|
createPromocode,
|
||||||
createFastLink,
|
createFastLink,
|
||||||
} = usePromocodes(page, pageSize, showStatisticsModalId, to, from);
|
} = usePromocodes(page, pageSize, showStatisticsModalId, to, from);
|
||||||
const columns = usePromocodeGridColDef(
|
const columns = usePromocodeGridColDef(setShowStatisticsModalId, deleteModalHC);
|
||||||
setShowStatisticsModalId,
|
|
||||||
deleteModalHC
|
|
||||||
);
|
|
||||||
if (error) return <Typography>Ошибка загрузки промокодов</Typography>;
|
if (error) return <Typography>Ошибка загрузки промокодов</Typography>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -104,11 +100,7 @@ export const PromocodeManagement = () => {
|
|||||||
promocodes={data?.items ?? []}
|
promocodes={data?.items ?? []}
|
||||||
createFastLink={createFastLink}
|
createFastLink={createFastLink}
|
||||||
/>
|
/>
|
||||||
<DeleteModal
|
<DeleteModal id={deleteModal} setModal={setDeleteModal} deletePromocode={deletePromocode} />
|
||||||
id={deleteModal}
|
|
||||||
setModal={setDeleteModal}
|
|
||||||
deletePromocode={deletePromocode}
|
|
||||||
/>
|
|
||||||
</LocalizationProvider>
|
</LocalizationProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -6,10 +6,7 @@ import { useMemo, useState } from "react";
|
|||||||
import { BarChart, Delete } from "@mui/icons-material";
|
import { BarChart, Delete } from "@mui/icons-material";
|
||||||
import { promocodeApi } from "@root/api/promocode/requests";
|
import { promocodeApi } from "@root/api/promocode/requests";
|
||||||
|
|
||||||
export function usePromocodeGridColDef(
|
export function usePromocodeGridColDef(setStatistics: (id: string) => void, deletePromocode: (id: string) => void) {
|
||||||
setStatistics: (id: string) => void,
|
|
||||||
deletePromocode: (id: string) => void
|
|
||||||
) {
|
|
||||||
const validity = (value: string | number) => {
|
const validity = (value: string | number) => {
|
||||||
if (value === 0) {
|
if (value === 0) {
|
||||||
return "неоганичен";
|
return "неоганичен";
|
||||||
@ -38,8 +35,7 @@ export function usePromocodeGridColDef(
|
|||||||
headerName: "Коэф. скидки",
|
headerName: "Коэф. скидки",
|
||||||
width: 120,
|
width: 120,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
valueGetter: ({ row }) =>
|
valueGetter: ({ row }) => Math.round(row.bonus.discount.factor * 1000) / 1000,
|
||||||
Math.round(row.bonus.discount.factor * 1000) / 1000,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "activationCount",
|
field: "activationCount",
|
||||||
|
@ -29,13 +29,8 @@ export const DateFilter = ({ to, setTo, from, setFrom }: DateFilterProps) => {
|
|||||||
<DatePicker
|
<DatePicker
|
||||||
inputFormat="DD/MM/YYYY"
|
inputFormat="DD/MM/YYYY"
|
||||||
value={from}
|
value={from}
|
||||||
onChange={(date) => date && setFrom(date.startOf('day'))}
|
onChange={(date) => date && setFrom(date.startOf("day"))}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => <TextField {...params} sx={{ background: "#1F2126", borderRadius: "5px" }} />}
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
sx={{ background: "#1F2126", borderRadius: "5px" }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
InputProps={{
|
InputProps={{
|
||||||
sx: {
|
sx: {
|
||||||
height: "40px",
|
height: "40px",
|
||||||
@ -61,13 +56,8 @@ export const DateFilter = ({ to, setTo, from, setFrom }: DateFilterProps) => {
|
|||||||
<DatePicker
|
<DatePicker
|
||||||
inputFormat="DD/MM/YYYY"
|
inputFormat="DD/MM/YYYY"
|
||||||
value={to}
|
value={to}
|
||||||
onChange={(date) => date && setTo(date.endOf('day'))}
|
onChange={(date) => date && setTo(date.endOf("day"))}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => <TextField {...params} sx={{ background: "#1F2126", borderRadius: "5px" }} />}
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
sx={{ background: "#1F2126", borderRadius: "5px" }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
InputProps={{
|
InputProps={{
|
||||||
sx: {
|
sx: {
|
||||||
height: "40px",
|
height: "40px",
|
||||||
|
@ -1,14 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import {
|
import { Table, TableBody, TableCell, TableHead, TableRow, Button, useTheme } from "@mui/material";
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
Button,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
|
|
||||||
import { DateFilter } from "./DateFilter";
|
import { DateFilter } from "./DateFilter";
|
||||||
|
|
||||||
|
@ -1,14 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import {
|
import { Table, TableBody, TableCell, TableHead, TableRow, Typography, useTheme } from "@mui/material";
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
Typography,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
|
|
||||||
import { DateFilter } from "./DateFilter";
|
import { DateFilter } from "./DateFilter";
|
||||||
|
|
||||||
@ -18,9 +10,7 @@ import { usePromocodeStatistics } from "@root/utils/hooks/usePromocodeStatistics
|
|||||||
import type { Moment } from "moment";
|
import type { Moment } from "moment";
|
||||||
|
|
||||||
export const StatisticsPromocode = () => {
|
export const StatisticsPromocode = () => {
|
||||||
const [from, setFrom] = useState<Moment | null>(
|
const [from, setFrom] = useState<Moment | null>(moment(moment().subtract(4, "weeks")));
|
||||||
moment(moment().subtract(4, "weeks"))
|
|
||||||
);
|
|
||||||
const [to, setTo] = useState<Moment | null>(moment());
|
const [to, setTo] = useState<Moment | null>(moment());
|
||||||
const promocodes = useAllPromocodes();
|
const promocodes = useAllPromocodes();
|
||||||
const promocodeStatistics = usePromocodeStatistics({ to, from });
|
const promocodeStatistics = usePromocodeStatistics({ to, from });
|
||||||
|
@ -55,14 +55,14 @@ export const StatisticsSchild = () => {
|
|||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
|
|
||||||
const [from, setFrom] = useState<Moment | null>(
|
const [from, setFrom] = useState<Moment | null>(moment(moment().subtract(4, "weeks")));
|
||||||
moment(moment().subtract(4, "weeks"))
|
|
||||||
);
|
|
||||||
const [to, setTo] = useState<Moment | null>(moment());
|
const [to, setTo] = useState<Moment | null>(moment());
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const statistics = useSchildStatistics(from, to)
|
const statistics = useSchildStatistics(from, to).map((obj) => ({
|
||||||
.map((obj) => ({...obj, Money: Number((obj.Money / 100).toFixed(2))}));
|
...obj,
|
||||||
|
Money: Number((obj.Money / 100).toFixed(2)),
|
||||||
|
}));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!openUserModal) {
|
if (!openUserModal) {
|
||||||
@ -81,20 +81,14 @@ export const StatisticsSchild = () => {
|
|||||||
}, [activeUserId]);
|
}, [activeUserId]);
|
||||||
|
|
||||||
const copyQuizLink = (quizId: string) => {
|
const copyQuizLink = (quizId: string) => {
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(`https://${window.location.href.includes("/admin.") ? "" : "s."}hbpn.link/${quizId}`);
|
||||||
`https://${
|
|
||||||
window.location.href.includes("/admin.") ? "" : "s."
|
|
||||||
}hbpn.link/${quizId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
enqueueSnackbar("Ссылка успешно скопирована");
|
enqueueSnackbar("Ссылка успешно скопирована");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography sx={{ mt: "20px", mb: "20px" }}>
|
<Typography sx={{ mt: "20px", mb: "20px" }}>Статистика переходов с шильдика</Typography>
|
||||||
Статистика переходов с шильдика
|
|
||||||
</Typography>
|
|
||||||
<DateFilter from={from} to={to} setFrom={setFrom} setTo={setTo} />
|
<DateFilter from={from} to={to} setFrom={setFrom} setTo={setTo} />
|
||||||
<DataGrid
|
<DataGrid
|
||||||
sx={{ marginTop: "30px", width: "80%" }}
|
sx={{ marginTop: "30px", width: "80%" }}
|
||||||
@ -110,9 +104,7 @@ export const StatisticsSchild = () => {
|
|||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
onPageChange={setPage}
|
onPageChange={setPage}
|
||||||
onPageSizeChange={setPageSize}
|
onPageSizeChange={setPageSize}
|
||||||
onCellClick={({ id, field }) =>
|
onCellClick={({ id, field }) => field === "user" && setActiveUserId(String(id))}
|
||||||
field === "user" && setActiveUserId(String(id))
|
|
||||||
}
|
|
||||||
getRowHeight={() => "auto"}
|
getRowHeight={() => "auto"}
|
||||||
columns={[
|
columns={[
|
||||||
...COLUMNS,
|
...COLUMNS,
|
||||||
@ -166,9 +158,7 @@ export const StatisticsSchild = () => {
|
|||||||
{row.Quizes.map(({ QuizID, Regs, Money }) => (
|
{row.Quizes.map(({ QuizID, Regs, Money }) => (
|
||||||
<TableRow key={QuizID}>
|
<TableRow key={QuizID}>
|
||||||
<TableCell sx={{ color: "inherit" }} align="center">
|
<TableCell sx={{ color: "inherit" }} align="center">
|
||||||
<Button onClick={() => copyQuizLink(QuizID)}>
|
<Button onClick={() => copyQuizLink(QuizID)}>{QuizID}</Button>
|
||||||
{QuizID}
|
|
||||||
</Button>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell sx={{ color: "inherit" }} align="center">
|
<TableCell sx={{ color: "inherit" }} align="center">
|
||||||
{Regs}
|
{Regs}
|
||||||
@ -186,11 +176,7 @@ export const StatisticsSchild = () => {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<ModalUser
|
<ModalUser open={openUserModal} onClose={() => setOpenUserModal(false)} userId={activeUserId} />
|
||||||
open={openUserModal}
|
|
||||||
onClose={() => setOpenUserModal(false)}
|
|
||||||
userId={activeUserId}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
import { Box, IconButton, InputAdornment, TextField, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, IconButton, InputAdornment, TextField, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { addOrUpdateMessages, clearMessageState, incrementMessageApiPage, setIsPreventAutoscroll, setTicketMessagesFetchState, useMessageStore } from "@root/stores/messages";
|
import {
|
||||||
|
addOrUpdateMessages,
|
||||||
|
clearMessageState,
|
||||||
|
incrementMessageApiPage,
|
||||||
|
setIsPreventAutoscroll,
|
||||||
|
setTicketMessagesFetchState,
|
||||||
|
useMessageStore,
|
||||||
|
} from "@root/stores/messages";
|
||||||
import Message from "./Message";
|
import Message from "./Message";
|
||||||
import SendIcon from "@mui/icons-material/Send";
|
import SendIcon from "@mui/icons-material/Send";
|
||||||
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
||||||
@ -9,7 +16,14 @@ import { TicketMessage } from "@root/model/ticket";
|
|||||||
import { sendTicketMessage } from "@root/api/tickets";
|
import { sendTicketMessage } from "@root/api/tickets";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useTicketStore } from "@root/stores/tickets";
|
import { useTicketStore } from "@root/stores/tickets";
|
||||||
import { getMessageFromFetchError, throttle, useEventListener, useSSESubscription, useTicketMessages, useToken } from "@frontend/kitui";
|
import {
|
||||||
|
getMessageFromFetchError,
|
||||||
|
throttle,
|
||||||
|
useEventListener,
|
||||||
|
useSSESubscription,
|
||||||
|
useTicketMessages,
|
||||||
|
useToken,
|
||||||
|
} from "@frontend/kitui";
|
||||||
import makeRequest from "@root/api/makeRequest";
|
import makeRequest from "@root/api/makeRequest";
|
||||||
import ChatImage from "./ChatImage";
|
import ChatImage from "./ChatImage";
|
||||||
import ChatDocument from "./ChatDocument";
|
import ChatDocument from "./ChatDocument";
|
||||||
@ -17,57 +31,57 @@ import ChatVideo from "./ChatVideo";
|
|||||||
import ChatMessage from "./ChatMessage";
|
import ChatMessage from "./ChatMessage";
|
||||||
import { ACCEPT_SEND_MEDIA_TYPES_MAP, MAX_FILE_SIZE, MAX_PHOTO_SIZE, MAX_VIDEO_SIZE } from "./fileUpload";
|
import { ACCEPT_SEND_MEDIA_TYPES_MAP, MAX_FILE_SIZE, MAX_PHOTO_SIZE, MAX_VIDEO_SIZE } from "./fileUpload";
|
||||||
|
|
||||||
const tooLarge = "Файл слишком большой"
|
const tooLarge = "Файл слишком большой";
|
||||||
const checkAcceptableMediaType = (file: File) => {
|
const checkAcceptableMediaType = (file: File) => {
|
||||||
if (file === null) return ""
|
if (file === null) return "";
|
||||||
|
|
||||||
const segments = file?.name.split('.');
|
const segments = file?.name.split(".");
|
||||||
const extension = segments[segments.length - 1];
|
const extension = segments[segments.length - 1];
|
||||||
const type = extension.toLowerCase();
|
const type = extension.toLowerCase();
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ACCEPT_SEND_MEDIA_TYPES_MAP.document.find(name => name === type):
|
case ACCEPT_SEND_MEDIA_TYPES_MAP.document.find((name) => name === type):
|
||||||
if (file.size > MAX_FILE_SIZE) return tooLarge
|
if (file.size > MAX_FILE_SIZE) return tooLarge;
|
||||||
return ""
|
return "";
|
||||||
|
|
||||||
case ACCEPT_SEND_MEDIA_TYPES_MAP.picture.find(name => name === type):
|
case ACCEPT_SEND_MEDIA_TYPES_MAP.picture.find((name) => name === type):
|
||||||
if (file.size > MAX_PHOTO_SIZE) return tooLarge
|
if (file.size > MAX_PHOTO_SIZE) return tooLarge;
|
||||||
return ""
|
return "";
|
||||||
|
|
||||||
case ACCEPT_SEND_MEDIA_TYPES_MAP.video.find(name => name === type):
|
case ACCEPT_SEND_MEDIA_TYPES_MAP.video.find((name) => name === type):
|
||||||
if (file.size > MAX_VIDEO_SIZE) return tooLarge
|
if (file.size > MAX_VIDEO_SIZE) return tooLarge;
|
||||||
return ""
|
return "";
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "Не удалось отправить файл. Недопустимый тип"
|
return "Не удалось отправить файл. Недопустимый тип";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default function Chat() {
|
export default function Chat() {
|
||||||
const token = useToken();
|
const token = useToken();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const tickets = useTicketStore(state => state.tickets);
|
const tickets = useTicketStore((state) => state.tickets);
|
||||||
const messages = useMessageStore(state => state.messages);
|
const messages = useMessageStore((state) => state.messages);
|
||||||
const [messageField, setMessageField] = useState<string>("");
|
const [messageField, setMessageField] = useState<string>("");
|
||||||
const ticketId = useParams().ticketId;
|
const ticketId = useParams().ticketId;
|
||||||
const chatBoxRef = useRef<HTMLDivElement>(null);
|
const chatBoxRef = useRef<HTMLDivElement>(null);
|
||||||
const messageApiPage = useMessageStore(state => state.apiPage);
|
const messageApiPage = useMessageStore((state) => state.apiPage);
|
||||||
const messagesPerPage = useMessageStore(state => state.messagesPerPage);
|
const messagesPerPage = useMessageStore((state) => state.messagesPerPage);
|
||||||
const isPreventAutoscroll = useMessageStore(state => state.isPreventAutoscroll);
|
const isPreventAutoscroll = useMessageStore((state) => state.isPreventAutoscroll);
|
||||||
const fetchState = useMessageStore(state => state.ticketMessagesFetchState);
|
const fetchState = useMessageStore((state) => state.ticketMessagesFetchState);
|
||||||
const lastMessageId = useMessageStore(state => state.lastMessageId);
|
const lastMessageId = useMessageStore((state) => state.lastMessageId);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [disableFileButton, setDisableFileButton] = useState(false);
|
const [disableFileButton, setDisableFileButton] = useState(false);
|
||||||
|
|
||||||
const ticket = tickets.find(ticket => ticket.id === ticketId);
|
const ticket = tickets.find((ticket) => ticket.id === ticketId);
|
||||||
|
|
||||||
useTicketMessages({
|
useTicketMessages({
|
||||||
url: process.env.REACT_APP_DOMAIN + "/heruvym/getMessages",
|
url: process.env.REACT_APP_DOMAIN + "/heruvym/getMessages",
|
||||||
ticketId,
|
ticketId,
|
||||||
messagesPerPage,
|
messagesPerPage,
|
||||||
messageApiPage,
|
messageApiPage,
|
||||||
onSuccess: messages => {
|
onSuccess: (messages) => {
|
||||||
if (chatBoxRef.current && chatBoxRef.current.scrollTop < 1) chatBoxRef.current.scrollTop = 1;
|
if (chatBoxRef.current && chatBoxRef.current.scrollTop < 1) chatBoxRef.current.scrollTop = 1;
|
||||||
addOrUpdateMessages(messages);
|
addOrUpdateMessages(messages);
|
||||||
},
|
},
|
||||||
@ -86,10 +100,12 @@ export default function Chat() {
|
|||||||
clearMessageState();
|
clearMessageState();
|
||||||
setIsPreventAutoscroll(false);
|
setIsPreventAutoscroll(false);
|
||||||
},
|
},
|
||||||
marker: "ticket message"
|
marker: "ticket message",
|
||||||
});
|
});
|
||||||
|
|
||||||
const throttledScrollHandler = useMemo(() => throttle(() => {
|
const throttledScrollHandler = useMemo(
|
||||||
|
() =>
|
||||||
|
throttle(() => {
|
||||||
const chatBox = chatBoxRef.current;
|
const chatBox = chatBoxRef.current;
|
||||||
if (!chatBox) return;
|
if (!chatBox) return;
|
||||||
|
|
||||||
@ -102,11 +118,14 @@ export default function Chat() {
|
|||||||
if (chatBox.scrollTop < chatBox.clientHeight) {
|
if (chatBox.scrollTop < chatBox.clientHeight) {
|
||||||
incrementMessageApiPage();
|
incrementMessageApiPage();
|
||||||
}
|
}
|
||||||
}, 200), [fetchState]);
|
}, 200),
|
||||||
|
[fetchState]
|
||||||
|
);
|
||||||
|
|
||||||
useEventListener("scroll", throttledScrollHandler, chatBoxRef);
|
useEventListener("scroll", throttledScrollHandler, chatBoxRef);
|
||||||
|
|
||||||
useEffect(function scrollOnNewMessage() {
|
useEffect(
|
||||||
|
function scrollOnNewMessage() {
|
||||||
if (!chatBoxRef.current) return;
|
if (!chatBoxRef.current) return;
|
||||||
|
|
||||||
if (!isPreventAutoscroll) {
|
if (!isPreventAutoscroll) {
|
||||||
@ -115,7 +134,9 @@ export default function Chat() {
|
|||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [lastMessageId]);
|
},
|
||||||
|
[lastMessageId]
|
||||||
|
);
|
||||||
|
|
||||||
function scrollToBottom(behavior?: ScrollBehavior) {
|
function scrollToBottom(behavior?: ScrollBehavior) {
|
||||||
if (!chatBoxRef.current) return;
|
if (!chatBoxRef.current) return;
|
||||||
@ -145,7 +166,7 @@ export default function Chat() {
|
|||||||
|
|
||||||
let data;
|
let data;
|
||||||
|
|
||||||
const ticketId = ticket?.id
|
const ticketId = ticket?.id;
|
||||||
if (ticketId !== undefined) {
|
if (ticketId !== undefined) {
|
||||||
try {
|
try {
|
||||||
const body = new FormData();
|
const body = new FormData();
|
||||||
@ -165,14 +186,14 @@ export default function Chat() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const sendFileHC = async (file: File) => {
|
const sendFileHC = async (file: File) => {
|
||||||
const check = checkAcceptableMediaType(file)
|
const check = checkAcceptableMediaType(file);
|
||||||
if (check.length > 0) {
|
if (check.length > 0) {
|
||||||
enqueueSnackbar(check)
|
enqueueSnackbar(check);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
setDisableFileButton(true)
|
setDisableFileButton(true);
|
||||||
await sendFile(file)
|
await sendFile(file);
|
||||||
setDisableFileButton(false)
|
setDisableFileButton(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleTextfieldKeyPress(e: KeyboardEvent) {
|
function handleTextfieldKeyPress(e: KeyboardEvent) {
|
||||||
@ -183,7 +204,8 @@ export default function Chat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
border: "1px solid",
|
border: "1px solid",
|
||||||
borderColor: theme.palette.grayDark.main,
|
borderColor: theme.palette.grayDark.main,
|
||||||
height: "600px",
|
height: "600px",
|
||||||
@ -195,7 +217,8 @@ export default function Chat() {
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "8px",
|
gap: "8px",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Typography>{ticket ? ticket.title : "Выберите тикет"}</Typography>
|
<Typography>{ticket ? ticket.title : "Выберите тикет"}</Typography>
|
||||||
<Box
|
<Box
|
||||||
ref={chatBoxRef}
|
ref={chatBoxRef}
|
||||||
@ -215,67 +238,73 @@ export default function Chat() {
|
|||||||
messages.map((message) => {
|
messages.map((message) => {
|
||||||
const isFileVideo = () => {
|
const isFileVideo = () => {
|
||||||
if (message.files) {
|
if (message.files) {
|
||||||
return (ACCEPT_SEND_MEDIA_TYPES_MAP.video.some((fileType) =>
|
return ACCEPT_SEND_MEDIA_TYPES_MAP.video.some((fileType) =>
|
||||||
message.files[0].toLowerCase().endsWith(fileType),
|
message.files[0].toLowerCase().endsWith(fileType)
|
||||||
))
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const isFileImage = () => {
|
const isFileImage = () => {
|
||||||
if (message.files) {
|
if (message.files) {
|
||||||
return (ACCEPT_SEND_MEDIA_TYPES_MAP.picture.some((fileType) =>
|
return ACCEPT_SEND_MEDIA_TYPES_MAP.picture.some((fileType) =>
|
||||||
message.files[0].toLowerCase().endsWith(fileType),
|
message.files[0].toLowerCase().endsWith(fileType)
|
||||||
))
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const isFileDocument = () => {
|
const isFileDocument = () => {
|
||||||
if (message.files) {
|
if (message.files) {
|
||||||
return (ACCEPT_SEND_MEDIA_TYPES_MAP.document.some((fileType) =>
|
return ACCEPT_SEND_MEDIA_TYPES_MAP.document.some((fileType) =>
|
||||||
message.files[0].toLowerCase().endsWith(fileType),
|
message.files[0].toLowerCase().endsWith(fileType)
|
||||||
))
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (message.files !== null && message.files.length > 0 && isFileImage()) {
|
if (message.files !== null && message.files.length > 0 && isFileImage()) {
|
||||||
return <ChatImage
|
return (
|
||||||
|
<ChatImage
|
||||||
unAuthenticated
|
unAuthenticated
|
||||||
key={message.id}
|
key={message.id}
|
||||||
file={message.files[0]}
|
file={message.files[0]}
|
||||||
createdAt={message.created_at}
|
createdAt={message.created_at}
|
||||||
isSelf={ticket.user !== message.user_id}
|
isSelf={ticket.user !== message.user_id}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (message.files !== null && message.files.length > 0 && isFileVideo()) {
|
if (message.files !== null && message.files.length > 0 && isFileVideo()) {
|
||||||
return <ChatVideo
|
return (
|
||||||
|
<ChatVideo
|
||||||
unAuthenticated
|
unAuthenticated
|
||||||
key={message.id}
|
key={message.id}
|
||||||
file={message.files[0]}
|
file={message.files[0]}
|
||||||
createdAt={message.created_at}
|
createdAt={message.created_at}
|
||||||
isSelf={ticket.user !== message.user_id}
|
isSelf={ticket.user !== message.user_id}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (message.files !== null && message.files.length > 0 && isFileDocument()) {
|
if (message.files !== null && message.files.length > 0 && isFileDocument()) {
|
||||||
return <ChatDocument
|
return (
|
||||||
|
<ChatDocument
|
||||||
unAuthenticated
|
unAuthenticated
|
||||||
key={message.id}
|
key={message.id}
|
||||||
file={message.files[0]}
|
file={message.files[0]}
|
||||||
createdAt={message.created_at}
|
createdAt={message.created_at}
|
||||||
isSelf={ticket.user !== message.user_id}
|
isSelf={ticket.user !== message.user_id}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return <ChatMessage
|
return (
|
||||||
|
<ChatMessage
|
||||||
unAuthenticated
|
unAuthenticated
|
||||||
key={message.id}
|
key={message.id}
|
||||||
text={message.message}
|
text={message.message}
|
||||||
createdAt={message.created_at}
|
createdAt={message.created_at}
|
||||||
isSelf={ticket.user !== message.user_id}
|
isSelf={ticket.user !== message.user_id}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
})
|
})}
|
||||||
}
|
|
||||||
</Box>
|
</Box>
|
||||||
{ticket &&
|
{ticket && (
|
||||||
<TextField
|
<TextField
|
||||||
value={messageField}
|
value={messageField}
|
||||||
onChange={e => setMessageField(e.target.value)}
|
onChange={(e) => setMessageField(e.target.value)}
|
||||||
onKeyPress={handleTextfieldKeyPress}
|
onKeyPress={handleTextfieldKeyPress}
|
||||||
id="message-input"
|
id="message-input"
|
||||||
placeholder="Написать сообщение"
|
placeholder="Написать сообщение"
|
||||||
@ -301,7 +330,7 @@ export default function Chat() {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!disableFileButton) fileInputRef.current?.click()
|
if (!disableFileButton) fileInputRef.current?.click();
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
height: "45px",
|
height: "45px",
|
||||||
@ -321,15 +350,15 @@ export default function Chat() {
|
|||||||
<AttachFileIcon sx={{ color: theme.palette.golden.main }} />
|
<AttachFileIcon sx={{ color: theme.palette.golden.main }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
)
|
),
|
||||||
}}
|
}}
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
style: {
|
style: {
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import DownloadIcon from '@mui/icons-material/Download';
|
import DownloadIcon from "@mui/icons-material/Download";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
unAuthenticated?: boolean;
|
unAuthenticated?: boolean;
|
||||||
@ -8,12 +8,7 @@ interface Props {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ChatDocument({
|
export default function ChatDocument({ unAuthenticated = false, isSelf, file, createdAt }: Props) {
|
||||||
unAuthenticated = false,
|
|
||||||
isSelf,
|
|
||||||
file,
|
|
||||||
createdAt,
|
|
||||||
}: Props) {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const date = new Date(createdAt);
|
const date = new Date(createdAt);
|
||||||
@ -27,11 +22,13 @@ export default function ChatDocument({
|
|||||||
justifyContent: isSelf ? "end" : "start",
|
justifyContent: isSelf ? "end" : "start",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography sx={{
|
<Typography
|
||||||
|
sx={{
|
||||||
fontSize: "12px",
|
fontSize: "12px",
|
||||||
alignSelf: "end",
|
alignSelf: "end",
|
||||||
}}>
|
}}
|
||||||
{new Date(createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
>
|
||||||
|
{new Date(createdAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -45,7 +42,6 @@ export default function ChatDocument({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
|
|
||||||
download
|
download
|
||||||
href={`https://admin.pena/pair/${file}`}
|
href={`https://admin.pena/pair/${file}`}
|
||||||
style={{
|
style={{
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
import {
|
import { Box, ButtonBase, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
Box,
|
|
||||||
ButtonBase,
|
|
||||||
Link,
|
|
||||||
Typography,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -15,17 +8,11 @@ interface Props {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ChatImage({
|
export default function ChatImage({ unAuthenticated = false, isSelf, file, createdAt }: Props) {
|
||||||
unAuthenticated = false,
|
|
||||||
isSelf,
|
|
||||||
file,
|
|
||||||
createdAt,
|
|
||||||
}: Props) {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -35,11 +22,13 @@ export default function ChatImage({
|
|||||||
justifyContent: isSelf ? "end" : "start",
|
justifyContent: isSelf ? "end" : "start",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography sx={{
|
<Typography
|
||||||
|
sx={{
|
||||||
fontSize: "12px",
|
fontSize: "12px",
|
||||||
alignSelf: "end",
|
alignSelf: "end",
|
||||||
}}>
|
}}
|
||||||
{new Date(createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
>
|
||||||
|
{new Date(createdAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -7,17 +7,10 @@ interface Props {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ChatMessage({
|
export default function ChatMessage({ unAuthenticated = false, isSelf, text, createdAt }: Props) {
|
||||||
unAuthenticated = false,
|
|
||||||
isSelf,
|
|
||||||
text,
|
|
||||||
createdAt,
|
|
||||||
}: Props) {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -27,11 +20,13 @@ export default function ChatMessage({
|
|||||||
justifyContent: isSelf ? "end" : "start",
|
justifyContent: isSelf ? "end" : "start",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography sx={{
|
<Typography
|
||||||
|
sx={{
|
||||||
fontSize: "12px",
|
fontSize: "12px",
|
||||||
alignSelf: "end",
|
alignSelf: "end",
|
||||||
}}>
|
}}
|
||||||
{new Date(createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
>
|
||||||
|
{new Date(createdAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -8,12 +8,7 @@ interface Props {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ChatImage({
|
export default function ChatImage({ unAuthenticated = false, isSelf, file, createdAt }: Props) {
|
||||||
unAuthenticated = false,
|
|
||||||
isSelf,
|
|
||||||
file,
|
|
||||||
createdAt,
|
|
||||||
}: Props) {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Box, Typography, useTheme } from "@mui/material";
|
import { Box, Typography, useTheme } from "@mui/material";
|
||||||
import { TicketMessage } from "@root/model/ticket";
|
import { TicketMessage } from "@root/model/ticket";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
message: TicketMessage;
|
message: TicketMessage;
|
||||||
isSelf?: boolean;
|
isSelf?: boolean;
|
||||||
@ -11,21 +10,26 @@ export default function Message({ message, isSelf }: Props) {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const time = (
|
const time = (
|
||||||
<Typography sx={{
|
<Typography
|
||||||
|
sx={{
|
||||||
fontSize: "12px",
|
fontSize: "12px",
|
||||||
alignSelf: "end",
|
alignSelf: "end",
|
||||||
}}>
|
}}
|
||||||
{new Date(message.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
>
|
||||||
|
{new Date(message.created_at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
|
||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: isSelf ? "end" : "start",
|
justifyContent: isSelf ? "end" : "start",
|
||||||
gap: "6px",
|
gap: "6px",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{isSelf && time}
|
{isSelf && time}
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
backgroundColor: "#2a2b2c",
|
backgroundColor: "#2a2b2c",
|
||||||
p: "12px",
|
p: "12px",
|
||||||
border: `1px solid ${theme.palette.golden.main}`,
|
border: `1px solid ${theme.palette.golden.main}`,
|
||||||
@ -33,7 +37,8 @@ export default function Message({ message, isSelf }: Props) {
|
|||||||
borderTopLeftRadius: isSelf ? "20px" : 0,
|
borderTopLeftRadius: isSelf ? "20px" : 0,
|
||||||
borderTopRightRadius: isSelf ? 0 : "20px",
|
borderTopRightRadius: isSelf ? 0 : "20px",
|
||||||
maxWidth: "90%",
|
maxWidth: "90%",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Typography fontSize="14px" sx={{ wordBreak: "break-word" }}>
|
<Typography fontSize="14px" sx={{ wordBreak: "break-word" }}>
|
||||||
{message.message}
|
{message.message}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useTheme } from "@mui/material";
|
import { useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
}
|
}
|
||||||
@ -9,9 +8,27 @@ export default function ExpandIcon({ isExpanded }: Props) {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg style={{ transform: isExpanded ? "rotate(180deg)" : undefined }} xmlns="http://www.w3.org/2000/svg" width="32" height="33" viewBox="0 0 32 33" fill="none">
|
<svg
|
||||||
<path stroke={isExpanded ? theme.palette.golden.main : theme.palette.goldenDark.main} d="M16 28.7949C22.6274 28.7949 28 23.4223 28 16.7949C28 10.1675 22.6274 4.79492 16 4.79492C9.37258 4.79492 4 10.1675 4 16.7949C4 23.4223 9.37258 28.7949 16 28.7949Z" strokeWidth="2" strokeMiterlimit="10" />
|
style={{ transform: isExpanded ? "rotate(180deg)" : undefined }}
|
||||||
<path stroke={isExpanded ? theme.palette.golden.main : theme.palette.goldenDark.main} d="M20.5 15.2949L16 20.2949L11.5 15.2949" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="32"
|
||||||
|
height="33"
|
||||||
|
viewBox="0 0 32 33"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke={isExpanded ? theme.palette.golden.main : theme.palette.goldenDark.main}
|
||||||
|
d="M16 28.7949C22.6274 28.7949 28 23.4223 28 16.7949C28 10.1675 22.6274 4.79492 16 4.79492C9.37258 4.79492 4 10.1675 4 16.7949C4 23.4223 9.37258 28.7949 16 28.7949Z"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
stroke={isExpanded ? theme.palette.golden.main : theme.palette.goldenDark.main}
|
||||||
|
d="M20.5 15.2949L16 20.2949L11.5 15.2949"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,20 +4,10 @@ import Chat from "./Chat/Chat";
|
|||||||
import Collapse from "./Collapse";
|
import Collapse from "./Collapse";
|
||||||
import TicketList from "./TicketList/TicketList";
|
import TicketList from "./TicketList/TicketList";
|
||||||
import { Ticket } from "@root/model/ticket";
|
import { Ticket } from "@root/model/ticket";
|
||||||
import {
|
import { clearTickets, setTicketsFetchState, updateTickets, useTicketStore } from "@root/stores/tickets";
|
||||||
clearTickets,
|
|
||||||
setTicketsFetchState,
|
|
||||||
updateTickets,
|
|
||||||
useTicketStore,
|
|
||||||
} from "@root/stores/tickets";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { clearMessageState } from "@root/stores/messages";
|
import { clearMessageState } from "@root/stores/messages";
|
||||||
import {
|
import { getMessageFromFetchError, useSSESubscription, useTicketsFetcher, useToken } from "@frontend/kitui";
|
||||||
getMessageFromFetchError,
|
|
||||||
useSSESubscription,
|
|
||||||
useTicketsFetcher,
|
|
||||||
useToken,
|
|
||||||
} from "@frontend/kitui";
|
|
||||||
import ModalUser from "@root/pages/dashboard/ModalUser";
|
import ModalUser from "@root/pages/dashboard/ModalUser";
|
||||||
|
|
||||||
export default function Support() {
|
export default function Support() {
|
||||||
@ -45,9 +35,7 @@ export default function Support() {
|
|||||||
|
|
||||||
useSSESubscription<Ticket>({
|
useSSESubscription<Ticket>({
|
||||||
enabled: Boolean(token),
|
enabled: Boolean(token),
|
||||||
url:
|
url: process.env.REACT_APP_DOMAIN + `/heruvym/subscribe?Authorization=${token}`,
|
||||||
process.env.REACT_APP_DOMAIN +
|
|
||||||
`/heruvym/subscribe?Authorization=${token}`,
|
|
||||||
onNewData: updateTickets,
|
onNewData: updateTickets,
|
||||||
onDisconnect: () => {
|
onDisconnect: () => {
|
||||||
clearMessageState();
|
clearMessageState();
|
||||||
@ -83,21 +71,12 @@ export default function Support() {
|
|||||||
>
|
>
|
||||||
{!upMd && (
|
{!upMd && (
|
||||||
<Collapse headerText="Тикеты">
|
<Collapse headerText="Тикеты">
|
||||||
{(closeCollapse) => (
|
{(closeCollapse) => <TicketList closeCollapse={closeCollapse} setActiveUserId={setActiveUserId} />}
|
||||||
<TicketList
|
|
||||||
closeCollapse={closeCollapse}
|
|
||||||
setActiveUserId={setActiveUserId}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Collapse>
|
</Collapse>
|
||||||
)}
|
)}
|
||||||
<Chat />
|
<Chat />
|
||||||
{upMd && <TicketList setActiveUserId={setActiveUserId} />}
|
{upMd && <TicketList setActiveUserId={setActiveUserId} />}
|
||||||
<ModalUser
|
<ModalUser open={openUserModal} onClose={() => setOpenUserModal(false)} userId={activeUserId} />
|
||||||
open={openUserModal}
|
|
||||||
onClose={() => setOpenUserModal(false)}
|
|
||||||
userId={activeUserId}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,12 @@ import Button from "@mui/material/Button";
|
|||||||
import makeRequest from "@root/api/makeRequest";
|
import makeRequest from "@root/api/makeRequest";
|
||||||
import { parseAxiosError } from "@root/utils/parse-error";
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
interface Props {
|
interface Props {
|
||||||
ticketId: string | undefined,
|
ticketId: string | undefined;
|
||||||
openModal: boolean,
|
openModal: boolean;
|
||||||
setOpenModal: (a: boolean) => void
|
setOpenModal: (a: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CloseTicketModal({ ticketId, openModal, setOpenModal }: Props) {
|
export default function CloseTicketModal({ ticketId, openModal, setOpenModal }: Props) {
|
||||||
|
|
||||||
const CloseTicket = async () => {
|
const CloseTicket = async () => {
|
||||||
try {
|
try {
|
||||||
const ticketCloseResponse = await makeRequest<unknown, unknown>({
|
const ticketCloseResponse = await makeRequest<unknown, unknown>({
|
||||||
@ -20,7 +19,7 @@ export default function CloseTicketModal({ticketId, openModal, setOpenModal}: Pr
|
|||||||
method: "post",
|
method: "post",
|
||||||
useToken: true,
|
useToken: true,
|
||||||
body: {
|
body: {
|
||||||
"ticket": ticketId
|
ticket: ticketId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -30,7 +29,7 @@ export default function CloseTicketModal({ticketId, openModal, setOpenModal}: Pr
|
|||||||
|
|
||||||
return [null, `Не удалось закрыть тикет. ${error}`];
|
return [null, `Не удалось закрыть тикет. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@ -67,21 +66,18 @@ export default function CloseTicketModal({ticketId, openModal, setOpenModal}: Pr
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
CloseTicket()
|
CloseTicket();
|
||||||
setOpenModal(false)
|
setOpenModal(false);
|
||||||
}}
|
}}
|
||||||
sx={{ width: "40px", height: "25px" }}
|
sx={{ width: "40px", height: "25px" }}
|
||||||
>
|
>
|
||||||
Да
|
Да
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button onClick={() => setOpenModal(false)} sx={{ width: "40px", height: "25px" }}>
|
||||||
onClick={() => setOpenModal(false)}
|
|
||||||
sx={{width: "40px", height: "25px"}}
|
|
||||||
>
|
|
||||||
Нет
|
Нет
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
);
|
||||||
}
|
}
|
@ -1,14 +1,5 @@
|
|||||||
import CircleIcon from "@mui/icons-material/Circle";
|
import CircleIcon from "@mui/icons-material/Circle";
|
||||||
import {
|
import { Box, Card, CardActionArea, CardContent, CardHeader, Divider, Typography, useTheme } from "@mui/material";
|
||||||
Box,
|
|
||||||
Card,
|
|
||||||
CardActionArea,
|
|
||||||
CardContent,
|
|
||||||
CardHeader,
|
|
||||||
Divider,
|
|
||||||
Typography,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { green } from "@mui/material/colors";
|
import { green } from "@mui/material/colors";
|
||||||
import { Ticket } from "@root/model/ticket";
|
import { Ticket } from "@root/model/ticket";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
@ -76,9 +67,7 @@ export default function TicketItem({ ticket, setActiveUserId }: Props) {
|
|||||||
p: 0,
|
p: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={flexCenterSx}>
|
<Box sx={flexCenterSx}>{new Date(ticket.top_message.created_at).toLocaleDateString()}</Box>
|
||||||
{new Date(ticket.top_message.created_at).toLocaleDateString()}
|
|
||||||
</Box>
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
...flexCenterSx,
|
...flexCenterSx,
|
||||||
|
@ -16,17 +16,14 @@ type TicketListProps = {
|
|||||||
setActiveUserId: (id: string) => void;
|
setActiveUserId: (id: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TicketList({
|
export default function TicketList({ closeCollapse, setActiveUserId }: TicketListProps) {
|
||||||
closeCollapse,
|
|
||||||
setActiveUserId,
|
|
||||||
}: TicketListProps) {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const tickets = useTicketStore((state) => state.tickets);
|
const tickets = useTicketStore((state) => state.tickets);
|
||||||
const ticketsFetchState = useTicketStore((state) => state.ticketsFetchState);
|
const ticketsFetchState = useTicketStore((state) => state.ticketsFetchState);
|
||||||
const ticketsBoxRef = useRef<HTMLDivElement>(null);
|
const ticketsBoxRef = useRef<HTMLDivElement>(null);
|
||||||
const ticketId = useParams().ticketId;
|
const ticketId = useParams().ticketId;
|
||||||
const [openModal, setOpenModal] = useState(false)
|
const [openModal, setOpenModal] = useState(false);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
function updateCurrentPageOnScroll() {
|
function updateCurrentPageOnScroll() {
|
||||||
@ -34,15 +31,8 @@ export default function TicketList({
|
|||||||
|
|
||||||
const ticketsBox = ticketsBoxRef.current;
|
const ticketsBox = ticketsBoxRef.current;
|
||||||
const scrollHandler = () => {
|
const scrollHandler = () => {
|
||||||
const scrollBottom =
|
const scrollBottom = ticketsBox.scrollHeight - ticketsBox.scrollTop - ticketsBox.clientHeight;
|
||||||
ticketsBox.scrollHeight -
|
if (scrollBottom < ticketsBox.clientHeight && ticketsFetchState === "idle") incrementTicketsApiPage();
|
||||||
ticketsBox.scrollTop -
|
|
||||||
ticketsBox.clientHeight;
|
|
||||||
if (
|
|
||||||
scrollBottom < ticketsBox.clientHeight &&
|
|
||||||
ticketsFetchState === "idle"
|
|
||||||
)
|
|
||||||
incrementTicketsApiPage();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const throttledScrollHandler = throttle(scrollHandler, 200);
|
const throttledScrollHandler = throttle(scrollHandler, 200);
|
||||||
@ -55,9 +45,7 @@ export default function TicketList({
|
|||||||
[ticketsFetchState]
|
[ticketsFetchState]
|
||||||
);
|
);
|
||||||
|
|
||||||
const sortedTickets = tickets
|
const sortedTickets = tickets.sort(sortTicketsByUpdateTime).sort(sortTicketsByUnread);
|
||||||
.sort(sortTicketsByUpdateTime)
|
|
||||||
.sort(sortTicketsByUnread);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
@ -2,9 +2,8 @@ import * as React from "react";
|
|||||||
import { Box, Typography, Button } from "@mui/material";
|
import { Box, Typography, Button } from "@mui/material";
|
||||||
import theme from "../../../../../theme";
|
import theme from "../../../../../theme";
|
||||||
|
|
||||||
|
|
||||||
export interface MWProps {
|
export interface MWProps {
|
||||||
openModal: (type:number, num: number) => void
|
openModal: (type: number, num: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Contractor: React.FC<MWProps> = ({ openModal }) => {
|
const Contractor: React.FC<MWProps> = ({ openModal }) => {
|
||||||
@ -19,30 +18,34 @@ const Contractor: React.FC<MWProps> = ({ openModal }) => {
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Сокращатель ссылок
|
Сокращатель ссылок
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
marginTop: "35px",
|
marginTop: "35px",
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: "repeat(2, 1fr)",
|
gridTemplateColumns: "repeat(2, 1fr)",
|
||||||
gridGap: "20px",
|
gridGap: "20px",
|
||||||
marginBottom: "120px",
|
marginBottom: "120px",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => openModal(3, 1)}
|
onClick={() => openModal(3, 1)}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.menu.main,
|
backgroundColor: theme.palette.menu.main,
|
||||||
padding: '11px 65px',
|
padding: "11px 65px",
|
||||||
fontWeight: "normal",
|
fontWeight: "normal",
|
||||||
fontSize: "17px",
|
fontSize: "17px",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.grayMedium.main
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
}
|
},
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Создать тариф <br /> на аналитику время
|
Создать тариф <br /> на аналитику время
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@ -50,32 +53,33 @@ const Contractor: React.FC<MWProps> = ({ openModal }) => {
|
|||||||
onClick={() => openModal(3, 1)}
|
onClick={() => openModal(3, 1)}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.menu.main,
|
backgroundColor: theme.palette.menu.main,
|
||||||
padding: '11px 65px',
|
padding: "11px 65px",
|
||||||
fontWeight: "normal",
|
fontWeight: "normal",
|
||||||
fontSize: "17px",
|
fontSize: "17px",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.grayMedium.main
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
}
|
},
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Создать тариф <br /> на a/b тесты время
|
Создать тариф <br /> на a/b тесты время
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.menu.main,
|
backgroundColor: theme.palette.menu.main,
|
||||||
padding: '11px 65px',
|
padding: "11px 65px",
|
||||||
fontWeight: "normal",
|
fontWeight: "normal",
|
||||||
fontSize: "17px",
|
fontSize: "17px",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.grayMedium.main
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
}
|
},
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Изменить тариф
|
Изменить тариф
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export default Contractor;
|
export default Contractor;
|
@ -7,28 +7,26 @@ import {
|
|||||||
FormControl,
|
FormControl,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
useTheme,
|
useTheme,
|
||||||
Box, TextField,
|
Box,
|
||||||
|
TextField,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { requestTariffs } from "@root/services/tariffs.service";
|
import { requestTariffs } from "@root/services/tariffs.service";
|
||||||
import { createTariff } from "@root/api/tariffs";
|
import { createTariff } from "@root/api/tariffs";
|
||||||
|
|
||||||
import {
|
import { findPrivilegeById, usePrivilegeStore } from "@root/stores/privilegesStore";
|
||||||
findPrivilegeById,
|
|
||||||
usePrivilegeStore,
|
|
||||||
} from "@root/stores/privilegesStore";
|
|
||||||
import { Privilege } from "@frontend/kitui";
|
import { Privilege } from "@frontend/kitui";
|
||||||
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
||||||
import { Formik, Field, Form, FormikHelpers } from "formik";
|
import { Formik, Field, Form, FormikHelpers } from "formik";
|
||||||
|
|
||||||
interface Values {
|
interface Values {
|
||||||
nameField: string,
|
nameField: string;
|
||||||
descriptionField: string,
|
descriptionField: string;
|
||||||
amountField: string,
|
amountField: string;
|
||||||
customPriceField: string,
|
customPriceField: string;
|
||||||
privilegeIdField: string,
|
privilegeIdField: string;
|
||||||
orderField: number,
|
orderField: number;
|
||||||
privilege: Privilege | null
|
privilege: Privilege | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CreateTariff() {
|
export default function CreateTariff() {
|
||||||
@ -40,16 +38,15 @@ export default function CreateTariff() {
|
|||||||
const errors = {} as any;
|
const errors = {} as any;
|
||||||
|
|
||||||
if (values.nameField.length === 0) {
|
if (values.nameField.length === 0) {
|
||||||
errors.nameField = "Пустое название тарифа"
|
errors.nameField = "Пустое название тарифа";
|
||||||
}
|
}
|
||||||
if (values.amountField.length === 0) {
|
if (values.amountField.length === 0) {
|
||||||
errors.amountField = "Пустое кол-во едениц привилегии"
|
errors.amountField = "Пустое кол-во едениц привилегии";
|
||||||
}
|
}
|
||||||
if (values.privilegeIdField.length === 0) {
|
if (values.privilegeIdField.length === 0) {
|
||||||
errors.privilegeIdField = "Не выбрана привилегия"
|
errors.privilegeIdField = "Не выбрана привилегия";
|
||||||
}
|
}
|
||||||
return errors;
|
return errors;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialValues: Values = {
|
const initialValues: Values = {
|
||||||
@ -59,13 +56,10 @@ export default function CreateTariff() {
|
|||||||
customPriceField: "",
|
customPriceField: "",
|
||||||
privilegeIdField: "",
|
privilegeIdField: "",
|
||||||
orderField: 0,
|
orderField: 0,
|
||||||
privilege: null
|
privilege: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const createTariffBackend = async (
|
const createTariffBackend = async (values: Values, formikHelpers: FormikHelpers<Values>) => {
|
||||||
values: Values,
|
|
||||||
formikHelpers: FormikHelpers<Values>
|
|
||||||
) => {
|
|
||||||
if (values.privilege !== null) {
|
if (values.privilege !== null) {
|
||||||
const [, createdTariffError] = await createTariff({
|
const [, createdTariffError] = await createTariff({
|
||||||
name: values.nameField,
|
name: values.nameField,
|
||||||
@ -111,11 +105,7 @@ export default function CreateTariff() {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Formik initialValues={initialValues} validate={checkFulledFields} onSubmit={createTariffBackend}>
|
||||||
initialValues={initialValues}
|
|
||||||
validate={checkFulledFields}
|
|
||||||
onSubmit={createTariffBackend}
|
|
||||||
>
|
|
||||||
{(props) => (
|
{(props) => (
|
||||||
<Form style={{ width: "100%" }}>
|
<Form style={{ width: "100%" }}>
|
||||||
<Container
|
<Container
|
||||||
@ -126,7 +116,6 @@ export default function CreateTariff() {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: "12px",
|
gap: "12px",
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h6" sx={{ textAlign: "center", mb: "16px" }}>
|
<Typography variant="h6" sx={{ textAlign: "center", mb: "16px" }}>
|
||||||
@ -162,13 +151,13 @@ export default function CreateTariff() {
|
|||||||
label="Привилегия"
|
label="Привилегия"
|
||||||
error={props.touched.privilegeIdField && !!props.errors.privilegeIdField}
|
error={props.touched.privilegeIdField && !!props.errors.privilegeIdField}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
console.log(e.target.value)
|
console.log(e.target.value);
|
||||||
console.log(findPrivilegeById(e.target.value))
|
console.log(findPrivilegeById(e.target.value));
|
||||||
if (findPrivilegeById(e.target.value) === null) {
|
if (findPrivilegeById(e.target.value) === null) {
|
||||||
return enqueueSnackbar("Привилегия не найдена");
|
return enqueueSnackbar("Привилегия не найдена");
|
||||||
}
|
}
|
||||||
props.setFieldValue("privilegeIdField", e.target.value)
|
props.setFieldValue("privilegeIdField", e.target.value);
|
||||||
props.setFieldValue("privilege", findPrivilegeById(e.target.value))
|
props.setFieldValue("privilege", findPrivilegeById(e.target.value));
|
||||||
}}
|
}}
|
||||||
onBlur={props.handleBlur}
|
onBlur={props.handleBlur}
|
||||||
sx={{
|
sx={{
|
||||||
@ -213,7 +202,8 @@ export default function CreateTariff() {
|
|||||||
Единица: <span>{props.values.privilege.type}</span>
|
Единица: <span>{props.values.privilege.type}</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography>
|
<Typography>
|
||||||
Стандартная цена за единицу: <span>{currencyFormatter.format(props.values.privilege.price / 100)}</span>
|
Стандартная цена за единицу:{" "}
|
||||||
|
<span>{currencyFormatter.format(props.values.privilege.price / 100)}</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@ -225,21 +215,17 @@ export default function CreateTariff() {
|
|||||||
label="Название тарифа"
|
label="Название тарифа"
|
||||||
type="text"
|
type="text"
|
||||||
error={props.touched.nameField && !!props.errors.nameField}
|
error={props.touched.nameField && !!props.errors.nameField}
|
||||||
helperText={
|
helperText={<Typography sx={{ fontSize: "12px", width: "200px" }}>{props.errors.nameField}</Typography>}
|
||||||
<Typography sx={{ fontSize: "12px", width: "200px" }}>
|
|
||||||
{props.errors.nameField}
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
style: {
|
style: {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
@ -247,27 +233,23 @@ export default function CreateTariff() {
|
|||||||
name="amountField"
|
name="amountField"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
props.setFieldValue("amountField", e.target.value.replace(/[^\d]/g, ''))
|
props.setFieldValue("amountField", e.target.value.replace(/[^\d]/g, ""));
|
||||||
}}
|
}}
|
||||||
value={props.values.amountField}
|
value={props.values.amountField}
|
||||||
onBlur={props.handleBlur}
|
onBlur={props.handleBlur}
|
||||||
label="Кол-во единиц привилегии"
|
label="Кол-во единиц привилегии"
|
||||||
error={props.touched.amountField && !!props.errors.amountField}
|
error={props.touched.amountField && !!props.errors.amountField}
|
||||||
helperText={
|
helperText={<Typography sx={{ fontSize: "12px", width: "200px" }}>{props.errors.amountField}</Typography>}
|
||||||
<Typography sx={{ fontSize: "12px", width: "200px" }}>
|
|
||||||
{props.errors.amountField}
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
style: {
|
style: {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
@ -275,7 +257,7 @@ export default function CreateTariff() {
|
|||||||
name="customPriceField"
|
name="customPriceField"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
props.setFieldValue("customPriceField", e.target.value.replace(/[^\d]/g, ''))
|
props.setFieldValue("customPriceField", e.target.value.replace(/[^\d]/g, ""));
|
||||||
}}
|
}}
|
||||||
value={props.values.customPriceField}
|
value={props.values.customPriceField}
|
||||||
onBlur={props.handleBlur}
|
onBlur={props.handleBlur}
|
||||||
@ -284,12 +266,12 @@ export default function CreateTariff() {
|
|||||||
style: {
|
style: {
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
style: {
|
style: {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
@ -297,7 +279,7 @@ export default function CreateTariff() {
|
|||||||
name="descriptionField"
|
name="descriptionField"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
props.setFieldValue("descriptionField", e.target.value)
|
props.setFieldValue("descriptionField", e.target.value);
|
||||||
}}
|
}}
|
||||||
value={props.values.descriptionField}
|
value={props.values.descriptionField}
|
||||||
onBlur={props.handleBlur}
|
onBlur={props.handleBlur}
|
||||||
@ -307,12 +289,12 @@ export default function CreateTariff() {
|
|||||||
style: {
|
style: {
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
style: {
|
style: {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
@ -320,7 +302,7 @@ export default function CreateTariff() {
|
|||||||
name="orderField"
|
name="orderField"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
props.setFieldValue("orderField", e.target.value)
|
props.setFieldValue("orderField", e.target.value);
|
||||||
}}
|
}}
|
||||||
value={props.values.orderField}
|
value={props.values.orderField}
|
||||||
onBlur={props.handleBlur}
|
onBlur={props.handleBlur}
|
||||||
@ -329,26 +311,21 @@ export default function CreateTariff() {
|
|||||||
style: {
|
style: {
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
type={'number'}
|
type={"number"}
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
style: {
|
style: {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button className="btn_createTariffBackend" type="submit" disabled={props.isSubmitting}>
|
||||||
className="btn_createTariffBackend"
|
|
||||||
type="submit"
|
|
||||||
disabled={props.isSubmitting}
|
|
||||||
>
|
|
||||||
Создать
|
Создать
|
||||||
</Button>
|
</Button>
|
||||||
</Container>
|
</Container>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Button, SxProps, Theme, useTheme } from "@mui/material";
|
import { Button, SxProps, Theme, useTheme } from "@mui/material";
|
||||||
import { MouseEventHandler, ReactNode } from "react";
|
import { MouseEventHandler, ReactNode } from "react";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -17,11 +16,11 @@ export default function CustomButton({ onClick, children, sx }: Props) {
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.menu.main,
|
backgroundColor: theme.palette.menu.main,
|
||||||
padding: '11px 25px',
|
padding: "11px 25px",
|
||||||
fontWeight: "normal",
|
fontWeight: "normal",
|
||||||
fontSize: "17px",
|
fontSize: "17px",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.grayMedium.main
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
},
|
},
|
||||||
...sx,
|
...sx,
|
||||||
}}
|
}}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Typography, useTheme } from "@mui/material";
|
import { Typography, useTheme } from "@mui/material";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
@ -19,7 +18,7 @@ export default function CustomHeader({ children }: Props) {
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -8,14 +8,11 @@ import { enqueueSnackbar } from "notistack";
|
|||||||
import { deleteTariff } from "@root/api/tariffs";
|
import { deleteTariff } from "@root/api/tariffs";
|
||||||
import { devlog } from "@frontend/kitui";
|
import { devlog } from "@frontend/kitui";
|
||||||
|
|
||||||
|
|
||||||
export default function DeleteModal() {
|
export default function DeleteModal() {
|
||||||
const deleteTariffIds = useTariffStore(state => state.deleteTariffIds);
|
const deleteTariffIds = useTariffStore((state) => state.deleteTariffIds);
|
||||||
|
|
||||||
async function deleteManyTariffs(tariffIds: string[]) {
|
async function deleteManyTariffs(tariffIds: string[]) {
|
||||||
const results = await Promise.allSettled(
|
const results = await Promise.allSettled(tariffIds.map((tariffId) => deleteTariff(tariffId)));
|
||||||
tariffIds.map((tariffId) => deleteTariff(tariffId))
|
|
||||||
);
|
|
||||||
|
|
||||||
let deletedCount = 0;
|
let deletedCount = 0;
|
||||||
let errorCount = 0;
|
let errorCount = 0;
|
||||||
@ -42,7 +39,7 @@ export default function DeleteModal() {
|
|||||||
closeDeleteTariffDialog();
|
closeDeleteTariffDialog();
|
||||||
|
|
||||||
requestTariffs();
|
requestTariffs();
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@ -77,10 +74,7 @@ export default function DeleteModal() {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button onClick={() => handleTariffDeleteClick()} sx={{ width: "40px", height: "25px" }}>
|
||||||
onClick={() => handleTariffDeleteClick()}
|
|
||||||
sx={{ width: "40px", height: "25px" }}
|
|
||||||
>
|
|
||||||
Да
|
Да
|
||||||
</Button>
|
</Button>
|
||||||
{/* <Typography>Тариф:</Typography>
|
{/* <Typography>Тариф:</Typography>
|
||||||
|
@ -36,8 +36,7 @@ export default function EditModal() {
|
|||||||
|
|
||||||
const price = parseFloat(priceField);
|
const price = parseFloat(priceField);
|
||||||
|
|
||||||
if (!isFinite(price))
|
if (!isFinite(price)) return enqueueSnackbar('Поле "Цена за единицу" не число');
|
||||||
return enqueueSnackbar('Поле "Цена за единицу" не число');
|
|
||||||
if (!nameField) return enqueueSnackbar('Поле "Имя" пустое');
|
if (!nameField) return enqueueSnackbar('Поле "Имя" пустое');
|
||||||
|
|
||||||
const updatedTariff = structuredClone(tariff);
|
const updatedTariff = structuredClone(tariff);
|
||||||
@ -78,12 +77,7 @@ export default function EditModal() {
|
|||||||
p: 4,
|
p: 4,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography id="modal-modal-title" variant="h6" component="h2" sx={{ whiteSpace: "nowrap" }}>
|
||||||
id="modal-modal-title"
|
|
||||||
variant="h6"
|
|
||||||
component="h2"
|
|
||||||
sx={{ whiteSpace: "nowrap" }}
|
|
||||||
>
|
|
||||||
Редактирование тариффа
|
Редактирование тариффа
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
@ -97,14 +91,10 @@ export default function EditModal() {
|
|||||||
value={nameField}
|
value={nameField}
|
||||||
sx={{ marginBottom: "10px" }}
|
sx={{ marginBottom: "10px" }}
|
||||||
/>
|
/>
|
||||||
<Typography>
|
<Typography>Цена: {Math.trunc((tariff.price ?? 0) / 100)}</Typography>
|
||||||
Цена: {Math.trunc((tariff.price ?? 0) / 100)}
|
|
||||||
</Typography>
|
|
||||||
<TextField
|
<TextField
|
||||||
type="number"
|
type="number"
|
||||||
onChange={({ target }) =>
|
onChange={({ target }) => setPriceField(String(+target.value * 100))}
|
||||||
setPriceField(String(+target.value * 100))
|
|
||||||
}
|
|
||||||
label="Цена"
|
label="Цена"
|
||||||
name="price"
|
name="price"
|
||||||
value={Math.trunc(Number(priceField) / 100)}
|
value={Math.trunc(Number(priceField) / 100)}
|
||||||
|
@ -2,9 +2,8 @@ import * as React from "react";
|
|||||||
import { Box, Typography, Button } from "@mui/material";
|
import { Box, Typography, Button } from "@mui/material";
|
||||||
import theme from "../../../../../theme";
|
import theme from "../../../../../theme";
|
||||||
|
|
||||||
|
|
||||||
export interface MWProps {
|
export interface MWProps {
|
||||||
openModal: (type:number, num: number) => void
|
openModal: (type: number, num: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Quiz: React.FC<MWProps> = ({ openModal }) => {
|
const Quiz: React.FC<MWProps> = ({ openModal }) => {
|
||||||
@ -19,18 +18,21 @@ const Quiz: React.FC<MWProps> = ({ openModal }) => {
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Опросник
|
Опросник
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
marginTop: "35px",
|
marginTop: "35px",
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: "repeat(2, 1fr)",
|
gridTemplateColumns: "repeat(2, 1fr)",
|
||||||
gridGap: "20px",
|
gridGap: "20px",
|
||||||
marginBottom: "120px",
|
marginBottom: "120px",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => openModal(2, 1)}
|
onClick={() => openModal(2, 1)}
|
||||||
@ -40,9 +42,10 @@ const Quiz: React.FC<MWProps> = ({ openModal }) => {
|
|||||||
fontWeight: "normal",
|
fontWeight: "normal",
|
||||||
fontSize: "17px",
|
fontSize: "17px",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.grayMedium.main
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
}
|
},
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Создать тариф на время
|
Создать тариф на время
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@ -50,32 +53,33 @@ const Quiz: React.FC<MWProps> = ({ openModal }) => {
|
|||||||
onClick={() => openModal(2, 0)}
|
onClick={() => openModal(2, 0)}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.menu.main,
|
backgroundColor: theme.palette.menu.main,
|
||||||
padding: '11px 43px',
|
padding: "11px 43px",
|
||||||
fontWeight: "normal",
|
fontWeight: "normal",
|
||||||
fontSize: "17px",
|
fontSize: "17px",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.grayMedium.main
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
}
|
},
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Создать тариф на объем
|
Создать тариф на объем
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.menu.main,
|
backgroundColor: theme.palette.menu.main,
|
||||||
padding: '11px 43px',
|
padding: "11px 43px",
|
||||||
fontWeight: "normal",
|
fontWeight: "normal",
|
||||||
fontSize: "17px",
|
fontSize: "17px",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.grayMedium.main
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
}
|
},
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Изменить тариф
|
Изменить тариф
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export default Quiz;
|
export default Quiz;
|
@ -4,9 +4,7 @@ import TariffsDG from "./tariffsDG";
|
|||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
|
|
||||||
|
|
||||||
export default function TariffsInfo() {
|
export default function TariffsInfo() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography variant="h6" mt="20px">
|
<Typography variant="h6" mt="20px">
|
||||||
|
@ -2,9 +2,8 @@ import * as React from "react";
|
|||||||
import { Box, Typography, Button } from "@mui/material";
|
import { Box, Typography, Button } from "@mui/material";
|
||||||
import theme from "../../../../../theme";
|
import theme from "../../../../../theme";
|
||||||
|
|
||||||
|
|
||||||
export interface MWProps {
|
export interface MWProps {
|
||||||
openModal: (type:number, num: number) => void
|
openModal: (type: number, num: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Templater: React.FC<MWProps> = ({ openModal }) => {
|
const Templater: React.FC<MWProps> = ({ openModal }) => {
|
||||||
@ -19,18 +18,21 @@ const Templater: React.FC<MWProps> = ({ openModal }) => {
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Шаблонизатор документов
|
Шаблонизатор документов
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
marginTop: "35px",
|
marginTop: "35px",
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: "repeat(2, 1fr)",
|
gridTemplateColumns: "repeat(2, 1fr)",
|
||||||
gridGap: "20px",
|
gridGap: "20px",
|
||||||
marginBottom: "120px",
|
marginBottom: "120px",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => openModal(1, 1)}
|
onClick={() => openModal(1, 1)}
|
||||||
@ -38,11 +40,12 @@ const Templater: React.FC<MWProps> = ({ openModal }) => {
|
|||||||
backgroundColor: theme.palette.menu.main,
|
backgroundColor: theme.palette.menu.main,
|
||||||
fontWeight: "normal",
|
fontWeight: "normal",
|
||||||
fontSize: "17px",
|
fontSize: "17px",
|
||||||
padding: '11px 25px',
|
padding: "11px 25px",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.grayMedium.main
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
}
|
},
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Создать тариф на время
|
Создать тариф на время
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@ -50,13 +53,14 @@ const Templater: React.FC<MWProps> = ({ openModal }) => {
|
|||||||
onClick={() => openModal(1, 0)}
|
onClick={() => openModal(1, 0)}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.menu.main,
|
backgroundColor: theme.palette.menu.main,
|
||||||
padding: '11px 25px',
|
padding: "11px 25px",
|
||||||
fontWeight: "normal",
|
fontWeight: "normal",
|
||||||
fontSize: "17px",
|
fontSize: "17px",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.grayMedium.main
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
}
|
},
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Создать тариф на объем
|
Создать тариф на объем
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@ -64,32 +68,33 @@ const Templater: React.FC<MWProps> = ({ openModal }) => {
|
|||||||
onClick={() => openModal(1, 2)}
|
onClick={() => openModal(1, 2)}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.menu.main,
|
backgroundColor: theme.palette.menu.main,
|
||||||
padding: '11px 25px',
|
padding: "11px 25px",
|
||||||
fontWeight: "normal",
|
fontWeight: "normal",
|
||||||
fontSize: "17px",
|
fontSize: "17px",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.grayMedium.main
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
}
|
},
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Создать тариф на гигабайты
|
Создать тариф на гигабайты
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: theme.palette.menu.main,
|
backgroundColor: theme.palette.menu.main,
|
||||||
padding: '11px 25px',
|
padding: "11px 25px",
|
||||||
fontWeight: "normal",
|
fontWeight: "normal",
|
||||||
fontSize: "17px",
|
fontSize: "17px",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.grayMedium.main
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
}
|
},
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Изменить тариф
|
Изменить тариф
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export default Templater;
|
export default Templater;
|
@ -7,12 +7,16 @@ import DeleteModal from "@root/pages/dashboard/Content/Tariffs/DeleteModal";
|
|||||||
import EditModal from "./EditModal";
|
import EditModal from "./EditModal";
|
||||||
import AutorenewIcon from "@mui/icons-material/Autorenew";
|
import AutorenewIcon from "@mui/icons-material/Autorenew";
|
||||||
import { requestTariffs } from "@root/services/tariffs.service";
|
import { requestTariffs } from "@root/services/tariffs.service";
|
||||||
import { openDeleteTariffDialog, openEditTariffDialog, setSelectedTariffIds, useTariffStore } from "@root/stores/tariffs";
|
import {
|
||||||
|
openDeleteTariffDialog,
|
||||||
|
openEditTariffDialog,
|
||||||
|
setSelectedTariffIds,
|
||||||
|
useTariffStore,
|
||||||
|
} from "@root/stores/tariffs";
|
||||||
import { Tariff } from "@frontend/kitui";
|
import { Tariff } from "@frontend/kitui";
|
||||||
import { getTariffPrice } from "@root/utils/tariffPrice";
|
import { getTariffPrice } from "@root/utils/tariffPrice";
|
||||||
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
||||||
|
|
||||||
|
|
||||||
const columns: GridColDef<Tariff, string | number>[] = [
|
const columns: GridColDef<Tariff, string | number>[] = [
|
||||||
{ field: "_id", headerName: "ID", width: 100, valueGetter: ({ row }) => row.order || 1 },
|
{ field: "_id", headerName: "ID", width: 100, valueGetter: ({ row }) => row.order || 1 },
|
||||||
{
|
{
|
||||||
@ -32,9 +36,24 @@ const columns: GridColDef<Tariff, string | number>[] = [
|
|||||||
{ field: "serviceName", headerName: "Сервис", width: 150, valueGetter: ({ row }) => row.privileges[0].serviceKey },
|
{ field: "serviceName", headerName: "Сервис", width: 150, valueGetter: ({ row }) => row.privileges[0].serviceKey },
|
||||||
{ field: "privilegeName", headerName: "Привилегия", width: 150, valueGetter: ({ row }) => row.privileges[0].name },
|
{ field: "privilegeName", headerName: "Привилегия", width: 150, valueGetter: ({ row }) => row.privileges[0].name },
|
||||||
{ field: "type", headerName: "Единица", width: 100, valueGetter: ({ row }) => row.privileges[0].type },
|
{ field: "type", headerName: "Единица", width: 100, valueGetter: ({ row }) => row.privileges[0].type },
|
||||||
{ field: "pricePerUnit", headerName: "Цена за ед.", width: 100, valueGetter: ({ row }) => currencyFormatter.format(row.privileges[0].price / 100) },
|
{
|
||||||
{ field: "isCustom", headerName: "Кастомная цена", width: 130, valueGetter: ({ row }) => row.isCustom ? "Да" : "Нет" },
|
field: "pricePerUnit",
|
||||||
{ field: "total", headerName: "Сумма", width: 100, valueGetter: ({ row }) => currencyFormatter.format(getTariffPrice(row) / 100) },
|
headerName: "Цена за ед.",
|
||||||
|
width: 100,
|
||||||
|
valueGetter: ({ row }) => currencyFormatter.format(row.privileges[0].price / 100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "isCustom",
|
||||||
|
headerName: "Кастомная цена",
|
||||||
|
width: 130,
|
||||||
|
valueGetter: ({ row }) => (row.isCustom ? "Да" : "Нет"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "total",
|
||||||
|
headerName: "Сумма",
|
||||||
|
width: 100,
|
||||||
|
valueGetter: ({ row }) => currencyFormatter.format(getTariffPrice(row) / 100),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: "delete",
|
field: "delete",
|
||||||
headerName: "Удаление",
|
headerName: "Удаление",
|
||||||
@ -51,7 +70,7 @@ const columns: GridColDef<Tariff, string | number>[] = [
|
|||||||
|
|
||||||
export default function TariffsDG() {
|
export default function TariffsDG() {
|
||||||
const tariffs = useTariffStore((state) => state.tariffs);
|
const tariffs = useTariffStore((state) => state.tariffs);
|
||||||
const selectedTariffIds = useTariffStore(state => state.selectedTariffIds);
|
const selectedTariffIds = useTariffStore((state) => state.selectedTariffIds);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -84,7 +103,7 @@ export default function TariffsDG() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => openDeleteTariffDialog(selectedTariffIds.map(s => s.toString()))}
|
onClick={() => openDeleteTariffDialog(selectedTariffIds.map((s) => s.toString()))}
|
||||||
sx={{ mr: "20px", zIndex: "10000" }}
|
sx={{ mr: "20px", zIndex: "10000" }}
|
||||||
>
|
>
|
||||||
Удалить выделенные
|
Удалить выделенные
|
||||||
|
@ -89,18 +89,9 @@ const Users: React.FC = () => {
|
|||||||
const [activeUserId, setActiveUserId] = useState<string>("");
|
const [activeUserId, setActiveUserId] = useState<string>("");
|
||||||
|
|
||||||
const { userId } = useParams();
|
const { userId } = useParams();
|
||||||
const { data: adminData, adminPages } = useAdmins(
|
const { data: adminData, adminPages } = useAdmins(page.adminPage + 1, pageSize.adminPageSize);
|
||||||
page.adminPage + 1,
|
const { data: managerData, managerPages } = useManagers(page.managerPage + 1, pageSize.managerPageSize);
|
||||||
pageSize.adminPageSize
|
const { data: userData, userPagesCount } = useUsers(page.userPage + 1, pageSize.userPageSize);
|
||||||
);
|
|
||||||
const { data: managerData, managerPages } = useManagers(
|
|
||||||
page.managerPage + 1,
|
|
||||||
pageSize.managerPageSize
|
|
||||||
);
|
|
||||||
const { data: userData, userPagesCount } = useUsers(
|
|
||||||
page.userPage + 1,
|
|
||||||
pageSize.userPageSize
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleChangeData();
|
handleChangeData();
|
||||||
@ -126,9 +117,7 @@ const Users: React.FC = () => {
|
|||||||
});
|
});
|
||||||
}, [selectedValue]);
|
}, [selectedValue]);
|
||||||
|
|
||||||
const [selectedTariffs, setSelectedTariffs] = useState<GridSelectionModel>(
|
const [selectedTariffs, setSelectedTariffs] = useState<GridSelectionModel>([]);
|
||||||
[]
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{/* <Button
|
{/* <Button
|
||||||
@ -163,9 +152,7 @@ const Users: React.FC = () => {
|
|||||||
<AccordionSummary
|
<AccordionSummary
|
||||||
sx={{ display: "flex" }}
|
sx={{ display: "flex" }}
|
||||||
onClick={handleToggleAccordion}
|
onClick={handleToggleAccordion}
|
||||||
expandIcon={
|
expandIcon={<ExpandMoreIcon sx={{ color: theme.palette.secondary.main }} />}
|
||||||
<ExpandMoreIcon sx={{ color: theme.palette.secondary.main }} />
|
|
||||||
}
|
|
||||||
aria-controls="panel1a-content"
|
aria-controls="panel1a-content"
|
||||||
id="panel1a-header"
|
id="panel1a-header"
|
||||||
>
|
>
|
||||||
@ -417,45 +404,33 @@ const Users: React.FC = () => {
|
|||||||
<ServiceUsersDG
|
<ServiceUsersDG
|
||||||
users={adminData?.users.length ? adminData.users : []}
|
users={adminData?.users.length ? adminData.users : []}
|
||||||
page={page.adminPage}
|
page={page.adminPage}
|
||||||
setPage={(adminPage) =>
|
setPage={(adminPage) => setPage((pages) => ({ ...pages, adminPage }))}
|
||||||
setPage((pages) => ({ ...pages, adminPage }))
|
|
||||||
}
|
|
||||||
pagesCount={adminPages}
|
pagesCount={adminPages}
|
||||||
pageSize={pageSize.adminPageSize}
|
pageSize={pageSize.adminPageSize}
|
||||||
handleSelectionChange={setSelectedTariffs}
|
handleSelectionChange={setSelectedTariffs}
|
||||||
onPageSizeChange={(adminPageSize) =>
|
onPageSizeChange={(adminPageSize) => setPageSize((pageSize) => ({ ...pageSize, adminPageSize }))}
|
||||||
setPageSize((pageSize) => ({ ...pageSize, adminPageSize }))
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
childrenManager={
|
childrenManager={
|
||||||
<ServiceUsersDG
|
<ServiceUsersDG
|
||||||
users={managerData?.users.length ? managerData.users : []}
|
users={managerData?.users.length ? managerData.users : []}
|
||||||
page={page.managerPage}
|
page={page.managerPage}
|
||||||
setPage={(managerPage) =>
|
setPage={(managerPage) => setPage((pages) => ({ ...pages, managerPage }))}
|
||||||
setPage((pages) => ({ ...pages, managerPage }))
|
|
||||||
}
|
|
||||||
pagesCount={managerPages}
|
pagesCount={managerPages}
|
||||||
pageSize={pageSize.managerPageSize}
|
pageSize={pageSize.managerPageSize}
|
||||||
handleSelectionChange={setSelectedTariffs}
|
handleSelectionChange={setSelectedTariffs}
|
||||||
onPageSizeChange={(managerPageSize) =>
|
onPageSizeChange={(managerPageSize) => setPageSize((pageSize) => ({ ...pageSize, managerPageSize }))}
|
||||||
setPageSize((pageSize) => ({ ...pageSize, managerPageSize }))
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
childrenUser={
|
childrenUser={
|
||||||
<ServiceUsersDG
|
<ServiceUsersDG
|
||||||
users={userData?.users.length ? userData.users : []}
|
users={userData?.users.length ? userData.users : []}
|
||||||
page={page.userPage}
|
page={page.userPage}
|
||||||
setPage={(userPage) =>
|
setPage={(userPage) => setPage((pages) => ({ ...pages, userPage }))}
|
||||||
setPage((pages) => ({ ...pages, userPage }))
|
|
||||||
}
|
|
||||||
pagesCount={userPagesCount}
|
pagesCount={userPagesCount}
|
||||||
pageSize={pageSize.userPageSize}
|
pageSize={pageSize.userPageSize}
|
||||||
handleSelectionChange={setSelectedTariffs}
|
handleSelectionChange={setSelectedTariffs}
|
||||||
onPageSizeChange={(userPageSize) =>
|
onPageSizeChange={(userPageSize) => setPageSize((pageSize) => ({ ...pageSize, userPageSize }))}
|
||||||
setPageSize((pageSize) => ({ ...pageSize, userPageSize }))
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -4,7 +4,7 @@ import { Box, Modal, Fade, Backdrop, Typography } from "@mui/material";
|
|||||||
import theme from "../../../theme";
|
import theme from "../../../theme";
|
||||||
|
|
||||||
export interface MWProps {
|
export interface MWProps {
|
||||||
open: boolean
|
open: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalAdmin = ({ open }: MWProps) => {
|
const ModalAdmin = ({ open }: MWProps) => {
|
||||||
@ -14,7 +14,7 @@ const ModalAdmin = ({open}: MWProps ) => {
|
|||||||
aria-labelledby="transition-modal-title"
|
aria-labelledby="transition-modal-title"
|
||||||
aria-describedby="transition-modal-description"
|
aria-describedby="transition-modal-description"
|
||||||
open={open}
|
open={open}
|
||||||
onClose={ useLinkClickHandler('/users') }
|
onClose={useLinkClickHandler("/users")}
|
||||||
closeAfterTransition
|
closeAfterTransition
|
||||||
BackdropComponent={Backdrop}
|
BackdropComponent={Backdrop}
|
||||||
BackdropProps={{
|
BackdropProps={{
|
||||||
@ -22,8 +22,9 @@ const ModalAdmin = ({open}: MWProps ) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Fade in={open}>
|
<Fade in={open}>
|
||||||
<Box sx={{
|
<Box
|
||||||
position: "absolute" as "absolute",
|
sx={{
|
||||||
|
position: "absolute" as const,
|
||||||
top: "50%",
|
top: "50%",
|
||||||
left: "50%",
|
left: "50%",
|
||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
@ -35,21 +36,28 @@ const ModalAdmin = ({open}: MWProps ) => {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Typography id="transition-modal-title" variant="caption">
|
<Typography id="transition-modal-title" variant="caption">
|
||||||
Администратор сервиса
|
Администратор сервиса
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
marginTop: "15px",
|
marginTop: "15px",
|
||||||
display: "flex"
|
display: "flex",
|
||||||
}}>
|
}}
|
||||||
<Box sx={{
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
backgroundColor: theme.palette.grayMedium.main,
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
width: "155px"
|
width: "155px",
|
||||||
}}>
|
}}
|
||||||
<Typography variant="h4" sx={{
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
sx={{
|
||||||
backgroundColor: theme.palette.grayMedium.main,
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "55px", //205px
|
height: "55px", //205px
|
||||||
@ -58,10 +66,13 @@ const ModalAdmin = ({open}: MWProps ) => {
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
ТЕКСТ
|
ТЕКСТ
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h4" sx={{
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
sx={{
|
||||||
backgroundColor: theme.palette.grayMedium.main,
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
color: theme.palette.grayDisabled.main,
|
color: theme.palette.grayDisabled.main,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@ -71,11 +82,14 @@ const ModalAdmin = ({open}: MWProps ) => {
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
textAlign: "center"
|
textAlign: "center",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
ТЕКСТ
|
ТЕКСТ
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h4" sx={{
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
sx={{
|
||||||
backgroundColor: theme.palette.grayMedium.main,
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
color: theme.palette.grayDisabled.main,
|
color: theme.palette.grayDisabled.main,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@ -85,11 +99,14 @@ const ModalAdmin = ({open}: MWProps ) => {
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
textAlign: "center"
|
textAlign: "center",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
ТЕКСТ
|
ТЕКСТ
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h4" sx={{
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
sx={{
|
||||||
backgroundColor: theme.palette.grayMedium.main,
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
color: theme.palette.grayDisabled.main,
|
color: theme.palette.grayDisabled.main,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@ -99,23 +116,25 @@ const ModalAdmin = ({open}: MWProps ) => {
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
textAlign: "center"
|
textAlign: "center",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
ТЕКСТ
|
ТЕКСТ
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
backgroundColor: theme.palette.grayMedium.main,
|
backgroundColor: theme.palette.grayMedium.main,
|
||||||
width: "calc(100% - 155px)",
|
width: "calc(100% - 155px)",
|
||||||
height: "55px",
|
height: "55px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center"
|
alignItems: "center",
|
||||||
}}>
|
}}
|
||||||
Long text Long text Long text Long text Long text
|
>
|
||||||
Long text Long text Long text Long text Long text
|
Long text Long text Long text Long text Long text Long text Long text Long text Long text Long text Long
|
||||||
Long text Long text Long text
|
text Long text Long text
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@ -123,7 +142,6 @@ const ModalAdmin = ({open}: MWProps ) => {
|
|||||||
</Modal>
|
</Modal>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export default ModalAdmin;
|
export default ModalAdmin;
|
||||||
|
@ -3,60 +3,60 @@ import { useLinkClickHandler } from "react-router-dom";
|
|||||||
import { Box, Modal, Fade, Backdrop, Typography } from "@mui/material";
|
import { Box, Modal, Fade, Backdrop, Typography } from "@mui/material";
|
||||||
import Tabs from "@mui/material/Tabs";
|
import Tabs from "@mui/material/Tabs";
|
||||||
import Tab from "@mui/material/Tab";
|
import Tab from "@mui/material/Tab";
|
||||||
import { DataGrid, GridColDef } from '@mui/x-data-grid';
|
import { DataGrid, GridColDef } from "@mui/x-data-grid";
|
||||||
import theme from "../../../theme";
|
import theme from "../../../theme";
|
||||||
|
|
||||||
|
|
||||||
export interface MWProps {
|
export interface MWProps {
|
||||||
open: boolean
|
open: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns: GridColDef[] = [
|
const columns: GridColDef[] = [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: "id",
|
||||||
headerName: 'ID',
|
headerName: "ID",
|
||||||
width: 30,
|
width: 30,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'dateTime',
|
field: "dateTime",
|
||||||
headerName: 'Дата / время',
|
headerName: "Дата / время",
|
||||||
width: 150,
|
width: 150,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'email',
|
field: "email",
|
||||||
headerName: 'Почта',
|
headerName: "Почта",
|
||||||
width: 110,
|
|
||||||
sortable: false,
|
|
||||||
},{
|
|
||||||
field: 'summa',
|
|
||||||
headerName: 'Сумма',
|
|
||||||
type: 'number',
|
|
||||||
width: 110,
|
width: 110,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'idLong',
|
field: "summa",
|
||||||
headerName: 'ID long',
|
headerName: "Сумма",
|
||||||
type: 'number',
|
type: "number",
|
||||||
width: 110,
|
width: 110,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'paymentStatus',
|
field: "idLong",
|
||||||
headerName: 'Статус платежа',
|
headerName: "ID long",
|
||||||
|
type: "number",
|
||||||
|
width: 110,
|
||||||
|
sortable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "paymentStatus",
|
||||||
|
headerName: "Статус платежа",
|
||||||
width: 160,
|
width: 160,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const rows = [
|
const rows = [
|
||||||
{ id: 1, dateTime: '22.09.22 12:15', email: 'asd@mail.ru', summa: 100500, idLong: 123, paymentStatus: "В обработке" },
|
{ id: 1, dateTime: "22.09.22 12:15", email: "asd@mail.ru", summa: 100500, idLong: 123, paymentStatus: "В обработке" },
|
||||||
{ id: 1, dateTime: '22.09.22 12:15', email: 'asd@mail.ru', summa: 100500, idLong: 123, paymentStatus: "В обработке" },
|
{ id: 1, dateTime: "22.09.22 12:15", email: "asd@mail.ru", summa: 100500, idLong: 123, paymentStatus: "В обработке" },
|
||||||
{ id: 1, dateTime: '22.09.22 12:15', email: 'asd@mail.ru', summa: 100500, idLong: 123, paymentStatus: "В обработке" },
|
{ id: 1, dateTime: "22.09.22 12:15", email: "asd@mail.ru", summa: 100500, idLong: 123, paymentStatus: "В обработке" },
|
||||||
{ id: 1, dateTime: '22.09.22 12:15', email: 'asd@mail.ru', summa: 100500, idLong: 123, paymentStatus: "В обработке" },
|
{ id: 1, dateTime: "22.09.22 12:15", email: "asd@mail.ru", summa: 100500, idLong: 123, paymentStatus: "В обработке" },
|
||||||
{ id: 1, dateTime: '22.09.22 12:15', email: 'asd@mail.ru', summa: 100500, idLong: 123, paymentStatus: "В обработке" },
|
{ id: 1, dateTime: "22.09.22 12:15", email: "asd@mail.ru", summa: 100500, idLong: 123, paymentStatus: "В обработке" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const ModalEntities = ({ open }: MWProps) => {
|
const ModalEntities = ({ open }: MWProps) => {
|
||||||
@ -80,8 +80,9 @@ const ModalEntities = ({open}: MWProps ) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Fade in={open}>
|
<Fade in={open}>
|
||||||
<Box sx={{
|
<Box
|
||||||
position: "absolute" as "absolute",
|
sx={{
|
||||||
|
position: "absolute" as const,
|
||||||
top: "50%",
|
top: "50%",
|
||||||
left: "50%",
|
left: "50%",
|
||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
@ -93,17 +94,19 @@ const ModalEntities = ({open}: MWProps ) => {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Typography id="transition-modal-title" variant="caption">
|
<Typography id="transition-modal-title" variant="caption">
|
||||||
Юридическое лицо
|
Юридическое лицо
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
marginTop: "50px",
|
marginTop: "50px",
|
||||||
display: "flex"
|
display: "flex",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Tabs
|
<Tabs
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
variant="scrollable"
|
variant="scrollable"
|
||||||
@ -114,25 +117,31 @@ const ModalEntities = ({open}: MWProps ) => {
|
|||||||
borderRight: 1,
|
borderRight: 1,
|
||||||
borderColor: theme.palette.secondary.main,
|
borderColor: theme.palette.secondary.main,
|
||||||
"& .MuiTab-root.Mui-selected": {
|
"& .MuiTab-root.Mui-selected": {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
|
}}
|
||||||
|
TabIndicatorProps={{
|
||||||
|
style: {
|
||||||
|
background: theme.palette.secondary.main,
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
TabIndicatorProps={{style: {
|
|
||||||
background: theme.palette.secondary.main
|
|
||||||
}}}
|
|
||||||
>
|
>
|
||||||
<Tab sx={{
|
<Tab
|
||||||
|
sx={{
|
||||||
color: theme.palette.grayDisabled.main,
|
color: theme.palette.grayDisabled.main,
|
||||||
width: "180px",
|
width: "180px",
|
||||||
fontSize: "15px"
|
fontSize: "15px",
|
||||||
}}
|
}}
|
||||||
label="Загруженные документы" />
|
label="Загруженные документы"
|
||||||
<Tab sx={{
|
/>
|
||||||
|
<Tab
|
||||||
|
sx={{
|
||||||
color: theme.palette.grayDisabled.main,
|
color: theme.palette.grayDisabled.main,
|
||||||
width: "180px",
|
width: "180px",
|
||||||
fontSize: "15px"
|
fontSize: "15px",
|
||||||
}}
|
}}
|
||||||
label="История транзакций" />
|
label="История транзакций"
|
||||||
|
/>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
{value == 0 && (
|
{value == 0 && (
|
||||||
@ -158,26 +167,24 @@ const ModalEntities = ({open}: MWProps ) => {
|
|||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
"& .MuiDataGrid-iconSeparator": {
|
"& .MuiDataGrid-iconSeparator": {
|
||||||
display: "none"
|
display: "none",
|
||||||
},
|
},
|
||||||
"& .css-levciy-MuiTablePagination-displayedRows": {
|
"& .css-levciy-MuiTablePagination-displayedRows": {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
},
|
},
|
||||||
"& .MuiSvgIcon-root": {
|
"& .MuiSvgIcon-root": {
|
||||||
color: theme.palette.secondary.main
|
color: theme.palette.secondary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Fade>
|
</Fade>
|
||||||
</Modal>
|
</Modal>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export default ModalEntities;
|
export default ModalEntities;
|
||||||
|
@ -109,9 +109,7 @@ export const PurchaseTab = ({ userId }: PurchaseTabProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addScrollEvent = () => {
|
const addScrollEvent = () => {
|
||||||
const grid = gridContainer.current?.querySelector(
|
const grid = gridContainer.current?.querySelector(".MuiDataGrid-virtualScroller");
|
||||||
".MuiDataGrid-virtualScroller"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (grid) {
|
if (grid) {
|
||||||
grid.addEventListener("scroll", handleScroll);
|
grid.addEventListener("scroll", handleScroll);
|
||||||
@ -125,18 +123,14 @@ export const PurchaseTab = ({ userId }: PurchaseTabProps) => {
|
|||||||
addScrollEvent();
|
addScrollEvent();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const grid = gridContainer.current?.querySelector(
|
const grid = gridContainer.current?.querySelector(".MuiDataGrid-virtualScroller");
|
||||||
".MuiDataGrid-virtualScroller"
|
|
||||||
);
|
|
||||||
|
|
||||||
grid?.removeEventListener("scroll", handleScroll);
|
grid?.removeEventListener("scroll", handleScroll);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const scrollDataGrid = (toStart = false) => {
|
const scrollDataGrid = (toStart = false) => {
|
||||||
const grid = gridContainer.current?.querySelector(
|
const grid = gridContainer.current?.querySelector(".MuiDataGrid-virtualScroller");
|
||||||
".MuiDataGrid-virtualScroller"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (grid) {
|
if (grid) {
|
||||||
scrollBlock(grid, { left: toStart ? 0 : grid.scrollWidth });
|
scrollBlock(grid, { left: toStart ? 0 : grid.scrollWidth });
|
||||||
@ -224,11 +218,7 @@ export const PurchaseTab = ({ userId }: PurchaseTabProps) => {
|
|||||||
}}
|
}}
|
||||||
onClick={() => scrollDataGrid(true)}
|
onClick={() => scrollDataGrid(true)}
|
||||||
>
|
>
|
||||||
<img
|
<img src={forwardIcon} alt="forward" style={{ transform: "rotate(180deg)" }} />
|
||||||
src={forwardIcon}
|
|
||||||
alt="forward"
|
|
||||||
style={{ transform: "rotate(180deg)" }}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{canScrollToRight && (
|
{canScrollToRight && (
|
||||||
@ -264,8 +254,7 @@ const a = {
|
|||||||
{ Key: "id", Value: "65e4f1881747c1eea8007d3b" },
|
{ Key: "id", Value: "65e4f1881747c1eea8007d3b" },
|
||||||
{
|
{
|
||||||
Key: "name",
|
Key: "name",
|
||||||
Value:
|
Value: "Количество Заявок, Скрытие шильдика в опроснике, 2024-03-03T21:54:16.434Z",
|
||||||
"Количество Заявок, Скрытие шильдика в опроснике, 2024-03-03T21:54:16.434Z",
|
|
||||||
},
|
},
|
||||||
{ Key: "price", Value: 0 },
|
{ Key: "price", Value: 0 },
|
||||||
{ Key: "iscustom", Value: true },
|
{ Key: "iscustom", Value: true },
|
||||||
|
@ -7,7 +7,7 @@ type QuizTabProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function QuizTab({ userId }: QuizTabProps) {
|
export default function QuizTab({ userId }: QuizTabProps) {
|
||||||
const [quizId, setQuizId] = useState<string>("")
|
const [quizId, setQuizId] = useState<string>("");
|
||||||
return (
|
return (
|
||||||
<Box sx={{ padding: "25px" }}>
|
<Box sx={{ padding: "25px" }}>
|
||||||
<Typography
|
<Typography
|
||||||
@ -22,8 +22,7 @@ export default function QuizTab({ userId }: QuizTabProps) {
|
|||||||
<TextField
|
<TextField
|
||||||
placeholder={"Ссылка на квиз"}
|
placeholder={"Ссылка на квиз"}
|
||||||
onChange={(event: ChangeEvent<HTMLTextAreaElement>) => {
|
onChange={(event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setQuizId(event.target.value.split("link/")[1])
|
setQuizId(event.target.value.split("link/")[1]);
|
||||||
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@ -34,14 +33,13 @@ export default function QuizTab({ userId }: QuizTabProps) {
|
|||||||
method: "post",
|
method: "post",
|
||||||
//useToken: true,
|
//useToken: true,
|
||||||
url: process.env.REACT_APP_DOMAIN + "/squiz/quiz/move",
|
url: process.env.REACT_APP_DOMAIN + "/squiz/quiz/move",
|
||||||
body: {Qid: quizId, AccountID: userId}
|
body: { Qid: quizId, AccountID: userId },
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Ок
|
Ок
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
@ -117,9 +117,7 @@ export const TransactionsTab = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addScrollEvent = () => {
|
const addScrollEvent = () => {
|
||||||
const grid = gridContainer.current?.querySelector(
|
const grid = gridContainer.current?.querySelector(".MuiDataGrid-virtualScroller");
|
||||||
".MuiDataGrid-virtualScroller"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (grid) {
|
if (grid) {
|
||||||
grid.addEventListener("scroll", handleScroll);
|
grid.addEventListener("scroll", handleScroll);
|
||||||
@ -133,18 +131,14 @@ export const TransactionsTab = () => {
|
|||||||
addScrollEvent();
|
addScrollEvent();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const grid = gridContainer.current?.querySelector(
|
const grid = gridContainer.current?.querySelector(".MuiDataGrid-virtualScroller");
|
||||||
".MuiDataGrid-virtualScroller"
|
|
||||||
);
|
|
||||||
|
|
||||||
grid?.removeEventListener("scroll", handleScroll);
|
grid?.removeEventListener("scroll", handleScroll);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const scrollDataGrid = (toStart = false) => {
|
const scrollDataGrid = (toStart = false) => {
|
||||||
const grid = gridContainer.current?.querySelector(
|
const grid = gridContainer.current?.querySelector(".MuiDataGrid-virtualScroller");
|
||||||
".MuiDataGrid-virtualScroller"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (grid) {
|
if (grid) {
|
||||||
scrollBlock(grid, { left: toStart ? 0 : grid.scrollWidth });
|
scrollBlock(grid, { left: toStart ? 0 : grid.scrollWidth });
|
||||||
@ -221,11 +215,7 @@ export const TransactionsTab = () => {
|
|||||||
}}
|
}}
|
||||||
onClick={() => scrollDataGrid(true)}
|
onClick={() => scrollDataGrid(true)}
|
||||||
>
|
>
|
||||||
<img
|
<img src={forwardIcon} alt="forward" style={{ transform: "rotate(180deg)" }} />
|
||||||
src={forwardIcon}
|
|
||||||
alt="forward"
|
|
||||||
style={{ transform: "rotate(180deg)" }}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{canScrollToRight && (
|
{canScrollToRight && (
|
||||||
|
@ -35,9 +35,7 @@ export const UserTab = ({ userId }: UserTabProps) => {
|
|||||||
<Box sx={{ maxWidth: "300px", width: "100%" }}>
|
<Box sx={{ maxWidth: "300px", width: "100%" }}>
|
||||||
<Box sx={{ marginBottom: "25px" }}>
|
<Box sx={{ marginBottom: "25px" }}>
|
||||||
<Typography sx={{ lineHeight: "20px" }}>ID</Typography>
|
<Typography sx={{ lineHeight: "20px" }}>ID</Typography>
|
||||||
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>
|
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>{user?._id}</Typography>
|
||||||
{user?._id}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ marginBottom: "25px" }}>
|
<Box sx={{ marginBottom: "25px" }}>
|
||||||
<Typography sx={{ lineHeight: "20px" }}>Дата регистрации</Typography>
|
<Typography sx={{ lineHeight: "20px" }}>Дата регистрации</Typography>
|
||||||
@ -47,15 +45,11 @@ export const UserTab = ({ userId }: UserTabProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ marginBottom: "25px" }}>
|
<Box sx={{ marginBottom: "25px" }}>
|
||||||
<Typography sx={{ lineHeight: "20px" }}>Email</Typography>
|
<Typography sx={{ lineHeight: "20px" }}>Email</Typography>
|
||||||
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>
|
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>{user?.email || user?.login}</Typography>
|
||||||
{user?.email || user?.login}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ marginBottom: "25px" }}>
|
<Box sx={{ marginBottom: "25px" }}>
|
||||||
<Typography sx={{ lineHeight: "20px" }}>Телефон</Typography>
|
<Typography sx={{ lineHeight: "20px" }}>Телефон</Typography>
|
||||||
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>
|
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>{user?.phoneNumber}</Typography>
|
||||||
{user?.phoneNumber}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ marginBottom: "25px" }}>
|
<Box sx={{ marginBottom: "25px" }}>
|
||||||
<Typography sx={{ lineHeight: "20px" }}>Тип:</Typography>
|
<Typography sx={{ lineHeight: "20px" }}>Тип:</Typography>
|
||||||
@ -70,19 +64,13 @@ export const UserTab = ({ userId }: UserTabProps) => {
|
|||||||
<Box sx={{ marginBottom: "25px" }}>
|
<Box sx={{ marginBottom: "25px" }}>
|
||||||
<Typography sx={{ lineHeight: "20px" }}>ФИО:</Typography>
|
<Typography sx={{ lineHeight: "20px" }}>ФИО:</Typography>
|
||||||
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>
|
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>
|
||||||
{`${account?.name.secondname || ""} ${
|
{`${account?.name.secondname || ""} ${account?.name.firstname || ""} ${account?.name.middlename || ""}`}
|
||||||
account?.name.firstname || ""
|
|
||||||
} ${account?.name.middlename || ""}`}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ marginBottom: "25px" }}>
|
<Box sx={{ marginBottom: "25px" }}>
|
||||||
<Typography sx={{ lineHeight: "20px" }}>
|
<Typography sx={{ lineHeight: "20px" }}>Внутренний кошелек</Typography>
|
||||||
Внутренний кошелек
|
|
||||||
</Typography>
|
|
||||||
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>
|
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>
|
||||||
{`${account ? account.wallet.money / 100 : 0} ${
|
{`${account ? account.wallet.money / 100 : 0} ${account?.wallet.currency || "RUB"}.`}
|
||||||
account?.wallet.currency || "RUB"
|
|
||||||
}.`}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -21,9 +21,7 @@ export const VerificationTab = ({ userId }: VerificationTabProps) => {
|
|||||||
const requestVefification = async () => {
|
const requestVefification = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
const [verificationResponse, verificationError] = await verification(
|
const [verificationResponse, verificationError] = await verification(userId);
|
||||||
userId
|
|
||||||
);
|
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|
||||||
@ -34,7 +32,7 @@ export const VerificationTab = ({ userId }: VerificationTabProps) => {
|
|||||||
if (verificationResponse) {
|
if (verificationResponse) {
|
||||||
setVerificationInfo(verificationResponse);
|
setVerificationInfo(verificationResponse);
|
||||||
setComment(verificationResponse.comment);
|
setComment(verificationResponse.comment);
|
||||||
setINN(verificationResponse.taxnumber)
|
setINN(verificationResponse.taxnumber);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -51,7 +49,6 @@ export const VerificationTab = ({ userId }: VerificationTabProps) => {
|
|||||||
});
|
});
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
requestVefification();
|
requestVefification();
|
||||||
}, []);
|
}, []);
|
||||||
@ -69,11 +66,12 @@ export const VerificationTab = ({ userId }: VerificationTabProps) => {
|
|||||||
status: verificationInfo.status,
|
status: verificationInfo.status,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (accepted && _ === "OK") await makeRequest({
|
if (accepted && _ === "OK")
|
||||||
|
await makeRequest({
|
||||||
method: "patch",
|
method: "patch",
|
||||||
useToken: true,
|
useToken: true,
|
||||||
url: process.env.REACT_APP_DOMAIN + `/customer/account/${userId}`,
|
url: process.env.REACT_APP_DOMAIN + `/customer/account/${userId}`,
|
||||||
body: {status: verificationInfo.status}
|
body: { status: verificationInfo.status },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (patchVerificationError) {
|
if (patchVerificationError) {
|
||||||
@ -95,13 +93,8 @@ export const VerificationTab = ({ userId }: VerificationTabProps) => {
|
|||||||
{verificationInfo?.accepted ? "Верификация пройдена" : "Не верифицирован"}
|
{verificationInfo?.accepted ? "Верификация пройдена" : "Не верифицирован"}
|
||||||
</Typography>
|
</Typography>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Typography
|
<Typography sx={{ fontWeight: "bold", fontSize: "18px", marginBottom: "25px" }}>Загрузка данных...</Typography>
|
||||||
sx={{ fontWeight: "bold", fontSize: "18px", marginBottom: "25px" }}
|
) : verificationInfo && verificationInfo.files.length > 0 ? (
|
||||||
>
|
|
||||||
Загрузка данных...
|
|
||||||
</Typography>
|
|
||||||
) :
|
|
||||||
verificationInfo && verificationInfo.files.length > 0 ? (
|
|
||||||
verificationInfo.files.map(({ name, url }, index) => (
|
verificationInfo.files.map(({ name, url }, index) => (
|
||||||
<Box sx={{ marginBottom: "25px" }} key={name + url}>
|
<Box sx={{ marginBottom: "25px" }} key={name + url}>
|
||||||
<Typography sx={{ fontWeight: "bold", fontSize: "18px" }}>
|
<Typography sx={{ fontWeight: "bold", fontSize: "18px" }}>
|
||||||
@ -130,9 +123,7 @@ export const VerificationTab = ({ userId }: VerificationTabProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<Typography
|
<Typography sx={{ fontWeight: "bold", fontSize: "18px", marginBottom: "25px" }}>
|
||||||
sx={{ fontWeight: "bold", fontSize: "18px", marginBottom: "25px" }}
|
|
||||||
>
|
|
||||||
Пользователь не загружал данные
|
Пользователь не загружал данные
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
@ -142,17 +133,14 @@ export const VerificationTab = ({ userId }: VerificationTabProps) => {
|
|||||||
if (!verificationInfo) {
|
if (!verificationInfo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setINN(event.target.value.replace(/[^0-9]/g,""))
|
setINN(event.target.value.replace(/[^0-9]/g, ""));
|
||||||
debouncedINNHC(event.target.value.replace(/[^0-9]/g,""))}
|
debouncedINNHC(event.target.value.replace(/[^0-9]/g, ""));
|
||||||
}
|
}}
|
||||||
placeholder="ИНН"
|
placeholder="ИНН"
|
||||||
/>
|
/>
|
||||||
{verificationInfo?.comment && (
|
{verificationInfo?.comment && (
|
||||||
<Box sx={{ marginBottom: "15px" }}>
|
<Box sx={{ marginBottom: "15px" }}>
|
||||||
<Typography
|
<Typography component="span" sx={{ fontWeight: "bold", marginBottom: "10px" }}>
|
||||||
component="span"
|
|
||||||
sx={{ fontWeight: "bold", marginBottom: "10px" }}
|
|
||||||
>
|
|
||||||
Комментарий:
|
Комментарий:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography component="span"> {verificationInfo.comment}</Typography>
|
<Typography component="span"> {verificationInfo.comment}</Typography>
|
||||||
@ -169,23 +157,13 @@ export const VerificationTab = ({ userId }: VerificationTabProps) => {
|
|||||||
maxWidth: "500px",
|
maxWidth: "500px",
|
||||||
marginBottom: "10px",
|
marginBottom: "10px",
|
||||||
}}
|
}}
|
||||||
onChange={(event: ChangeEvent<HTMLTextAreaElement>) =>
|
onChange={(event: ChangeEvent<HTMLTextAreaElement>) => setComment(event.target.value)}
|
||||||
setComment(event.target.value)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<Box sx={{ display: "flex", columnGap: "10px" }}>
|
<Box sx={{ display: "flex", columnGap: "10px" }}>
|
||||||
<Button
|
<Button variant="text" sx={{ background: "#9A9AAF" }} onClick={() => verify(false)}>
|
||||||
variant="text"
|
|
||||||
sx={{ background: "#9A9AAF" }}
|
|
||||||
onClick={() => verify(false)}
|
|
||||||
>
|
|
||||||
Отклонить
|
Отклонить
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button variant="text" sx={{ background: "#9A9AAF" }} onClick={() => verify(true)}>
|
||||||
variant="text"
|
|
||||||
sx={{ background: "#9A9AAF" }}
|
|
||||||
onClick={() => verify(true)}
|
|
||||||
>
|
|
||||||
Подтвердить
|
Подтвердить
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,15 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import { Box, Modal, Fade, Backdrop, Typography, Tabs, Tab, useTheme, useMediaQuery } from "@mui/material";
|
||||||
Box,
|
|
||||||
Modal,
|
|
||||||
Fade,
|
|
||||||
Backdrop,
|
|
||||||
Typography,
|
|
||||||
Tabs,
|
|
||||||
Tab,
|
|
||||||
useTheme,
|
|
||||||
useMediaQuery,
|
|
||||||
} from "@mui/material";
|
|
||||||
|
|
||||||
import { UserTab } from "./UserTab";
|
import { UserTab } from "./UserTab";
|
||||||
import { PurchaseTab } from "./PurchaseTab";
|
import { PurchaseTab } from "./PurchaseTab";
|
||||||
@ -21,7 +11,7 @@ import { ReactComponent as PackageIcon } from "@root/assets/icons/package.svg";
|
|||||||
import { ReactComponent as TransactionsIcon } from "@root/assets/icons/transactions.svg";
|
import { ReactComponent as TransactionsIcon } from "@root/assets/icons/transactions.svg";
|
||||||
import { ReactComponent as CheckIcon } from "@root/assets/icons/check.svg";
|
import { ReactComponent as CheckIcon } from "@root/assets/icons/check.svg";
|
||||||
import { ReactComponent as CloseIcon } from "@root/assets/icons/close.svg";
|
import { ReactComponent as CloseIcon } from "@root/assets/icons/close.svg";
|
||||||
import QuizIcon from '@mui/icons-material/Quiz';
|
import QuizIcon from "@mui/icons-material/Quiz";
|
||||||
import forwardIcon from "@root/assets/icons/forward.svg";
|
import forwardIcon from "@root/assets/icons/forward.svg";
|
||||||
|
|
||||||
import type { SyntheticEvent } from "react";
|
import type { SyntheticEvent } from "react";
|
||||||
@ -91,10 +81,7 @@ const ModalUser = ({ open, onClose, userId }: ModalUserProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{mobile && (
|
{mobile && (
|
||||||
<Box
|
<Box onClick={onClose} sx={{ position: "absolute", top: "10px", right: "5px" }}>
|
||||||
onClick={onClose}
|
|
||||||
sx={{ position: "absolute", top: "10px", right: "5px" }}
|
|
||||||
>
|
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@ -118,11 +105,7 @@ const ModalUser = ({ open, onClose, userId }: ModalUserProps) => {
|
|||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
maxWidth: mobile
|
maxWidth: mobile ? (openNavigation ? "276px" : "68px") : "276px",
|
||||||
? openNavigation
|
|
||||||
? "276px"
|
|
||||||
: "68px"
|
|
||||||
: "276px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{mobile && (
|
{mobile && (
|
||||||
@ -143,9 +126,7 @@ const ModalUser = ({ open, onClose, userId }: ModalUserProps) => {
|
|||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
variant="scrollable"
|
variant="scrollable"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(event: SyntheticEvent, newValue: number) =>
|
onChange={(event: SyntheticEvent, newValue: number) => setValue(newValue)}
|
||||||
setValue(newValue)
|
|
||||||
}
|
|
||||||
aria-label="Vertical tabs example"
|
aria-label="Vertical tabs example"
|
||||||
sx={{
|
sx={{
|
||||||
padding: mobile ? "16px" : "10px",
|
padding: mobile ? "16px" : "10px",
|
||||||
@ -188,8 +169,7 @@ const ModalUser = ({ open, onClose, userId }: ModalUserProps) => {
|
|||||||
position: "relative",
|
position: "relative",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
color: theme.palette.common.black,
|
color: theme.palette.common.black,
|
||||||
boxShadow:
|
boxShadow: "inset 30px 0px 40px 0px rgba(210, 208, 225, 0.2)",
|
||||||
"inset 30px 0px 40px 0px rgba(210, 208, 225, 0.2)",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{value === 0 && <UserTab userId={userId} />}
|
{value === 0 && <UserTab userId={userId} />}
|
||||||
|
@ -5,7 +5,7 @@ import type { CustomPrivilege } from "@frontend/kitui";
|
|||||||
|
|
||||||
const mutatePrivileges = (privileges: CustomPrivilege[]) => {
|
const mutatePrivileges = (privileges: CustomPrivilege[]) => {
|
||||||
let extracted: CustomPrivilege[] = [];
|
let extracted: CustomPrivilege[] = [];
|
||||||
for (let serviceKey in privileges) {
|
for (const serviceKey in privileges) {
|
||||||
//Приходит объект. В его значениях массивы привилегий для разных сервисов. Высыпаем в общую кучу и обновляем стор
|
//Приходит объект. В его значениях массивы привилегий для разных сервисов. Высыпаем в общую кучу и обновляем стор
|
||||||
extracted = extracted.concat(privileges[serviceKey]);
|
extracted = extracted.concat(privileges[serviceKey]);
|
||||||
}
|
}
|
||||||
@ -14,8 +14,7 @@ const mutatePrivileges = (privileges: CustomPrivilege[]) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const requestPrivileges = async () => {
|
export const requestPrivileges = async () => {
|
||||||
const [privilegesResponse, privilegesError] =
|
const [privilegesResponse, privilegesError] = await requestServicePrivileges();
|
||||||
await requestServicePrivileges();
|
|
||||||
|
|
||||||
if (privilegesError) {
|
if (privilegesError) {
|
||||||
return console.error(privilegesError);
|
return console.error(privilegesError);
|
||||||
@ -24,7 +23,8 @@ export const requestPrivileges = async () => {
|
|||||||
let allPrivileges: CustomPrivilege[] = [];
|
let allPrivileges: CustomPrivilege[] = [];
|
||||||
|
|
||||||
if (privilegesResponse) {
|
if (privilegesResponse) {
|
||||||
if (privilegesResponse.templategen !== undefined) allPrivileges = allPrivileges.concat(privilegesResponse.templategen);
|
if (privilegesResponse.templategen !== undefined)
|
||||||
|
allPrivileges = allPrivileges.concat(privilegesResponse.templategen);
|
||||||
if (privilegesResponse.squiz !== undefined) allPrivileges = allPrivileges.concat(privilegesResponse.squiz);
|
if (privilegesResponse.squiz !== undefined) allPrivileges = allPrivileges.concat(privilegesResponse.squiz);
|
||||||
mutatePrivileges(allPrivileges);
|
mutatePrivileges(allPrivileges);
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,8 @@ const mutateTariffs = (tariffs: Tariff[]) => {
|
|||||||
updateTariffs(nonDeletedTariffs);
|
updateTariffs(nonDeletedTariffs);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requestTariffs = async (
|
export const requestTariffs = async (page: number = 1, existingTariffs: Tariff[] = []): Promise<void> => {
|
||||||
page: number = 1,
|
const [tariffsResponse, tariffsResponseError] = await requestTariffsRequest(page);
|
||||||
existingTariffs: Tariff[] = []
|
|
||||||
): Promise<void> => {
|
|
||||||
const [tariffsResponse, tariffsResponseError] = await requestTariffsRequest(
|
|
||||||
page
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tariffsResponseError) {
|
if (tariffsResponseError) {
|
||||||
console.error(tariffsResponseError);
|
console.error(tariffsResponseError);
|
||||||
@ -25,10 +20,7 @@ export const requestTariffs = async (
|
|||||||
|
|
||||||
if (tariffsResponse) {
|
if (tariffsResponse) {
|
||||||
if (page < tariffsResponse.totalPages) {
|
if (page < tariffsResponse.totalPages) {
|
||||||
return requestTariffs(page + 1, [
|
return requestTariffs(page + 1, [...existingTariffs, ...tariffsResponse.tariffs]);
|
||||||
...existingTariffs,
|
|
||||||
...tariffsResponse.tariffs,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mutateTariffs([...existingTariffs, ...tariffsResponse.tariffs]);
|
mutateTariffs([...existingTariffs, ...tariffsResponse.tariffs]);
|
||||||
|
@ -2,7 +2,6 @@ import { create } from "zustand";
|
|||||||
import { devtools } from "zustand/middleware";
|
import { devtools } from "zustand/middleware";
|
||||||
import { CartData } from "@frontend/kitui";
|
import { CartData } from "@frontend/kitui";
|
||||||
|
|
||||||
|
|
||||||
interface CartStore {
|
interface CartStore {
|
||||||
cartData: CartData | null;
|
cartData: CartData | null;
|
||||||
}
|
}
|
||||||
@ -12,13 +11,10 @@ const initialState: CartStore = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useCartStore = create<CartStore>()(
|
export const useCartStore = create<CartStore>()(
|
||||||
devtools(
|
devtools((set, get) => initialState, {
|
||||||
(set, get) => initialState,
|
|
||||||
{
|
|
||||||
name: "Cart",
|
name: "Cart",
|
||||||
enabled: process.env.NODE_ENV === "development",
|
enabled: process.env.NODE_ENV === "development",
|
||||||
}
|
})
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const setCartData = (cartData: CartStore["cartData"]) => useCartStore.setState({ cartData });
|
export const setCartData = (cartData: CartStore["cartData"]) => useCartStore.setState({ cartData });
|
||||||
|
@ -4,10 +4,9 @@ import { devtools } from "zustand/middleware";
|
|||||||
import { produce } from "immer";
|
import { produce } from "immer";
|
||||||
import { Discount } from "@frontend/kitui";
|
import { Discount } from "@frontend/kitui";
|
||||||
|
|
||||||
|
|
||||||
interface DiscountStore {
|
interface DiscountStore {
|
||||||
discounts: Discount[];
|
discounts: Discount[];
|
||||||
selectedDiscountIds: GridSelectionModel,
|
selectedDiscountIds: GridSelectionModel;
|
||||||
editDiscountId: string | null;
|
editDiscountId: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,23 +26,26 @@ export const useDiscountStore = create<DiscountStore>()(
|
|||||||
|
|
||||||
export const setDiscounts = (discounts: DiscountStore["discounts"]) => useDiscountStore.setState({ discounts });
|
export const setDiscounts = (discounts: DiscountStore["discounts"]) => useDiscountStore.setState({ discounts });
|
||||||
|
|
||||||
export const findDiscountsById = (discountId: string): (Discount | null) => useDiscountStore.getState().discounts.find(discount => discount.ID === discountId) ?? null;
|
export const findDiscountsById = (discountId: string): Discount | null =>
|
||||||
|
useDiscountStore.getState().discounts.find((discount) => discount.ID === discountId) ?? null;
|
||||||
|
|
||||||
export const addDiscount = (discount: DiscountStore["discounts"][number]) => useDiscountStore.setState(
|
export const addDiscount = (discount: DiscountStore["discounts"][number]) =>
|
||||||
state => ({ discounts: [...state.discounts, discount] })
|
useDiscountStore.setState((state) => ({ discounts: [...state.discounts, discount] }));
|
||||||
);
|
|
||||||
|
|
||||||
export const updateDiscount = (updatedDiscount: DiscountStore["discounts"][number]) => useDiscountStore.setState(
|
export const updateDiscount = (updatedDiscount: DiscountStore["discounts"][number]) =>
|
||||||
produce<DiscountStore>(state => {
|
useDiscountStore.setState(
|
||||||
const discountIndex = state.discounts.findIndex(discount => discount.ID === updatedDiscount.ID);
|
produce<DiscountStore>((state) => {
|
||||||
|
const discountIndex = state.discounts.findIndex((discount) => discount.ID === updatedDiscount.ID);
|
||||||
if (discountIndex === -1) throw new Error(`Discount not found when updating: ${updatedDiscount.ID}`);
|
if (discountIndex === -1) throw new Error(`Discount not found when updating: ${updatedDiscount.ID}`);
|
||||||
|
|
||||||
state.discounts.splice(discountIndex, 1, updatedDiscount);
|
state.discounts.splice(discountIndex, 1, updatedDiscount);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const setSelectedDiscountIds = (selectedDiscountIds: DiscountStore["selectedDiscountIds"]) => useDiscountStore.setState({ selectedDiscountIds });
|
export const setSelectedDiscountIds = (selectedDiscountIds: DiscountStore["selectedDiscountIds"]) =>
|
||||||
|
useDiscountStore.setState({ selectedDiscountIds });
|
||||||
|
|
||||||
export const openEditDiscountDialog = (editDiscountId: DiscountStore["editDiscountId"]) => useDiscountStore.setState({ editDiscountId });
|
export const openEditDiscountDialog = (editDiscountId: DiscountStore["editDiscountId"]) =>
|
||||||
|
useDiscountStore.setState({ editDiscountId });
|
||||||
|
|
||||||
export const closeEditDiscountDialog = () => useDiscountStore.setState({ editDiscountId: null });
|
export const closeEditDiscountDialog = () => useDiscountStore.setState({ editDiscountId: null });
|
||||||
|
@ -3,7 +3,6 @@ import { TicketMessage } from "@root/model/ticket";
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { devtools } from "zustand/middleware";
|
import { devtools } from "zustand/middleware";
|
||||||
|
|
||||||
|
|
||||||
interface MessageStore {
|
interface MessageStore {
|
||||||
messages: TicketMessage[];
|
messages: TicketMessage[];
|
||||||
fetchState: "idle" | "fetching" | "all fetched";
|
fetchState: "idle" | "fetching" | "all fetched";
|
||||||
@ -34,9 +33,9 @@ export const useMessageStore = create<MessageStore>()(
|
|||||||
|
|
||||||
export const addOrUpdateMessages = (receivedMessages: TicketMessage[]) => {
|
export const addOrUpdateMessages = (receivedMessages: TicketMessage[]) => {
|
||||||
const state = useMessageStore.getState();
|
const state = useMessageStore.getState();
|
||||||
const messageIdToMessageMap: { [messageId: string]: TicketMessage; } = {};
|
const messageIdToMessageMap: { [messageId: string]: TicketMessage } = {};
|
||||||
|
|
||||||
[...state.messages, ...receivedMessages].forEach(message => messageIdToMessageMap[message.id] = message);
|
[...state.messages, ...receivedMessages].forEach((message) => (messageIdToMessageMap[message.id] = message));
|
||||||
|
|
||||||
const sortedMessages = Object.values(messageIdToMessageMap).sort(sortMessagesByTime);
|
const sortedMessages = Object.values(messageIdToMessageMap).sort(sortMessagesByTime);
|
||||||
|
|
||||||
@ -46,14 +45,16 @@ export const addOrUpdateMessages = (receivedMessages: TicketMessage[]) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clearMessageState = () => useMessageStore.setState({
|
export const clearMessageState = () =>
|
||||||
|
useMessageStore.setState({
|
||||||
messages: [],
|
messages: [],
|
||||||
apiPage: 0,
|
apiPage: 0,
|
||||||
lastMessageId: undefined,
|
lastMessageId: undefined,
|
||||||
fetchState: "idle",
|
fetchState: "idle",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setMessageFetchState = (fetchState: MessageStore["fetchState"]) => useMessageStore.setState({ fetchState });
|
export const setMessageFetchState = (fetchState: MessageStore["fetchState"]) =>
|
||||||
|
useMessageStore.setState({ fetchState });
|
||||||
|
|
||||||
export const incrementMessageApiPage = () => {
|
export const incrementMessageApiPage = () => {
|
||||||
const state = useMessageStore.getState();
|
const state = useMessageStore.getState();
|
||||||
@ -61,9 +62,11 @@ export const incrementMessageApiPage = () => {
|
|||||||
useMessageStore.setState({ apiPage: state.apiPage + 1 });
|
useMessageStore.setState({ apiPage: state.apiPage + 1 });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setIsPreventAutoscroll = (isPreventAutoscroll: boolean) => useMessageStore.setState({ isPreventAutoscroll });
|
export const setIsPreventAutoscroll = (isPreventAutoscroll: boolean) =>
|
||||||
|
useMessageStore.setState({ isPreventAutoscroll });
|
||||||
|
|
||||||
export const setTicketMessagesFetchState = (ticketMessagesFetchState: FetchState) => useMessageStore.setState({ ticketMessagesFetchState });
|
export const setTicketMessagesFetchState = (ticketMessagesFetchState: FetchState) =>
|
||||||
|
useMessageStore.setState({ ticketMessagesFetchState });
|
||||||
|
|
||||||
function sortMessagesByTime(ticket1: TicketMessage, ticket2: TicketMessage) {
|
function sortMessagesByTime(ticket1: TicketMessage, ticket2: TicketMessage) {
|
||||||
const date1 = new Date(ticket1.created_at).getTime();
|
const date1 = new Date(ticket1.created_at).getTime();
|
||||||
|
@ -1,49 +1,48 @@
|
|||||||
import { Ticket } from "@root/model/ticket";
|
import { Ticket } from "@root/model/ticket";
|
||||||
|
|
||||||
|
|
||||||
export const testTickets: Ticket[] = [
|
export const testTickets: Ticket[] = [
|
||||||
{
|
{
|
||||||
"id": "cg5irh4vc9g7b3n3tcrg",
|
id: "cg5irh4vc9g7b3n3tcrg",
|
||||||
"user": "6407625ed01874dcffa8b008",
|
user: "6407625ed01874dcffa8b008",
|
||||||
"sess": "6407625ed01874dcffa8b008",
|
sess: "6407625ed01874dcffa8b008",
|
||||||
"ans": "",
|
ans: "",
|
||||||
"state": "open",
|
state: "open",
|
||||||
"top_message": {
|
top_message: {
|
||||||
"id": "cg5irh4vc9g7b3n3tcs0",
|
id: "cg5irh4vc9g7b3n3tcs0",
|
||||||
"ticket_id": "cg5irh4vc9g7b3n3tcrg",
|
ticket_id: "cg5irh4vc9g7b3n3tcrg",
|
||||||
"user_id": "6407625ed01874dcffa8b008",
|
user_id: "6407625ed01874dcffa8b008",
|
||||||
"session_id": "6407625ed01874dcffa8b008",
|
session_id: "6407625ed01874dcffa8b008",
|
||||||
"message": "text",
|
message: "text",
|
||||||
"files": [],
|
files: [],
|
||||||
"shown": {},
|
shown: {},
|
||||||
"request_screenshot": "",
|
request_screenshot: "",
|
||||||
"created_at": "2023-03-10T13:16:52.73Z"
|
created_at: "2023-03-10T13:16:52.73Z",
|
||||||
},
|
},
|
||||||
"title": "textual ticket",
|
title: "textual ticket",
|
||||||
"created_at": "2023-03-10T13:16:52.73Z",
|
created_at: "2023-03-10T13:16:52.73Z",
|
||||||
"updated_at": "2023-03-10T13:16:52.73Z",
|
updated_at: "2023-03-10T13:16:52.73Z",
|
||||||
"rate": -1
|
rate: -1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cg55nssvc9g7gddpnsug",
|
id: "cg55nssvc9g7gddpnsug",
|
||||||
"user": "",
|
user: "",
|
||||||
"sess": "",
|
sess: "",
|
||||||
"ans": "",
|
ans: "",
|
||||||
"state": "open",
|
state: "open",
|
||||||
"top_message": {
|
top_message: {
|
||||||
"id": "cg55nssvc9g7gddpnsv0",
|
id: "cg55nssvc9g7gddpnsv0",
|
||||||
"ticket_id": "cg55nssvc9g7gddpnsug",
|
ticket_id: "cg55nssvc9g7gddpnsug",
|
||||||
"user_id": "",
|
user_id: "",
|
||||||
"session_id": "",
|
session_id: "",
|
||||||
"message": "text",
|
message: "text",
|
||||||
"files": [],
|
files: [],
|
||||||
"shown": {},
|
shown: {},
|
||||||
"request_screenshot": "",
|
request_screenshot: "",
|
||||||
"created_at": "2023-03-09T22:21:39.822Z"
|
created_at: "2023-03-09T22:21:39.822Z",
|
||||||
|
},
|
||||||
|
title: "textual ticket",
|
||||||
|
created_at: "2023-03-09T22:21:39.822Z",
|
||||||
|
updated_at: "2023-03-09T22:21:39.822Z",
|
||||||
|
rate: -1,
|
||||||
},
|
},
|
||||||
"title": "textual ticket",
|
|
||||||
"created_at": "2023-03-09T22:21:39.822Z",
|
|
||||||
"updated_at": "2023-03-09T22:21:39.822Z",
|
|
||||||
"rate": -1
|
|
||||||
}
|
|
||||||
];
|
];
|
@ -1,8 +1,7 @@
|
|||||||
import { User } from "@root/model/user";
|
import { User } from "@root/model/user";
|
||||||
|
|
||||||
|
|
||||||
export const testUser: User = {
|
export const testUser: User = {
|
||||||
"ID": "buddy",
|
ID: "buddy",
|
||||||
"Type": "",
|
Type: "",
|
||||||
"PurchasesAmount": 11000,
|
PurchasesAmount: 11000,
|
||||||
};
|
};
|
@ -2,7 +2,6 @@ import { create } from "zustand";
|
|||||||
import { devtools } from "zustand/middleware";
|
import { devtools } from "zustand/middleware";
|
||||||
import { CustomPrivilege } from "@frontend/kitui";
|
import { CustomPrivilege } from "@frontend/kitui";
|
||||||
|
|
||||||
|
|
||||||
interface PrivilegeStore {
|
interface PrivilegeStore {
|
||||||
privileges: CustomPrivilege[];
|
privileges: CustomPrivilege[];
|
||||||
}
|
}
|
||||||
@ -19,8 +18,13 @@ export const usePrivilegeStore = create<PrivilegeStore>()(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
export const resetPrivilegeArray = (privileges: PrivilegeStore["privileges"]) => usePrivilegeStore.setState({ privileges });
|
export const resetPrivilegeArray = (privileges: PrivilegeStore["privileges"]) =>
|
||||||
|
usePrivilegeStore.setState({ privileges });
|
||||||
|
|
||||||
export const findPrivilegeById = (privilegeId: string) => {
|
export const findPrivilegeById = (privilegeId: string) => {
|
||||||
return usePrivilegeStore.getState().privileges.find((privilege) => privilege._id === privilegeId || privilege.privilegeId === privilegeId) ?? null;
|
return (
|
||||||
|
usePrivilegeStore
|
||||||
|
.getState()
|
||||||
|
.privileges.find((privilege) => privilege._id === privilegeId || privilege.privilegeId === privilegeId) ?? null
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user