commit
1254227e54
@ -1,30 +1,32 @@
|
||||
const MOCK_DATA_USERS = [
|
||||
{
|
||||
export const MOCK_DATA_USERS = [
|
||||
{
|
||||
key: 0,
|
||||
id: "someid1",
|
||||
name: "admin",
|
||||
desc:"Администратор сервиса"
|
||||
},
|
||||
{
|
||||
desc: "Администратор сервиса",
|
||||
},
|
||||
{
|
||||
key: 1,
|
||||
id: "someid2",
|
||||
name: "manager",
|
||||
desc:"Менеджер сервиса"
|
||||
},
|
||||
{
|
||||
desc: "Менеджер сервиса",
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
id: "someid3",
|
||||
name: "user",
|
||||
desc:"Пользователь сервиса"
|
||||
}
|
||||
desc: "Пользователь сервиса",
|
||||
},
|
||||
];
|
||||
|
||||
export type TMockData = typeof MOCK_DATA_USERS;
|
||||
|
||||
export const getRoles_mock = ():Promise<TMockData> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve( MOCK_DATA_USERS );
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
export type UsersType = { login: string; email: string; phoneNumber: string; isDeleted: boolean; createdAt: string }[];
|
||||
|
||||
export const getRoles_mock = (): Promise<TMockData> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(MOCK_DATA_USERS);
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
@ -1,89 +1,110 @@
|
||||
import makeRequest from "@root/kitUI/makeRequest";
|
||||
import { GetMessagesRequest, GetMessagesResponse, GetTicketsRequest, GetTicketsResponse, SendTicketMessageRequest } from "@root/model/ticket";
|
||||
import {
|
||||
GetMessagesRequest,
|
||||
GetMessagesResponse,
|
||||
GetTicketsRequest,
|
||||
GetTicketsResponse,
|
||||
SendTicketMessageRequest,
|
||||
} from "@root/model/ticket";
|
||||
import { authStore } from "@root/stores/auth";
|
||||
import ReconnectingEventSource from "reconnecting-eventsource";
|
||||
|
||||
// const { makeRequest } = authStore();
|
||||
|
||||
const supportApiUrl = "https://admin.pena.digital/heruvym";
|
||||
|
||||
export function subscribeToAllTickets({ onMessage, onError, accessToken }: {
|
||||
accessToken: string;
|
||||
onMessage: (e: MessageEvent) => void;
|
||||
onError: (e: Event) => void;
|
||||
export function subscribeToAllTickets({
|
||||
onMessage,
|
||||
onError,
|
||||
accessToken,
|
||||
}: {
|
||||
accessToken: string;
|
||||
onMessage: (e: MessageEvent) => void;
|
||||
onError: (e: Event) => void;
|
||||
}) {
|
||||
const url = `${supportApiUrl}/subscribe?Authorization=${accessToken}`;
|
||||
const url = `${supportApiUrl}/subscribe?Authorization=${accessToken}`;
|
||||
|
||||
const eventSource = createEventSource(onMessage, onError, url);
|
||||
const eventSource = createEventSource(onMessage, onError, url);
|
||||
|
||||
return () => {
|
||||
eventSource.close();
|
||||
};
|
||||
return () => {
|
||||
eventSource.close();
|
||||
};
|
||||
}
|
||||
|
||||
export function subscribeToTicketMessages({ onMessage, onError, accessToken, ticketId }: {
|
||||
accessToken: string;
|
||||
ticketId: string;
|
||||
onMessage: (e: MessageEvent) => void;
|
||||
onError: (e: Event) => void;
|
||||
export function subscribeToTicketMessages({
|
||||
onMessage,
|
||||
onError,
|
||||
accessToken,
|
||||
ticketId,
|
||||
}: {
|
||||
accessToken: string;
|
||||
ticketId: string;
|
||||
onMessage: (e: MessageEvent) => void;
|
||||
onError: (e: Event) => void;
|
||||
}) {
|
||||
const url = `${supportApiUrl}/ticket?ticket=${ticketId}&Authorization=${accessToken}`;
|
||||
const url = `${supportApiUrl}/ticket?ticket=${ticketId}&Authorization=${accessToken}`;
|
||||
|
||||
const eventSource = createEventSource(onMessage, onError, url);
|
||||
const eventSource = createEventSource(onMessage, onError, url);
|
||||
|
||||
return () => {
|
||||
eventSource.close();
|
||||
};
|
||||
return () => {
|
||||
eventSource.close();
|
||||
};
|
||||
}
|
||||
|
||||
export async function getTickets({ body, signal }: {
|
||||
body: GetTicketsRequest;
|
||||
signal: AbortSignal;
|
||||
export async function getTickets({
|
||||
body,
|
||||
signal,
|
||||
}: {
|
||||
body: GetTicketsRequest;
|
||||
signal: AbortSignal;
|
||||
}): Promise<GetTicketsResponse> {
|
||||
return makeRequest({
|
||||
url: `${supportApiUrl}/getTickets`,
|
||||
method: "POST",
|
||||
useToken: true,
|
||||
body,
|
||||
signal,
|
||||
}).then(response => {
|
||||
const result = (response as any).data as GetTicketsResponse;
|
||||
return result;
|
||||
});
|
||||
return makeRequest({
|
||||
url: `${supportApiUrl}/getTickets`,
|
||||
method: "POST",
|
||||
useToken: true,
|
||||
body,
|
||||
signal,
|
||||
}).then((response) => {
|
||||
const result = (response as any).data as GetTicketsResponse;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getTicketMessages({ body, signal }: {
|
||||
body: GetMessagesRequest;
|
||||
signal: AbortSignal;
|
||||
export async function getTicketMessages({
|
||||
body,
|
||||
signal,
|
||||
}: {
|
||||
body: GetMessagesRequest;
|
||||
signal: AbortSignal;
|
||||
}): Promise<GetMessagesResponse> {
|
||||
return makeRequest({
|
||||
url: `${supportApiUrl}/getMessages`,
|
||||
method: "POST",
|
||||
useToken: true,
|
||||
body,
|
||||
signal,
|
||||
}).then(response => {
|
||||
const result = (response as any).data as GetMessagesResponse;
|
||||
return result;
|
||||
});
|
||||
return makeRequest({
|
||||
url: `${supportApiUrl}/getMessages`,
|
||||
method: "POST",
|
||||
useToken: true,
|
||||
body,
|
||||
signal,
|
||||
}).then((response) => {
|
||||
const result = (response as any).data as GetMessagesResponse;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
export async function sendTicketMessage({ body }: {
|
||||
body: SendTicketMessageRequest;
|
||||
}) {
|
||||
return makeRequest({
|
||||
url: `${supportApiUrl}/send`,
|
||||
method: "POST",
|
||||
useToken: true,
|
||||
body,
|
||||
});
|
||||
export async function sendTicketMessage({ body }: { body: SendTicketMessageRequest }) {
|
||||
return makeRequest({
|
||||
url: `${supportApiUrl}/send`,
|
||||
method: "POST",
|
||||
useToken: true,
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
function createEventSource(onMessage: (e: MessageEvent) => void, onError: (e: Event) => void, url: string) {
|
||||
const eventSource = new ReconnectingEventSource(url);
|
||||
const eventSource = new ReconnectingEventSource(url);
|
||||
|
||||
eventSource.addEventListener("open", () => console.log(`EventSource connected with ${url}`));
|
||||
eventSource.addEventListener("close", () => console.log(`EventSource closed with ${url}`));
|
||||
eventSource.addEventListener("message", onMessage);
|
||||
eventSource.addEventListener("error", onError);
|
||||
eventSource.addEventListener("open", () => console.log(`EventSource connected with ${url}`));
|
||||
eventSource.addEventListener("close", () => console.log(`EventSource closed with ${url}`));
|
||||
eventSource.addEventListener("message", onMessage);
|
||||
eventSource.addEventListener("error", onError);
|
||||
|
||||
return eventSource;
|
||||
}
|
||||
return eventSource;
|
||||
}
|
||||
|
138
src/index.tsx
138
src/index.tsx
@ -1,12 +1,15 @@
|
||||
import * as React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import { SnackbarProvider } from 'notistack';
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter, Routes, Route, Outlet, Navigate } from "react-router-dom";
|
||||
import theme from "./theme";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
|
||||
import { SnackbarProvider } from "notistack";
|
||||
|
||||
import PublicRoute from "@kitUI/publicRoute";
|
||||
import PrivateRoute from "@kitUI/privateRoute";
|
||||
|
||||
import Signin from "@pages/Authorization/signin";
|
||||
import Signup from "@pages/Authorization/signup";
|
||||
import Restore from "@pages/Authorization/restore";
|
||||
@ -18,47 +21,96 @@ import Entities from "@pages/dashboard/Content/Entities";
|
||||
import Tariffs from "@pages/dashboard/Content/Tariffs";
|
||||
import DiscountManagement from "@pages/dashboard/Content/DiscountManagement";
|
||||
import PromocodeManagement from "@pages/dashboard/Content/PromocodeManagement";
|
||||
import Support from "@root/pages/dashboard/Content/Support/Support";
|
||||
import { SettingRoles } from "@pages/Setting/SettingRoles";
|
||||
import Support from "@pages/dashboard/Content/Support/Support";
|
||||
|
||||
import theme from "./theme";
|
||||
import "./index.css";
|
||||
|
||||
|
||||
const componentsArray = [
|
||||
["/users", <Users />],
|
||||
["/entities",<Entities />],
|
||||
["/tariffs", <Tariffs />],
|
||||
["/discounts", <DiscountManagement />],
|
||||
["/promocode", <PromocodeManagement />],
|
||||
["/support", <Support />],
|
||||
["/support/:ticketId", <Support />],
|
||||
]
|
||||
["/users", <Users />],
|
||||
["/entities", <Entities />],
|
||||
["/tariffs", <Tariffs />],
|
||||
["/discounts", <DiscountManagement />],
|
||||
["/promocode", <PromocodeManagement />],
|
||||
["/support", <Support />],
|
||||
["/support/:ticketId", <Support />],
|
||||
];
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const container = document.getElementById("root");
|
||||
const root = createRoot(container!);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<CssBaseline />
|
||||
<ThemeProvider theme={theme}>
|
||||
<SnackbarProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<PublicRoute><Signin /></PublicRoute> } />
|
||||
<Route path="/signin" element={ <PublicRoute><Signin /></PublicRoute> } />
|
||||
<Route path="/signup" element={ <PublicRoute><Signup /></PublicRoute> } />
|
||||
<Route path="/restore" element={ <PublicRoute><Restore /></PublicRoute> } />
|
||||
<Route path="/dispatch" element={ <PublicRoute><Sections /></PublicRoute> } />
|
||||
<Route element={<PrivateRoute><Dashboard/></PrivateRoute>}>
|
||||
{componentsArray.map((e:any, i) => (
|
||||
<Route key={i} path={e[0]} element={e[1]} />
|
||||
))}
|
||||
</Route>
|
||||
|
||||
<Route
|
||||
path="*"
|
||||
element={ <Error404 /> }
|
||||
/>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</SnackbarProvider>
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<CssBaseline />
|
||||
<ThemeProvider theme={theme}>
|
||||
<SnackbarProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<PublicRoute>
|
||||
<Signin />
|
||||
</PublicRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/signin"
|
||||
element={
|
||||
<PublicRoute>
|
||||
<Signin />
|
||||
</PublicRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/signup"
|
||||
element={
|
||||
<PublicRoute>
|
||||
<Signup />
|
||||
</PublicRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/restore"
|
||||
element={
|
||||
<PublicRoute>
|
||||
<Restore />
|
||||
</PublicRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/dispatch"
|
||||
element={
|
||||
<PublicRoute>
|
||||
<Sections />
|
||||
</PublicRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Dashboard />
|
||||
</PrivateRoute>
|
||||
}
|
||||
>
|
||||
<Route
|
||||
path="/settingRoles"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<SettingRoles />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
{componentsArray.map((e: any, i) => (
|
||||
<Route key={e} path={e[0]} element={e[1]} />
|
||||
))}
|
||||
</Route>
|
||||
|
||||
<Route path="*" element={<Error404 />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</SnackbarProvider>
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
@ -1,67 +1,58 @@
|
||||
import axios from 'axios'
|
||||
import axios from "axios";
|
||||
interface MakeRequest {
|
||||
method?: string
|
||||
url: string
|
||||
body?: unknown
|
||||
useToken?: boolean
|
||||
contentType?: boolean
|
||||
signal?: AbortSignal
|
||||
method?: string;
|
||||
url: string;
|
||||
body?: unknown;
|
||||
useToken?: boolean;
|
||||
contentType?: boolean;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export default (props: MakeRequest) => {
|
||||
return (
|
||||
new Promise(async (resolve, reject) => {
|
||||
await makeRequest(props)
|
||||
.then(r => resolve(r))
|
||||
.catch(r => reject(r))
|
||||
})
|
||||
)
|
||||
}
|
||||
return new Promise(async (resolve, reject) => {
|
||||
await makeRequest(props)
|
||||
.then((r) => resolve(r))
|
||||
.catch((r) => reject(r));
|
||||
});
|
||||
};
|
||||
|
||||
function makeRequest({
|
||||
method = "post",
|
||||
url,
|
||||
body,
|
||||
useToken = true,
|
||||
signal,
|
||||
contentType = false
|
||||
}: MakeRequest) {
|
||||
//В случае 401 рефреш должен попробовать вызваться 1 раз
|
||||
let counterRefresh = true
|
||||
let headers: any = {}
|
||||
if (useToken) headers["Authorization"] = localStorage.getItem('AT')
|
||||
if (contentType) headers["Content-Type"] = "application/json"
|
||||
return axios({
|
||||
url: url,
|
||||
method: method,
|
||||
headers: headers,
|
||||
data: body,
|
||||
signal,
|
||||
function makeRequest({ method = "post", url, body, useToken = true, signal, contentType = false }: MakeRequest) {
|
||||
//В случае 401 рефреш должен попробовать вызваться 1 раз
|
||||
let counterRefresh = true;
|
||||
let headers: any = {};
|
||||
if (useToken) headers["Authorization"] = localStorage.getItem("AT");
|
||||
if (contentType) headers["Content-Type"] = "application/json";
|
||||
return axios({
|
||||
url: url,
|
||||
method: method,
|
||||
headers: headers,
|
||||
data: body,
|
||||
signal,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.data && response.data.accessToken) {
|
||||
localStorage.setItem("AT", response.data.accessToken);
|
||||
}
|
||||
return response;
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data && response.data.accessToken) {
|
||||
localStorage.setItem('AT', response.data.accessToken)
|
||||
}
|
||||
return response
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response.status == 401 && counterRefresh) {
|
||||
refresh().then(response => {
|
||||
if (response.data && response.data.accessToken) localStorage.setItem('AT', response.data.accessToken)
|
||||
counterRefresh = false
|
||||
})
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
throw error
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response.status == 401 && counterRefresh) {
|
||||
refresh().then((response) => {
|
||||
if (response.data && response.data.accessToken) localStorage.setItem("AT", response.data.accessToken);
|
||||
counterRefresh = false;
|
||||
});
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
return axios("https://admin.pena.digital/auth/refresh", {
|
||||
headers: {
|
||||
"Authorization": localStorage.getItem('AT'),
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
})
|
||||
}
|
||||
return axios("https://admin.pena.digital/auth/refresh", {
|
||||
headers: {
|
||||
Authorization: localStorage.getItem("AT"),
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { authStore } from "@root/stores/auth";
|
||||
import * as React from "react";
|
||||
import { useLocation, Navigate } from 'react-router-dom'
|
||||
import { useLocation, Navigate } from "react-router-dom";
|
||||
|
||||
export default ({ children }: any) => {
|
||||
const location = useLocation()
|
||||
//Если пользователь авторизован, перенаправляем его на нужный путь. Иначе выкидываем в регистрацию
|
||||
if (localStorage.getItem('AT')) {
|
||||
return children
|
||||
}
|
||||
return <Navigate to="/" state={{from: location}} />
|
||||
|
||||
}
|
||||
const { token } = authStore();
|
||||
const location = useLocation();
|
||||
//Если пользователь авторизован, перенаправляем его на нужный путь. Иначе выкидываем в регистрацию
|
||||
if (token) {
|
||||
return children;
|
||||
}
|
||||
return <Navigate to="/" state={{ from: location }} />;
|
||||
};
|
||||
|
@ -1,12 +1,16 @@
|
||||
import * as React from "react";
|
||||
import { useLocation, Navigate } from 'react-router-dom'
|
||||
import { useLocation, Navigate } from "react-router-dom";
|
||||
|
||||
export default ({ children }: any) => {
|
||||
const location = useLocation()
|
||||
//Если пользователь авторизован, перенаправляем его в приложение. Иначе пускаем куда хотел
|
||||
if (localStorage.getItem('AT')) {
|
||||
return <Navigate to="/users" state={{from: location}} />
|
||||
}
|
||||
return children
|
||||
import { authStore } from "@root/stores/auth";
|
||||
|
||||
}
|
||||
const PublicRoute = ({ children }: any) => {
|
||||
const location = useLocation();
|
||||
const { token } = authStore();
|
||||
|
||||
if (token) {
|
||||
return <Navigate to="/users" state={{ from: location }} />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export default PublicRoute;
|
||||
|
@ -1,119 +1,130 @@
|
||||
import * as React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Formik, Field, Form } from 'formik';
|
||||
import {useTheme} from "@mui/material/styles";
|
||||
import { Link } from "react-router-dom"
|
||||
import {Box, Typography} from "@mui/material";
|
||||
import { Formik, Field, Form } from "formik";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import Logo from "@pages/Logo";
|
||||
import CleverButton from "@kitUI/cleverButton"
|
||||
import MakeRequest from "@kitUI/makeRequest";
|
||||
import CleverButton from "@kitUI/cleverButton";
|
||||
import EmailOutlinedIcon from "@mui/icons-material/EmailOutlined";
|
||||
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
|
||||
import OutlinedInput from "@kitUI/outlinedInput";
|
||||
import { authStore } from "@root/stores/auth";
|
||||
|
||||
export default () => {
|
||||
const theme = useTheme()
|
||||
const navigate = useNavigate();
|
||||
const [restore, setRestore] = React.useState(true)
|
||||
const [isReady, setIsReady] = React.useState(true)
|
||||
if (restore) {
|
||||
return (
|
||||
|
||||
<Formik
|
||||
initialValues={{
|
||||
mail: ""
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
setRestore(false)
|
||||
}}
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const [restore, setRestore] = React.useState(true);
|
||||
const [isReady, setIsReady] = React.useState(true);
|
||||
const { makeRequest } = authStore();
|
||||
if (restore) {
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{
|
||||
mail: "",
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
setRestore(false);
|
||||
}}
|
||||
>
|
||||
<Form>
|
||||
<Box
|
||||
component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Form>
|
||||
<Box component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0"
|
||||
}}
|
||||
>
|
||||
<Box component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>Восстановление пароля</Typography>
|
||||
<Logo/>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>
|
||||
Восстановление пароля
|
||||
</Typography>
|
||||
<Logo />
|
||||
|
||||
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||
<EmailOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||
<Field as={OutlinedInput} autoComplete="none" variant="filled" name="mail" label="Эл. почта"/>
|
||||
</Box>
|
||||
<CleverButton type="submit" text="Отправить" isReady={isReady}/>
|
||||
<Link to="/signin" style={{textDecoration:"none"}}><Typography color={theme.palette.golden.main}>Я помню пароль</Typography></Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
)
|
||||
} else {
|
||||
return(
|
||||
<Formik
|
||||
initialValues={{
|
||||
code: ""
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
}}
|
||||
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
|
||||
<EmailOutlinedIcon htmlColor={theme.palette.golden.main} />
|
||||
<Field as={OutlinedInput} autoComplete="none" variant="filled" name="mail" label="Эл. почта" />
|
||||
</Box>
|
||||
<CleverButton type="submit" text="Отправить" isReady={isReady} />
|
||||
<Link to="/signin" style={{ textDecoration: "none" }}>
|
||||
<Typography color={theme.palette.golden.main}>Я помню пароль</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{
|
||||
code: "",
|
||||
}}
|
||||
onSubmit={(values) => {}}
|
||||
>
|
||||
<Form>
|
||||
<Box
|
||||
component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Form>
|
||||
<Box component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0"
|
||||
}}
|
||||
>
|
||||
<Box component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>Восстановление пароля</Typography>
|
||||
<Logo/>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>
|
||||
Восстановление пароля
|
||||
</Typography>
|
||||
<Logo />
|
||||
|
||||
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||
<Field as={OutlinedInput} name="code" variant="filled" label="Код из сообщения"/>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main} />
|
||||
<Field as={OutlinedInput} name="code" variant="filled" label="Код из сообщения" />
|
||||
</Box>
|
||||
|
||||
<CleverButton type="submit" text="Отправить" isReady={isReady}/>
|
||||
<Link to="/signin" style={{textDecoration:"none"}}><Typography color={theme.palette.golden.main}>Я помню пароль</Typography></Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
)
|
||||
}
|
||||
}
|
||||
<CleverButton type="submit" text="Отправить" isReady={isReady} />
|
||||
<Link to="/signin" style={{ textDecoration: "none" }}>
|
||||
<Typography color={theme.palette.golden.main}>Я помню пароль</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -1,137 +1,163 @@
|
||||
import * as React from "react"
|
||||
import * as React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import {useTheme} from "@mui/material/styles"
|
||||
import { Formik, Field, Form } from 'formik'
|
||||
import { Link } from "react-router-dom"
|
||||
import { Box, Checkbox, TextField, Typography, FormControlLabel} from "@mui/material"
|
||||
import Logo from "@pages/Logo"
|
||||
import CleverButton from "@kitUI/cleverButton"
|
||||
import OutlinedInput from "@kitUI/outlinedInput"
|
||||
import makeRequest from "@kitUI/makeRequest";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { Formik, Field, Form } from "formik";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Box, Checkbox, Typography, FormControlLabel } from "@mui/material";
|
||||
import Logo from "@pages/Logo";
|
||||
import CleverButton from "@kitUI/cleverButton";
|
||||
import OutlinedInput from "@kitUI/outlinedInput";
|
||||
import EmailOutlinedIcon from "@mui/icons-material/EmailOutlined";
|
||||
import LockOutlinedIcon from "@mui/icons-material/LockOutlined"
|
||||
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
|
||||
import { authStore } from "@root/stores/auth";
|
||||
|
||||
interface Values {
|
||||
email: string;
|
||||
password: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
function validate(values: Values) {
|
||||
const errors = {} as any;
|
||||
if (!values.email) {
|
||||
errors.email = "Required";
|
||||
}
|
||||
if (!values.password) {
|
||||
errors.password = "Required";
|
||||
} else if (!/^[\S]{8,25}$/i.test(values.password)) {
|
||||
errors.password = "Invalid password";
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
export default () => {
|
||||
const theme = useTheme()
|
||||
const navigate = useNavigate();
|
||||
const [isReady, setIsReady] = React.useState(true)
|
||||
const errors = {} as any;
|
||||
|
||||
return(
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: "",
|
||||
password: ""
|
||||
}}
|
||||
validate={validate}
|
||||
onSubmit={(values) => {
|
||||
makeRequest({
|
||||
url: "https://admin.pena.digital/auth/login",
|
||||
body: {
|
||||
"email": values.email,
|
||||
"password": values.password
|
||||
},
|
||||
useToken: false
|
||||
})
|
||||
.then((e) => {
|
||||
console.log(e)
|
||||
navigate("/users")
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
enqueueSnackbar(e.message ? e.message : `Unknown error`)
|
||||
})
|
||||
}}
|
||||
if (!values.email) {
|
||||
errors.email = "Required";
|
||||
}
|
||||
|
||||
if (!values.password) {
|
||||
errors.password = "Required";
|
||||
}
|
||||
|
||||
if (values.password && !/^[\S]{8,25}$/i.test(values.password)) {
|
||||
errors.password = "Invalid password";
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
const SigninForm = () => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const [isReady] = React.useState(true);
|
||||
|
||||
const { makeRequest } = authStore();
|
||||
|
||||
const initialValues: Values = {
|
||||
email: "",
|
||||
password: "",
|
||||
};
|
||||
|
||||
const onSignFormSubmit = (values: Values) => {
|
||||
makeRequest({
|
||||
url: "https://admin.pena.digital/auth/login",
|
||||
body: {
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
},
|
||||
useToken: false,
|
||||
})
|
||||
.then((e) => {
|
||||
navigate("/users");
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
enqueueSnackbar(e.message ? e.message : `Unknown error`);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik initialValues={initialValues} validate={validate} onSubmit={onSignFormSubmit}>
|
||||
<Form>
|
||||
<Box
|
||||
component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0",
|
||||
}}
|
||||
>
|
||||
<Form>
|
||||
<Box component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0"
|
||||
}}
|
||||
>
|
||||
<Box component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Logo/>
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.secondary.main}>Добро пожаловать</Typography>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>Мы рады что вы выбрали нас!</Typography>
|
||||
</Box>
|
||||
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||
<EmailOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||
<Field as={OutlinedInput} name="email" variant="filled" label="Эл. почта"/>
|
||||
</Box>
|
||||
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||
<Field as={OutlinedInput} type="password" name="password" variant="filled" label="Пароль"/>
|
||||
</Box>
|
||||
<Box component="article"
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
sx={{color:"white"}}
|
||||
control={<Checkbox
|
||||
value="checkedA"
|
||||
inputProps={{ 'aria-label': 'Checkbox A' }}
|
||||
sx={{
|
||||
color: "white",
|
||||
transform: "scale(1.5)",
|
||||
"&.Mui-checked": {
|
||||
color: "white",
|
||||
},
|
||||
"&.MuiFormControlLabel-root": {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>} label="Запомнить этот компьютер" />
|
||||
</Box>
|
||||
<Link to="/restore" style={{textDecoration:"none"}}><Typography color={theme.palette.golden.main}>Забыли пароль?</Typography></Link>
|
||||
<CleverButton type="submit" text="Войти" isReady={isReady}/>
|
||||
<Box sx={{
|
||||
display: "flex"
|
||||
}}>
|
||||
<Typography color={theme.palette.secondary.main}>У вас нет аккаунта? </Typography>
|
||||
<Link to="/signup" style={{textDecoration:"none"}}><Typography color={theme.palette.golden.main}>Зарегестрируйтесь</Typography></Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
)
|
||||
}
|
||||
<Box
|
||||
component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Logo />
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.secondary.main}>
|
||||
Добро пожаловать
|
||||
</Typography>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>
|
||||
Мы рады что вы выбрали нас!
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
|
||||
<EmailOutlinedIcon htmlColor={theme.palette.golden.main} />
|
||||
<Field as={OutlinedInput} name="email" variant="filled" label="Эл. почта" />
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main} />
|
||||
<Field as={OutlinedInput} type="password" name="password" variant="filled" label="Пароль" />
|
||||
</Box>
|
||||
<Box
|
||||
component="article"
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
sx={{ color: "white" }}
|
||||
control={
|
||||
<Checkbox
|
||||
value="checkedA"
|
||||
inputProps={{ "aria-label": "Checkbox A" }}
|
||||
sx={{
|
||||
color: "white",
|
||||
transform: "scale(1.5)",
|
||||
"&.Mui-checked": {
|
||||
color: "white",
|
||||
},
|
||||
"&.MuiFormControlLabel-root": {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Запомнить этот компьютер"
|
||||
/>
|
||||
</Box>
|
||||
<Link to="/restore" style={{ textDecoration: "none" }}>
|
||||
<Typography color={theme.palette.golden.main}>Забыли пароль?</Typography>
|
||||
</Link>
|
||||
<CleverButton type="submit" text="Войти" isReady={isReady} />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<Typography color={theme.palette.secondary.main}>У вас нет аккаунта? </Typography>
|
||||
<Link to="/signup" style={{ textDecoration: "none" }}>
|
||||
<Typography color={theme.palette.golden.main}>Зарегестрируйтесь</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
};
|
||||
|
||||
export default SigninForm;
|
||||
|
@ -1,118 +1,138 @@
|
||||
import * as React from "react"
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import * as React from "react";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTheme } from "@mui/material/styles"
|
||||
import { Formik, Field, Form } from "formik"
|
||||
import { Link } from "react-router-dom"
|
||||
import {Box, Typography} from "@mui/material"
|
||||
import CleverButton from "@kitUI/cleverButton"
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { Formik, Field, Form } from "formik";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import CleverButton from "@kitUI/cleverButton";
|
||||
import OutlinedInput from "@kitUI/outlinedInput";
|
||||
import makeRequest from "@kitUI/makeRequest";
|
||||
import Logo from "@pages/Logo/index"
|
||||
import EmailOutlinedIcon from "@mui/icons-material/EmailOutlined"
|
||||
import LockOutlinedIcon from "@mui/icons-material/LockOutlined"
|
||||
|
||||
import Logo from "@pages/Logo/index";
|
||||
import EmailOutlinedIcon from "@mui/icons-material/EmailOutlined";
|
||||
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
|
||||
import { authStore } from "@root/stores/auth";
|
||||
interface Values {
|
||||
email: string;
|
||||
password: string;
|
||||
repeatPassword: string;
|
||||
email: string;
|
||||
password: string;
|
||||
repeatPassword: string;
|
||||
}
|
||||
function validate(values: Values) {
|
||||
const errors = {} as any;
|
||||
if (!values.email) {
|
||||
errors.login = "Required";
|
||||
}
|
||||
if (!values.password) {
|
||||
errors.password = "Required";
|
||||
} else if (!/^[\S]{8,25}$/i.test(values.password)) {
|
||||
errors.password = "Invalid password";
|
||||
}
|
||||
if (values.password !== values.repeatPassword) {
|
||||
errors.repeatPassword = "Passwords do not match";
|
||||
}
|
||||
return errors;
|
||||
const errors = {} as any;
|
||||
if (!values.email) {
|
||||
errors.login = "Required";
|
||||
}
|
||||
if (!values.password) {
|
||||
errors.password = "Required";
|
||||
} else if (!/^[\S]{8,25}$/i.test(values.password)) {
|
||||
errors.password = "Invalid password";
|
||||
}
|
||||
if (values.password !== values.repeatPassword) {
|
||||
errors.repeatPassword = "Passwords do not match";
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
export default () => {
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme()
|
||||
const [isReady, setIsReady] = React.useState(true)
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const [isReady, setIsReady] = React.useState(true);
|
||||
const { makeRequest } = authStore();
|
||||
|
||||
return(
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: "",
|
||||
password: "",
|
||||
repeatPassword: ""
|
||||
}}
|
||||
validate={validate}
|
||||
onSubmit={(values) => {
|
||||
makeRequest({
|
||||
url: "https://admin.pena.digital/auth/register",
|
||||
body: {
|
||||
"login": values.email,
|
||||
"email": values.email,
|
||||
"password": values.repeatPassword,
|
||||
"phoneNumber": "--"
|
||||
},
|
||||
useToken: false
|
||||
})
|
||||
.then((e) => {
|
||||
navigate("/users")
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
enqueueSnackbar(e.response && e.response.data && e.response.data.message ? e.response.data.message : `Unknown error`)
|
||||
})
|
||||
}}
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: "",
|
||||
password: "",
|
||||
repeatPassword: "",
|
||||
}}
|
||||
validate={validate}
|
||||
onSubmit={(values) => {
|
||||
makeRequest({
|
||||
url: "https://admin.pena.digital/auth/register",
|
||||
body: {
|
||||
login: values.email,
|
||||
email: values.email,
|
||||
password: values.repeatPassword,
|
||||
phoneNumber: "--",
|
||||
},
|
||||
useToken: false,
|
||||
})
|
||||
.then((e) => {
|
||||
navigate("/users");
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
enqueueSnackbar(
|
||||
e.response && e.response.data && e.response.data.message ? e.response.data.message : `Unknown error`
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Form>
|
||||
<Box
|
||||
component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0",
|
||||
}}
|
||||
>
|
||||
<Form>
|
||||
<Box component="section"
|
||||
sx={{
|
||||
minHeight: "100vh",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "15px 0"
|
||||
}}
|
||||
>
|
||||
<Box component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>Новый аккаунт</Typography>
|
||||
<Logo/>
|
||||
<Box
|
||||
component="article"
|
||||
sx={{
|
||||
width: "350px",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
"> *": {
|
||||
marginTop: "15px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>
|
||||
Новый аккаунт
|
||||
</Typography>
|
||||
<Logo />
|
||||
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.secondary.main}>Добро пожаловать</Typography>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>Мы рады что вы выбрали нас!</Typography>
|
||||
</Box>
|
||||
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||
<EmailOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||
<Field as={OutlinedInput} name="email" variant="filled" label="Эл. почта"/>
|
||||
</Box>
|
||||
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||
<Field as={OutlinedInput} type="password" name="password" variant="filled" label="Пароль"/>
|
||||
</Box>
|
||||
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||
<Field as={OutlinedInput} type="password" name="repeatPassword" variant="filled" label="Повторите пароль"/>
|
||||
</Box>
|
||||
<CleverButton type="submit" text="Отправить" isReady={isReady}/>
|
||||
<Link to="/signin" style={{textDecoration: "none"}} ><Typography color={theme.palette.golden.main}>У меня уже есть аккаунт</Typography></Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
)
|
||||
}
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.secondary.main}>
|
||||
Добро пожаловать
|
||||
</Typography>
|
||||
<Typography variant="h6" color={theme.palette.secondary.main}>
|
||||
Мы рады что вы выбрали нас!
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
|
||||
<EmailOutlinedIcon htmlColor={theme.palette.golden.main} />
|
||||
<Field as={OutlinedInput} name="email" variant="filled" label="Эл. почта" />
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main} />
|
||||
<Field as={OutlinedInput} type="password" name="password" variant="filled" label="Пароль" />
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
|
||||
<LockOutlinedIcon htmlColor={theme.palette.golden.main} />
|
||||
<Field
|
||||
as={OutlinedInput}
|
||||
type="password"
|
||||
name="repeatPassword"
|
||||
variant="filled"
|
||||
label="Повторите пароль"
|
||||
/>
|
||||
</Box>
|
||||
<CleverButton type="submit" text="Отправить" isReady={isReady} />
|
||||
<Link to="/signin" style={{ textDecoration: "none" }}>
|
||||
<Typography color={theme.palette.golden.main}>У меня уже есть аккаунт</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
};
|
||||
|
48
src/pages/Setting/ConditionalRender.tsx
Normal file
48
src/pages/Setting/ConditionalRender.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import axios from "axios";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
type ConditionalRenderProps = {
|
||||
isLoading: boolean;
|
||||
role: string;
|
||||
childrenUser?: JSX.Element;
|
||||
childrenAdmin?: JSX.Element;
|
||||
childrenManager?: JSX.Element;
|
||||
};
|
||||
|
||||
const ConditionalRender = ({
|
||||
isLoading,
|
||||
role,
|
||||
childrenUser,
|
||||
childrenAdmin,
|
||||
childrenManager,
|
||||
}: ConditionalRenderProps): JSX.Element => {
|
||||
// const [role, setRole] = useState<string>("");
|
||||
|
||||
// useEffect(() => {
|
||||
// const axiosAccount = async () => {
|
||||
// try {
|
||||
// const { data } = await axios.get("https://admin.pena.digital/user/643e23f3dba63ba17272664d");
|
||||
// setRole(data.role);
|
||||
// } catch (error) {
|
||||
// console.error("Ошибка при получение роли пользавателя");
|
||||
// }
|
||||
// };
|
||||
// axiosAccount();
|
||||
// }, [role]);
|
||||
|
||||
if (!isLoading) {
|
||||
if (role === "admin") {
|
||||
return childrenAdmin ? childrenAdmin : <div>Администратор</div>;
|
||||
}
|
||||
if (role === "user") {
|
||||
return childrenUser ? childrenUser : <div>Пользователь</div>;
|
||||
}
|
||||
if (role === "manager") {
|
||||
return childrenManager ? childrenManager : <div>Менеджер</div>;
|
||||
}
|
||||
}
|
||||
|
||||
return <React.Fragment />;
|
||||
};
|
||||
|
||||
export default ConditionalRender;
|
79
src/pages/Setting/FormCreateRoles.tsx
Normal file
79
src/pages/Setting/FormCreateRoles.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
FormControl,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { MOCK_DATA_USERS } from "@root/api/roles";
|
||||
|
||||
const ITEM_HEIGHT = 48;
|
||||
const ITEM_PADDING_TOP = 8;
|
||||
const MenuProps = {
|
||||
PaperProps: {
|
||||
style: {
|
||||
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default function CreateForm() {
|
||||
const [personName, setPersonName] = useState<string[]>([]);
|
||||
|
||||
const handleChange = (event: SelectChangeEvent<typeof personName>) => {
|
||||
const {
|
||||
target: { value },
|
||||
} = event;
|
||||
setPersonName(typeof value === "string" ? value.split(",") : value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextField
|
||||
placeholder="название"
|
||||
fullWidth
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
width: "400px",
|
||||
"& .MuiInputBase-root": {
|
||||
backgroundColor: "#F2F3F7",
|
||||
height: "48px",
|
||||
},
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
borderRadius: "10px",
|
||||
fontSize: "18px",
|
||||
lineHeight: "21px",
|
||||
py: 0,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Button sx={{ ml: "5px", bgcolor: "#fe9903", color: "white" }}>Отправить</Button>
|
||||
<FormControl sx={{ ml: "25px", width: "80%" }}>
|
||||
<Select
|
||||
sx={{ bgcolor: "white", height: "48px" }}
|
||||
labelId="demo-multiple-checkbox-label"
|
||||
id="demo-multiple-checkbox"
|
||||
multiple
|
||||
value={personName}
|
||||
onChange={handleChange}
|
||||
renderValue={(selected) => selected.join(", ")}
|
||||
MenuProps={MenuProps}
|
||||
>
|
||||
{MOCK_DATA_USERS.map(({ name, id }) => (
|
||||
<MenuItem key={id} value={name}>
|
||||
<Checkbox checked={personName.indexOf(name) > -1} />
|
||||
<ListItemText primary={name} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
}
|
84
src/pages/Setting/FormDeleteRoles.tsx
Normal file
84
src/pages/Setting/FormDeleteRoles.tsx
Normal file
@ -0,0 +1,84 @@
|
||||
import { useState } from "react";
|
||||
import axios from "axios";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
FormControl,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { MOCK_DATA_USERS } from "@root/api/roles";
|
||||
|
||||
const ITEM_HEIGHT = 48;
|
||||
const ITEM_PADDING_TOP = 8;
|
||||
const MenuProps = {
|
||||
PaperProps: {
|
||||
style: {
|
||||
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default function DeleteForm() {
|
||||
const [personName, setPersonName] = useState<string[]>([]);
|
||||
const [roleId, setRoleId] = useState<string>();
|
||||
|
||||
const handleChange = (event: SelectChangeEvent<typeof personName>) => {
|
||||
const {
|
||||
target: { value },
|
||||
} = event;
|
||||
setPersonName(typeof value === "string" ? value.split(",") : value);
|
||||
};
|
||||
|
||||
const rolesDelete = (id = "") => {
|
||||
axios.delete("https://admin.pena.digital/role/" + id);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => rolesDelete(roleId)} sx={{ mr: "5px", bgcolor: "#fe9903", color: "white" }}>
|
||||
Удалить
|
||||
</Button>
|
||||
<TextField
|
||||
placeholder="название"
|
||||
fullWidth
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
width: "400px",
|
||||
"& .MuiInputBase-root": {
|
||||
backgroundColor: "#F2F3F7",
|
||||
height: "48px",
|
||||
},
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
borderRadius: "10px",
|
||||
fontSize: "18px",
|
||||
lineHeight: "21px",
|
||||
py: 0,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<FormControl sx={{ ml: "25px", width: "80%" }}>
|
||||
<Select
|
||||
sx={{ bgcolor: "white", height: "48px" }}
|
||||
labelId="demo-multiple-checkbox-label"
|
||||
id="demo-multiple-checkbox"
|
||||
value={personName}
|
||||
onChange={handleChange}
|
||||
renderValue={(selected) => selected.join(", ")}
|
||||
MenuProps={MenuProps}
|
||||
>
|
||||
{MOCK_DATA_USERS.map(({ name, id }) => (
|
||||
<MenuItem key={id} value={name}>
|
||||
<Checkbox onClick={() => setRoleId(id)} checked={personName.indexOf(name) > -1} />
|
||||
<ListItemText primary={name} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
}
|
104
src/pages/Setting/SettingRoles.tsx
Normal file
104
src/pages/Setting/SettingRoles.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
import { AccordionDetails, Table, TableBody, TableCell, TableHead, TableRow, Typography } from "@mui/material";
|
||||
|
||||
import FormDeleteRoles from "./FormDeleteRoles";
|
||||
import FormCreateRoles from "./FormCreateRoles";
|
||||
|
||||
import theme from "../../theme";
|
||||
|
||||
export const SettingRoles = () => {
|
||||
return (
|
||||
<AccordionDetails>
|
||||
<Table
|
||||
sx={{
|
||||
width: "890px",
|
||||
border: "2px solid",
|
||||
borderColor: theme.palette.secondary.main,
|
||||
}}
|
||||
aria-label="simple table"
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow
|
||||
sx={{
|
||||
borderBottom: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px",
|
||||
}}
|
||||
>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
Настройки ролей
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
<TableRow
|
||||
sx={{
|
||||
p: "5px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
borderTop: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<FormCreateRoles />
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Table
|
||||
sx={{
|
||||
mt: "30px",
|
||||
width: "890px",
|
||||
border: "2px solid",
|
||||
borderColor: theme.palette.secondary.main,
|
||||
}}
|
||||
aria-label="simple table"
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow
|
||||
sx={{
|
||||
borderBottom: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px",
|
||||
}}
|
||||
>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
Удаление ролей
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
<TableRow
|
||||
sx={{
|
||||
p: "5px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
borderTop: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<FormDeleteRoles />
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</AccordionDetails>
|
||||
);
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import { Box, IconButton, InputAdornment, TextField, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { addOrUpdateMessages, clearMessages, setMessages, useMessageStore } from "@root/stores/messages";
|
||||
import { addOrUpdateMessages, clearMessageState, incrementMessageApiPage, setIsPreventAutoscroll, setMessageFetchState, useMessageStore } from "@root/stores/messages";
|
||||
import Message from "./Message";
|
||||
import SendIcon from "@mui/icons-material/Send";
|
||||
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
||||
@ -9,9 +9,12 @@ import { GetMessagesRequest, TicketMessage } from "@root/model/ticket";
|
||||
import { getTicketMessages, sendTicketMessage, subscribeToTicketMessages } from "@root/api/tickets";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useTicketStore } from "@root/stores/tickets";
|
||||
import { throttle } from "@root/utils/throttle";
|
||||
|
||||
import { authStore } from "@root/stores/auth";
|
||||
|
||||
export default function Chat() {
|
||||
const { token } = authStore();
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const tickets = useTicketStore(state => state.tickets);
|
||||
@ -19,6 +22,11 @@ export default function Chat() {
|
||||
const [messageField, setMessageField] = useState<string>("");
|
||||
const ticketId = useParams().ticketId;
|
||||
const chatBoxRef = useRef<HTMLDivElement>(null);
|
||||
const messageApiPage = useMessageStore(state => state.apiPage);
|
||||
const messagesPerPage = useMessageStore(state => state.messagesPerPage);
|
||||
const messagesFetchStateRef = useRef(useMessageStore.getState().fetchState);
|
||||
const isPreventAutoscroll = useMessageStore(state => state.isPreventAutoscroll);
|
||||
const lastMessageId = useMessageStore(state => state.lastMessageId);
|
||||
|
||||
const ticket = tickets.find(ticket => ticket.id === ticketId);
|
||||
|
||||
@ -26,69 +34,114 @@ export default function Chat() {
|
||||
scrollToBottom();
|
||||
}, [messages]);
|
||||
|
||||
useEffect(function fetchTicketMessages() {
|
||||
if (!ticketId) return;
|
||||
useEffect(
|
||||
function fetchTicketMessages() {
|
||||
if (!ticketId) return;
|
||||
|
||||
const getTicketsBody: GetMessagesRequest = {
|
||||
amt: 100, // TODO use pagination
|
||||
page: 0,
|
||||
amt: messagesPerPage,
|
||||
page: messageApiPage,
|
||||
ticket: ticketId,
|
||||
};
|
||||
const controller = new AbortController();
|
||||
|
||||
setMessageFetchState("fetching");
|
||||
getTicketMessages({
|
||||
body: getTicketsBody,
|
||||
signal: controller.signal,
|
||||
}).then(result => {
|
||||
console.log("GetMessagesResponse", result);
|
||||
setMessages(result);
|
||||
if (result?.length > 0) {
|
||||
addOrUpdateMessages(result);
|
||||
setMessageFetchState("idle");
|
||||
} else setMessageFetchState("all fetched");
|
||||
}).catch(error => {
|
||||
console.log("Error fetching tickets", error);
|
||||
console.log("Error fetching messages", error);
|
||||
enqueueSnackbar(error.message);
|
||||
});
|
||||
|
||||
return () => {
|
||||
controller.abort();
|
||||
clearMessages();
|
||||
clearMessageState();
|
||||
};
|
||||
}, [ticketId]);
|
||||
}, [messageApiPage, messagesPerPage, ticketId]);
|
||||
|
||||
useEffect(function subscribeToMessages() {
|
||||
if (!ticketId) return;
|
||||
|
||||
const token = localStorage.getItem("AT");
|
||||
if (!token) return;
|
||||
|
||||
const unsubscribe = subscribeToTicketMessages({
|
||||
ticketId,
|
||||
accessToken: token,
|
||||
onMessage(event) {
|
||||
try {
|
||||
const newMessage = JSON.parse(event.data) as TicketMessage;
|
||||
console.log("SSE: parsed newMessage:", newMessage);
|
||||
addOrUpdateMessages([newMessage]);
|
||||
} catch (error) {
|
||||
console.log("SSE: couldn't parse:", event.data);
|
||||
console.log("Error parsing message SSE", error);
|
||||
}
|
||||
},
|
||||
onError(event) {
|
||||
console.log("SSE Error:", event);
|
||||
},
|
||||
});
|
||||
const unsubscribe = subscribeToTicketMessages({
|
||||
ticketId,
|
||||
accessToken: token,
|
||||
onMessage(event) {
|
||||
try {
|
||||
const newMessage = JSON.parse(event.data) as TicketMessage;
|
||||
console.log("SSE: parsed newMessage:", newMessage);
|
||||
addOrUpdateMessages([newMessage]);
|
||||
} catch (error) {
|
||||
console.log("SSE: couldn't parse:", event.data);
|
||||
console.log("Error parsing message SSE", error);
|
||||
}
|
||||
},
|
||||
onError(event) {
|
||||
console.log("SSE Error:", event);
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
clearMessageState();
|
||||
setIsPreventAutoscroll(false);
|
||||
};
|
||||
}, [ticketId]);
|
||||
|
||||
function scrollToBottom() {
|
||||
useEffect(function attachScrollHandler() {
|
||||
if (!chatBoxRef.current) return;
|
||||
|
||||
chatBoxRef.current.scroll({
|
||||
const chatBox = chatBoxRef.current;
|
||||
const scrollHandler = () => {
|
||||
const scrollBottom = chatBox.scrollHeight - chatBox.scrollTop - chatBox.clientHeight;
|
||||
const isPreventAutoscroll = scrollBottom > chatBox.clientHeight;
|
||||
setIsPreventAutoscroll(isPreventAutoscroll);
|
||||
|
||||
if (messagesFetchStateRef.current !== "idle") return;
|
||||
|
||||
if (chatBox.scrollTop < chatBox.clientHeight) {
|
||||
if (chatBox.scrollTop < 1) chatBox.scrollTop = 1;
|
||||
incrementMessageApiPage();
|
||||
}
|
||||
};
|
||||
|
||||
const throttledScrollHandler = throttle(scrollHandler, 200);
|
||||
chatBox.addEventListener("scroll", throttledScrollHandler);
|
||||
|
||||
return () => {
|
||||
chatBox.removeEventListener("scroll", throttledScrollHandler);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(function scrollOnNewMessage() {
|
||||
if (!chatBoxRef.current) return;
|
||||
|
||||
if (!isPreventAutoscroll) {
|
||||
setTimeout(() => {
|
||||
scrollToBottom();
|
||||
}, 50);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [lastMessageId]);
|
||||
|
||||
useEffect(() => useMessageStore.subscribe(state => (messagesFetchStateRef.current = state.fetchState)), []);
|
||||
|
||||
function scrollToBottom(behavior?: ScrollBehavior) {
|
||||
if (!chatBoxRef.current) return;
|
||||
|
||||
const chatBox = chatBoxRef.current;
|
||||
chatBox.scroll({
|
||||
left: 0,
|
||||
top: chatBoxRef.current.scrollHeight,
|
||||
behavior: "smooth",
|
||||
top: chatBox.scrollHeight,
|
||||
behavior,
|
||||
});
|
||||
}
|
||||
|
||||
@ -106,9 +159,7 @@ export default function Chat() {
|
||||
setMessageField("");
|
||||
}
|
||||
|
||||
function handleAddAttachment() {
|
||||
|
||||
}
|
||||
function handleAddAttachment() {}
|
||||
|
||||
function handleTextfieldKeyPress(e: KeyboardEvent) {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
@ -207,7 +258,7 @@ export default function Chat() {
|
||||
}
|
||||
|
||||
function sortMessagesByTime(message1: TicketMessage, message2: TicketMessage) {
|
||||
const date1 = new Date(message1.created_at).getTime();
|
||||
const date2 = new Date(message2.created_at).getTime();
|
||||
return date1 - date2;
|
||||
}
|
||||
const date1 = new Date(message1.created_at).getTime();
|
||||
const date2 = new Date(message2.created_at).getTime();
|
||||
return date1 - date2;
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ export default function Message({ message, isSelf }: Props) {
|
||||
borderTopRightRadius: isSelf ? 0 : "20px",
|
||||
maxWidth: "90%",
|
||||
}}>
|
||||
<Typography fontSize="14px">
|
||||
<Typography fontSize="14px" sx={{ wordBreak: "break-word" }}>
|
||||
{message.message}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
@ -1,32 +1,32 @@
|
||||
import { Box, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import Chat from "./Chat/Chat";
|
||||
import Collapse from "./Collapse";
|
||||
import TicketList from "./TicketList/TicketList";
|
||||
import { getTickets, subscribeToAllTickets } from "@root/api/tickets";
|
||||
import { GetTicketsRequest, Ticket } from "@root/model/ticket";
|
||||
import { clearTickets, updateTickets } from "@root/stores/tickets";
|
||||
import { clearTickets, setTicketsFetchState, updateTickets, useTicketStore } from "@root/stores/tickets";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { clearMessages } from "@root/stores/messages";
|
||||
import {clearMessageState } from "@root/stores/messages";
|
||||
import { authStore } from "@root/stores/auth";
|
||||
|
||||
|
||||
const TICKETS_PER_PAGE = 20;
|
||||
|
||||
export default function Support() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const [currentPage, setCurrentPage] = useState<number>(0);
|
||||
const fetchingStateRef = useRef<"idle" | "fetching" | "all fetched">("idle");
|
||||
const ticketsPerPage = useTicketStore(state => state.ticketsPerPage);
|
||||
const ticketApiPage = useTicketStore(state => state.apiPage);
|
||||
const { token } = authStore();
|
||||
|
||||
useEffect(function fetchTickets() {
|
||||
const getTicketsBody: GetTicketsRequest = {
|
||||
amt: TICKETS_PER_PAGE,
|
||||
page: currentPage,
|
||||
amt: ticketsPerPage,
|
||||
page: ticketApiPage,
|
||||
status: "open",
|
||||
};
|
||||
const controller = new AbortController();
|
||||
|
||||
fetchingStateRef.current = "fetching";
|
||||
setTicketsFetchState("fetching");
|
||||
getTickets({
|
||||
body: getTicketsBody,
|
||||
signal: controller.signal,
|
||||
@ -34,18 +34,17 @@ export default function Support() {
|
||||
console.log("GetTicketsResponse", result);
|
||||
if (result.data) {
|
||||
updateTickets(result.data);
|
||||
fetchingStateRef.current = "idle";
|
||||
} else fetchingStateRef.current = "all fetched";
|
||||
setTicketsFetchState("idle");
|
||||
} else setTicketsFetchState("all fetched");
|
||||
}).catch(error => {
|
||||
console.log("Error fetching tickets", error);
|
||||
enqueueSnackbar(error.message);
|
||||
});
|
||||
|
||||
return () => controller.abort();
|
||||
}, [currentPage]);
|
||||
}, [ticketApiPage, ticketsPerPage]);
|
||||
|
||||
useEffect(function subscribeToTickets() {
|
||||
const token = localStorage.getItem("AT");
|
||||
if (!token) return;
|
||||
|
||||
const unsubscribe = subscribeToAllTickets({
|
||||
@ -67,15 +66,11 @@ export default function Support() {
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
clearMessages();
|
||||
clearMessageState();
|
||||
clearTickets();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const incrementCurrentPage = () => setCurrentPage(prev => prev + 1);
|
||||
|
||||
const ticketList = <TicketList fetchingStateRef={fetchingStateRef} incrementCurrentPage={incrementCurrentPage} />;
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
@ -85,11 +80,11 @@ export default function Support() {
|
||||
}}>
|
||||
{!upMd &&
|
||||
<Collapse headerText="Тикеты">
|
||||
{ticketList}
|
||||
<TicketList />
|
||||
</Collapse>
|
||||
}
|
||||
<Chat />
|
||||
{upMd && ticketList}
|
||||
{upMd && <TicketList />}
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -2,21 +2,17 @@ import HighlightOffOutlinedIcon from '@mui/icons-material/HighlightOffOutlined';
|
||||
import SearchOutlinedIcon from '@mui/icons-material/SearchOutlined';
|
||||
import { Box, Button, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { Ticket } from "@root/model/ticket";
|
||||
import { useTicketStore } from "@root/stores/tickets";
|
||||
import { incrementTicketsApiPage, useTicketStore } from "@root/stores/tickets";
|
||||
import { throttle } from '@root/utils/throttle';
|
||||
import { MutableRefObject, useEffect, useRef } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import TicketItem from "./TicketItem";
|
||||
|
||||
|
||||
interface Props {
|
||||
fetchingStateRef: MutableRefObject<"idle" | "fetching" | "all fetched">;
|
||||
incrementCurrentPage: () => void;
|
||||
}
|
||||
|
||||
export default function TicketList({ fetchingStateRef, incrementCurrentPage }: Props) {
|
||||
export default function TicketList() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const tickets = useTicketStore(state => state.tickets);
|
||||
const ticketsFetchStateRef = useRef(useTicketStore.getState().fetchState);
|
||||
const ticketsBoxRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(function updateCurrentPageOnScroll() {
|
||||
@ -26,9 +22,9 @@ export default function TicketList({ fetchingStateRef, incrementCurrentPage }: P
|
||||
const scrollHandler = () => {
|
||||
const scrollBottom = ticketsBox.scrollHeight - ticketsBox.scrollTop - ticketsBox.clientHeight;
|
||||
if (
|
||||
scrollBottom < 10 &&
|
||||
fetchingStateRef.current === "idle"
|
||||
) incrementCurrentPage();
|
||||
scrollBottom < ticketsBox.clientHeight &&
|
||||
ticketsFetchStateRef.current === "idle"
|
||||
) incrementTicketsApiPage();
|
||||
};
|
||||
|
||||
const throttledScrollHandler = throttle(scrollHandler, 200);
|
||||
@ -37,7 +33,9 @@ export default function TicketList({ fetchingStateRef, incrementCurrentPage }: P
|
||||
return () => {
|
||||
ticketsBox.removeEventListener("scroll", throttledScrollHandler);
|
||||
};
|
||||
}, [incrementCurrentPage, fetchingStateRef]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => useTicketStore.subscribe(state => (ticketsFetchStateRef.current = state.fetchState)), []);
|
||||
|
||||
const sortedTickets = tickets.sort(sortTicketsByUpdateTime).sort(sortTicketsByUnread);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Box, Typography, TextField, Button } from "@mui/material";
|
||||
import Table from "@mui/material/Table";
|
||||
import TableHead from "@mui/material/TableHead";
|
||||
@ -8,240 +8,302 @@ import TableCell from "@mui/material/TableCell";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import Radio from "@mui/material/Radio";
|
||||
import Skeleton from "@mui/material/Skeleton";
|
||||
import Accordion from '@mui/material/Accordion';
|
||||
import AccordionSummary from '@mui/material/AccordionSummary';
|
||||
import AccordionDetails from '@mui/material/AccordionDetails';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ClearIcon from '@mui/icons-material/Clear';
|
||||
import Accordion from "@mui/material/Accordion";
|
||||
import AccordionSummary from "@mui/material/AccordionSummary";
|
||||
import AccordionDetails from "@mui/material/AccordionDetails";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
import { getRoles_mock, TMockData } from "../../../api/roles";
|
||||
import theme from "../../../theme"
|
||||
|
||||
import type { UsersType } from "../../../api/roles";
|
||||
|
||||
const Users: React.FC = () => {
|
||||
const radioboxes = [ "a", "b", "c" ];
|
||||
import theme from "../../../theme";
|
||||
import axios from "axios";
|
||||
import {authStore} from "@stores/auth";
|
||||
import ConditionalRender from "@root/pages/Setting/ConditionalRender";
|
||||
|
||||
const [selectedValue, setSelectedValue] = React.useState("a");
|
||||
const Users: React.FC = () => {
|
||||
const { makeRequest } = authStore();
|
||||
// makeRequest({
|
||||
// url: "https://admin.pena.digital/strator/account",
|
||||
// method: "get",
|
||||
// bearer: true,
|
||||
// contentType: true,
|
||||
// })
|
||||
const radioboxes = ["admin", "manager", "user"];
|
||||
|
||||
const [selectedValue, setSelectedValue] = React.useState("admin");
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSelectedValue(event.target.value);
|
||||
|
||||
if (selectedValue === "manager") {
|
||||
}
|
||||
};
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [data, setData] = React.useState<TMockData>([]);
|
||||
|
||||
const handleChangeData = () => {
|
||||
getRoles_mock().then((mockdata) => {
|
||||
setData( mockdata );
|
||||
})
|
||||
setData(mockdata);
|
||||
});
|
||||
};
|
||||
|
||||
const [accordionHeader, setAccordionHeader] = React.useState<string>("none");
|
||||
const [accordionState, setAccordionState] = React.useState<boolean>(true);
|
||||
const [accordionText, setAccordionText] = React.useState<string>("");
|
||||
const handleChangeAccordion = (text:string) => {
|
||||
if( text == "" ) {
|
||||
setAccordionState( true );
|
||||
const handleChangeAccordion = (text: string) => {
|
||||
if (text == "") {
|
||||
setAccordionState(true);
|
||||
setAccordionHeader("none");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
handleToggleAccordion();
|
||||
setAccordionHeader("flex")
|
||||
setAccordionHeader("flex");
|
||||
}
|
||||
|
||||
accordionState
|
||||
? setAccordionText( text )
|
||||
: setAccordionText( "" )
|
||||
}
|
||||
accordionState ? setAccordionText(text) : setAccordionText("");
|
||||
};
|
||||
|
||||
const handleToggleAccordion = () => {
|
||||
setAccordionState( !accordionState );
|
||||
}
|
||||
setAccordionState(!accordionState);
|
||||
};
|
||||
|
||||
handleChangeData();
|
||||
|
||||
const [roles, setRoles] = React.useState<TMockData>([]);
|
||||
const [users, setUsers] = React.useState<UsersType>();
|
||||
const [manager, setManager] = React.useState<UsersType>();
|
||||
|
||||
React.useEffect(() => {
|
||||
const axiosRoles = async () => {
|
||||
const { data } = await axios({
|
||||
method: "get",
|
||||
url: "https://admin.pena.digital/strator/role/",
|
||||
});
|
||||
setRoles(data);
|
||||
};
|
||||
const gettingRegisteredUsers = async () => {
|
||||
const { data } = await axios({
|
||||
method: "get",
|
||||
url: "https://hub.pena.digital/user/",
|
||||
});
|
||||
setUsers(data);
|
||||
};
|
||||
|
||||
const gettingListManagers = async () => {
|
||||
const { data } = await axios({
|
||||
method: "get",
|
||||
url: "https://admin.pena.digital/user/",
|
||||
});
|
||||
setManager(data);
|
||||
};
|
||||
|
||||
gettingListManagers();
|
||||
gettingRegisteredUsers();
|
||||
axiosRoles();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={ () => navigate("/modalAdmin") }
|
||||
sx={{
|
||||
width: "84%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginBottom: "35px",
|
||||
border: "2px solid",
|
||||
fontWeight: "normal",
|
||||
borderColor: theme.palette.golden.main,
|
||||
color: theme.palette.secondary.main,
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.menu.main
|
||||
}
|
||||
}}>
|
||||
ИНФОРМАЦИЯ О ПРОЕКТЕ
|
||||
</Button>
|
||||
|
||||
|
||||
<Accordion sx={{
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={() => navigate("/modalAdmin")}
|
||||
sx={{
|
||||
width: "84%",
|
||||
backgroundColor: theme.palette.content.main
|
||||
}} expanded={ accordionState }>
|
||||
<AccordionSummary
|
||||
sx = {{ display: accordionHeader }}
|
||||
expandIcon={
|
||||
<ExpandMoreIcon sx={{
|
||||
color: theme.palette.secondary.main
|
||||
}}
|
||||
onClick={ () => handleToggleAccordion() }
|
||||
/>
|
||||
}
|
||||
aria-controls="panel1a-content"
|
||||
id="panel1a-header"
|
||||
>
|
||||
<Typography sx={{
|
||||
width: "100%",
|
||||
color: theme.palette.secondary.main
|
||||
}}>
|
||||
{ accordionText }
|
||||
</Typography>
|
||||
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
alignItems: "right"
|
||||
}}>
|
||||
<ClearIcon
|
||||
sx={{ color: theme.palette.secondary.main }}
|
||||
onClick={ () => handleChangeAccordion("") }
|
||||
/>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
<Table sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginBottom: "35px",
|
||||
border: "2px solid",
|
||||
borderColor: theme.palette.secondary.main,
|
||||
}} aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow sx={{
|
||||
borderBottom: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px"
|
||||
}}>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}>
|
||||
Имя
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}>
|
||||
Описание
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}>
|
||||
Отобразить
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
fontWeight: "normal",
|
||||
borderColor: theme.palette.golden.main,
|
||||
color: theme.palette.secondary.main,
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.menu.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
ИНФОРМАЦИЯ О ПРОЕКТЕ
|
||||
</Button>
|
||||
|
||||
<TableBody>
|
||||
<Accordion
|
||||
sx={{
|
||||
width: "84%",
|
||||
backgroundColor: theme.palette.content.main,
|
||||
}}
|
||||
expanded={accordionState}
|
||||
>
|
||||
<AccordionSummary
|
||||
sx={{ display: accordionHeader }}
|
||||
expandIcon={
|
||||
<ExpandMoreIcon
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
onClick={() => handleToggleAccordion()}
|
||||
/>
|
||||
}
|
||||
aria-controls="panel1a-content"
|
||||
id="panel1a-header"
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
width: "100%",
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
{accordionText}
|
||||
</Typography>
|
||||
|
||||
{
|
||||
data.length
|
||||
? (
|
||||
data.map(function(item, i) {
|
||||
return(
|
||||
<TableRow sx={{
|
||||
borderTop: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px",
|
||||
cursor: "pointer"
|
||||
}}
|
||||
key={ item.key }
|
||||
onClick={ () => handleChangeAccordion( item.desc ) }>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "right",
|
||||
}}
|
||||
>
|
||||
<ClearIcon sx={{ color: theme.palette.secondary.main }} onClick={() => handleChangeAccordion("")} />
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Table
|
||||
sx={{
|
||||
width: "100%",
|
||||
border: "2px solid",
|
||||
borderColor: theme.palette.secondary.main,
|
||||
}}
|
||||
aria-label="simple table"
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow
|
||||
sx={{
|
||||
borderBottom: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px",
|
||||
}}
|
||||
>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
Имя
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
Описание
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
Отобразить
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{data.length ? (
|
||||
data.map(function (item, index) {
|
||||
return (
|
||||
<TableRow
|
||||
sx={{
|
||||
borderTop: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
key={item.key}
|
||||
onClick={() => handleChangeAccordion(item.desc)}
|
||||
>
|
||||
<TableCell>
|
||||
<Typography sx={{
|
||||
color: theme.palette.secondary.main
|
||||
}}>
|
||||
{ item.name }
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography sx={{
|
||||
color: theme.palette.secondary.main
|
||||
}}>
|
||||
{ item.desc }
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Radio
|
||||
checked={selectedValue === radioboxes[ i ]}
|
||||
onChange={handleChange}
|
||||
value={ radioboxes[ i ] }
|
||||
<Typography
|
||||
sx={{
|
||||
"&, &.Mui-checked": {
|
||||
color: theme.palette.secondary.main,
|
||||
},
|
||||
}} />
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
{item.desc}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Radio
|
||||
checked={selectedValue === radioboxes[index]}
|
||||
onChange={handleChange}
|
||||
value={radioboxes[index]}
|
||||
sx={{
|
||||
"&, &.Mui-checked": {
|
||||
color: theme.palette.secondary.main,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
|
||||
) : (
|
||||
<TableRow sx={{
|
||||
borderTop: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px"
|
||||
}}>
|
||||
) : (
|
||||
<TableRow
|
||||
sx={{
|
||||
borderTop: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px",
|
||||
}}
|
||||
>
|
||||
<TableCell colSpan={3}>
|
||||
<Skeleton variant="rectangular"
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
sx={{
|
||||
width: "100%",
|
||||
minWidth: "100%",
|
||||
minHeight: "200px",
|
||||
marginTop: "15px",
|
||||
marginBottom: "15px"
|
||||
marginBottom: "15px",
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
||||
|
||||
<Box sx={{
|
||||
width: "90%",
|
||||
marginTop: "35px"
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "90%",
|
||||
marginTop: "35px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<TextField
|
||||
id = "standard-basic"
|
||||
label = "id"
|
||||
variant = "filled"
|
||||
color = "secondary"
|
||||
sx = {{
|
||||
width: "80%"
|
||||
id="standard-basic"
|
||||
label="id"
|
||||
variant="filled"
|
||||
color="secondary"
|
||||
sx={{
|
||||
width: "80%",
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
@ -249,43 +311,46 @@ const Users: React.FC = () => {
|
||||
color: theme.palette.secondary.main,
|
||||
borderBottom: "1px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
} }}
|
||||
},
|
||||
}}
|
||||
InputLabelProps={{
|
||||
style: {
|
||||
color: theme.palette.secondary.main
|
||||
} }}
|
||||
color: theme.palette.secondary.main,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
maxWidth: '317px',
|
||||
width: '100%'
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
maxWidth: "317px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant = "text"
|
||||
variant="text"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.content.main,
|
||||
color: theme.palette.secondary.main,
|
||||
// width: "20%",
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.content.main
|
||||
}
|
||||
}}>
|
||||
НАЙТИ
|
||||
backgroundColor: theme.palette.content.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
НАЙТИ
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<TextField
|
||||
id = "standard-basic"
|
||||
label = "mail"
|
||||
variant = "filled"
|
||||
color = "secondary"
|
||||
sx = {{
|
||||
width: "80%"
|
||||
id="standard-basic"
|
||||
label="mail"
|
||||
variant="filled"
|
||||
color="secondary"
|
||||
sx={{
|
||||
width: "80%",
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
@ -293,112 +358,253 @@ const Users: React.FC = () => {
|
||||
color: theme.palette.secondary.main,
|
||||
borderBottom: "1px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
} }}
|
||||
},
|
||||
}}
|
||||
InputLabelProps={{
|
||||
style: {
|
||||
color: theme.palette.secondary.main
|
||||
} }}
|
||||
color: theme.palette.secondary.main,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
maxWidth: '317px',
|
||||
width: '100%'
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
maxWidth: "317px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant = "text"
|
||||
variant="text"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.content.main,
|
||||
color: theme.palette.secondary.main,
|
||||
width: "20%",
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.content.main
|
||||
}
|
||||
}}>
|
||||
СБРОСИТЬ
|
||||
backgroundColor: theme.palette.content.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
СБРОСИТЬ
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
|
||||
<Table sx={{
|
||||
width: "90%",
|
||||
border: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
marginTop: "35px",
|
||||
}} aria-label="simple table">
|
||||
<Table
|
||||
sx={{
|
||||
width: "90%",
|
||||
border: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
marginTop: "35px",
|
||||
}}
|
||||
aria-label="simple table"
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow sx={{
|
||||
borderBottom: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px"
|
||||
}}>
|
||||
<TableRow
|
||||
sx={{
|
||||
borderBottom: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px",
|
||||
}}
|
||||
>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}>
|
||||
ID
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
login
|
||||
</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
email
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
phoneNumber
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
isDeleted
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
createdAt
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
<TableRow sx={{
|
||||
borderBottom: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px",
|
||||
cursor: "pointer"
|
||||
}} onClick={ () => navigate("/modalUser") }>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography sx={{
|
||||
color: theme.palette.secondary.main
|
||||
}}>
|
||||
1
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<ConditionalRender
|
||||
isLoading={false}
|
||||
role={selectedValue}
|
||||
childrenManager={
|
||||
<>
|
||||
{manager &&
|
||||
manager.map(({ login, email, phoneNumber, isDeleted, createdAt }) => (
|
||||
<TableRow
|
||||
key={createdAt}
|
||||
sx={{
|
||||
borderBottom: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px",
|
||||
}}
|
||||
>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
{login}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableRow sx={{
|
||||
borderBottom: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px",
|
||||
cursor: "pointer"
|
||||
}} onClick={ () => navigate("/modalUser") }>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography sx={{
|
||||
color: theme.palette.secondary.main
|
||||
}}>
|
||||
2
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
{email}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
{phoneNumber}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
{isDeleted ? "true" : "false"}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
{createdAt}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
childrenUser={
|
||||
<>
|
||||
{users &&
|
||||
users.map(({ login, email, phoneNumber, isDeleted, createdAt }) => (
|
||||
<TableRow
|
||||
key={createdAt}
|
||||
sx={{
|
||||
borderBottom: "2px solid",
|
||||
borderColor: theme.palette.grayLight.main,
|
||||
height: "100px",
|
||||
}}
|
||||
>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
{login}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableRow sx={{
|
||||
borderBottom: "1px solid",
|
||||
border: theme.palette.secondary.main,
|
||||
height: "100px",
|
||||
cursor: "pointer"
|
||||
}} onClick={ () => navigate("/modalUser") }>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography sx={{
|
||||
color: theme.palette.secondary.main
|
||||
}}>
|
||||
3
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
{email}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
{phoneNumber}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
{isDeleted ? "true" : "false"}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell sx={{ textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
}}
|
||||
>
|
||||
{createdAt}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export default Users;
|
||||
export default Users;
|
||||
|
@ -1,36 +1,42 @@
|
||||
import * as React from "react";
|
||||
import {Box, IconButton, Typography} from "@mui/material";
|
||||
import { Box, IconButton, Typography } from "@mui/material";
|
||||
import theme from "../../../theme";
|
||||
import ExitToAppOutlinedIcon from '@mui/icons-material/ExitToAppOutlined';
|
||||
import ExitToAppOutlinedIcon from "@mui/icons-material/ExitToAppOutlined";
|
||||
import Logo from "../../Logo";
|
||||
import makeRequest from "@kitUI/makeRequest";
|
||||
import { authStore } from "@root/stores/auth";
|
||||
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const Header: React.FC = () => {
|
||||
const { makeRequest, clearToken } = authStore();
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Box sx={{
|
||||
backgroundColor: theme.palette.menu.main,
|
||||
width: "100%",
|
||||
minHeight: "85px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexWrap: 'wrap',
|
||||
paddingLeft: '5px'
|
||||
}}>
|
||||
<Box sx={{
|
||||
width: "150px",
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: theme.palette.menu.main,
|
||||
width: "100%",
|
||||
minHeight: "85px",
|
||||
display: "flex",
|
||||
justifyContent: "right",
|
||||
alignItems: "center"
|
||||
}}>
|
||||
justifyContent: "space-between",
|
||||
flexWrap: "wrap",
|
||||
paddingLeft: "5px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "150px",
|
||||
display: "flex",
|
||||
justifyContent: "right",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Logo />
|
||||
</Box>
|
||||
|
||||
<Box sx={{
|
||||
display: "flex"
|
||||
}}>
|
||||
<Typography
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
maxWidth: "230px",
|
||||
@ -38,38 +44,39 @@ const Header: React.FC = () => {
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
fontWeight: "normal"
|
||||
fontWeight: "normal",
|
||||
}}
|
||||
>
|
||||
Добро пожаловать, Администратор сервиса
|
||||
Добро пожаловать, Администратор сервиса
|
||||
</Typography>
|
||||
|
||||
<IconButton
|
||||
onClick={()=>{
|
||||
makeRequest({
|
||||
url: "https://admin.pena.digital/auth/logout",
|
||||
contentType: true
|
||||
})
|
||||
.then(()=>localStorage.setItem('AT', ""))
|
||||
onClick={() => {
|
||||
makeRequest({
|
||||
url: "https://admin.pena.digital/auth/logout",
|
||||
contentType: true,
|
||||
}).then(() => clearToken());
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
padding: "0 31px",
|
||||
}}
|
||||
>
|
||||
<ExitToAppOutlinedIcon
|
||||
sx={{
|
||||
color: theme.palette.golden.main,
|
||||
transform: "scale(1.3)",
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
padding: '0 31px'
|
||||
}}>
|
||||
<ExitToAppOutlinedIcon sx={{
|
||||
color: theme.palette.golden.main,
|
||||
transform: "scale(1.3)"
|
||||
}} />
|
||||
/>
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export default Header;
|
||||
export default Header;
|
||||
|
@ -1,13 +1,13 @@
|
||||
import * as React from "react";
|
||||
import { Box } from "@mui/material";
|
||||
import PersonOutlineOutlinedIcon from '@mui/icons-material/PersonOutlineOutlined';
|
||||
import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined';
|
||||
import BathtubOutlinedIcon from '@mui/icons-material/BathtubOutlined';
|
||||
import AddPhotoAlternateOutlinedIcon from '@mui/icons-material/AddPhotoAlternateOutlined';
|
||||
import NaturePeopleOutlinedIcon from '@mui/icons-material/NaturePeopleOutlined';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import CameraIcon from '@mui/icons-material/Camera';
|
||||
import HeadsetMicOutlinedIcon from '@mui/icons-material/HeadsetMicOutlined';
|
||||
import PersonOutlineOutlinedIcon from "@mui/icons-material/PersonOutlineOutlined";
|
||||
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
|
||||
import BathtubOutlinedIcon from "@mui/icons-material/BathtubOutlined";
|
||||
import AddPhotoAlternateOutlinedIcon from "@mui/icons-material/AddPhotoAlternateOutlined";
|
||||
import NaturePeopleOutlinedIcon from "@mui/icons-material/NaturePeopleOutlined";
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import CameraIcon from "@mui/icons-material/Camera";
|
||||
import HeadsetMicOutlinedIcon from "@mui/icons-material/HeadsetMicOutlined";
|
||||
import theme from "../../../theme";
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
@ -21,44 +21,44 @@ import ListItem from "@mui/material/ListItem";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import {CSSObject, styled, Theme, useTheme} from "@mui/material/styles";
|
||||
import MuiDrawer from '@mui/material/Drawer';
|
||||
import MuiAppBar, { AppBarProps as MuiAppBarProps } from '@mui/material/AppBar';
|
||||
import { CSSObject, styled, Theme, useTheme } from "@mui/material/styles";
|
||||
import MuiDrawer from "@mui/material/Drawer";
|
||||
import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar";
|
||||
import Header from "../Header/index";
|
||||
import {Link} from 'react-router-dom';
|
||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||
import { Link } from "react-router-dom";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import Paper from "@mui/material/Paper";
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
const openedMixin = (theme: Theme): CSSObject => ({
|
||||
width: drawerWidth,
|
||||
background: '#2f3339',
|
||||
transition: theme.transitions.create('width', {
|
||||
background: "#2f3339",
|
||||
transition: theme.transitions.create("width", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
overflowX: 'hidden',
|
||||
overflowX: "hidden",
|
||||
});
|
||||
|
||||
const closedMixin = (theme: Theme): CSSObject => ({
|
||||
transition: theme.transitions.create('width', {
|
||||
transition: theme.transitions.create("width", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
overflowX: 'hidden',
|
||||
overflowX: "hidden",
|
||||
width: `calc(${theme.spacing(7)} + 1px)`,
|
||||
background: '#2f3339',
|
||||
// marginTop: '20px',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
background: "#2f3339",
|
||||
// marginTop: '20px',
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
width: `calc(${theme.spacing(8)} + 1px)`,
|
||||
},
|
||||
});
|
||||
|
||||
const DrawerHeader = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
const DrawerHeader = styled("div")(({ theme }) => ({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
padding: theme.spacing(0, 1),
|
||||
...theme.mixins.toolbar,
|
||||
}));
|
||||
@ -68,178 +68,181 @@ interface AppBarProps extends MuiAppBarProps {
|
||||
}
|
||||
|
||||
const AppBar = styled(MuiAppBar, {
|
||||
shouldForwardProp: (prop) => prop !== 'open',
|
||||
shouldForwardProp: (prop) => prop !== "open",
|
||||
})<AppBarProps>(({ theme, open }) => ({
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
transition: theme.transitions.create(['width', 'margin'], {
|
||||
transition: theme.transitions.create(["width", "margin"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
...(open && {
|
||||
marginLeft: drawerWidth,
|
||||
width: `calc(100% - ${drawerWidth}px)`,
|
||||
transition: theme.transitions.create(['width', 'margin'], {
|
||||
transition: theme.transitions.create(["width", "margin"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
||||
const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })(
|
||||
({ theme, open }) => ({
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
boxSizing: 'border-box',
|
||||
...(open && {
|
||||
...openedMixin(theme),
|
||||
'& .MuiDrawer-paper': openedMixin(theme),
|
||||
}),
|
||||
...(!open && {
|
||||
...closedMixin(theme),
|
||||
'& .MuiDrawer-paper': closedMixin(theme),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== "open" })(({ theme, open }) => ({
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
boxSizing: "border-box",
|
||||
...(open && {
|
||||
...openedMixin(theme),
|
||||
"& .MuiDrawer-paper": openedMixin(theme),
|
||||
}),
|
||||
...(!open && {
|
||||
...closedMixin(theme),
|
||||
"& .MuiDrawer-paper": closedMixin(theme),
|
||||
}),
|
||||
}));
|
||||
|
||||
const links: {path: string; element: JSX.Element; title: string, className: string} [] =[
|
||||
{path: '/users', element: <PersonOutlineOutlinedIcon/>, title: 'Информация о проекте', className:'menu'},
|
||||
{path: '/entities', element: <SettingsOutlinedIcon/>, title: 'Юридические лица', className:'menu'},
|
||||
{path: '/tariffs', element: <BathtubOutlinedIcon/>, title: 'Шаблонизатор документов', className:'menu'},
|
||||
{path: '/discounts', element: <AddPhotoAlternateOutlinedIcon/>, title: 'Скидки', className:'menu'},
|
||||
{path: '/promocode', element: <NaturePeopleOutlinedIcon/>, title: 'Промокод', className:'menu'},
|
||||
{path: '/kkk', element: <SettingsIcon/>, title: 'Настройки', className:'menu'},
|
||||
{path: '/jjj', element: <CameraIcon/>, title: 'Камера', className:'menu'},
|
||||
{path: '/support', element: <HeadsetMicOutlinedIcon/>, title: 'Служба поддержки', className:'menu'},
|
||||
]
|
||||
const links: { path: string; element: JSX.Element; title: string; className: string }[] = [
|
||||
{ path: "/users", element: <PersonOutlineOutlinedIcon />, title: "Информация о проекте", className: "menu" },
|
||||
{ path: "/entities", element: <SettingsOutlinedIcon />, title: "Юридические лица", className: "menu" },
|
||||
{ path: "/tariffs", element: <BathtubOutlinedIcon />, title: "Шаблонизатор документов", className: "menu" },
|
||||
{ path: "/discounts", element: <AddPhotoAlternateOutlinedIcon />, title: "Скидки", className: "menu" },
|
||||
{ path: "/promocode", element: <NaturePeopleOutlinedIcon />, title: "Промокод", className: "menu" },
|
||||
{ path: "/settingRoles", element: <SettingsIcon />, title: "Настройки", className: "menu" },
|
||||
{ path: "/jjj", element: <CameraIcon />, title: "Камера", className: "menu" },
|
||||
{ path: "/support", element: <HeadsetMicOutlinedIcon />, title: "Служба поддержки", className: "menu" },
|
||||
];
|
||||
|
||||
const Navigation = (props: any) => {
|
||||
return (
|
||||
<List
|
||||
sx={{
|
||||
background: "#2f3339",
|
||||
}}
|
||||
>
|
||||
{links.map((e, i) => (
|
||||
<ListItem key={i} disablePadding sx={{ display: "block" }}>
|
||||
<Link to={e.path} style={{ textDecoration: "none" }} className={e.className}>
|
||||
<ListItemButton
|
||||
onClick={props.SladeMobileHC}
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
height: "50px",
|
||||
justifyContent: props.visible ? "initial" : "center",
|
||||
px: 2.5,
|
||||
margin: "20px 0",
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
mr: props.visible ? 3 : "auto",
|
||||
justifyContent: "center",
|
||||
color: "#eaba5b",
|
||||
transform: "scale(1.2)",
|
||||
}}
|
||||
>
|
||||
{e.element}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={e.title}
|
||||
sx={{
|
||||
opacity: props.visible ? 1 : 0,
|
||||
color: "#eaba5b",
|
||||
}}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</Link>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
const Navigation = (props:any) => {
|
||||
return (
|
||||
<List
|
||||
sx={{
|
||||
background: '#2f3339'
|
||||
}}
|
||||
>
|
||||
{links.map((e, i) => (
|
||||
<ListItem key={i} disablePadding sx={{ display: 'block' }}>
|
||||
<Link to={e.path} style={{textDecoration: 'none'}} className={e.className}>
|
||||
<ListItemButton onClick={props.SladeMobileHC}
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
height: '50px',
|
||||
justifyContent: props.visible ? 'initial' : 'center',
|
||||
px: 2.5,
|
||||
margin: '20px 0'
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
mr: props.visible ? 3 : 'auto',
|
||||
justifyContent: 'center',
|
||||
color: '#eaba5b',
|
||||
transform: 'scale(1.2)'
|
||||
}}
|
||||
>
|
||||
{e.element}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={e.title}
|
||||
sx={{
|
||||
opacity: props.visible ? 1 : 0,
|
||||
color: '#eaba5b',
|
||||
}} />
|
||||
</ListItemButton>
|
||||
</Link>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
)
|
||||
}
|
||||
const Menu: React.FC = () => {
|
||||
const tablet = useMediaQuery("(max-width:600px)");
|
||||
|
||||
const mobile = useMediaQuery("(max-width:340px)");
|
||||
|
||||
const Menu: React.FC = () => {
|
||||
const tablet = useMediaQuery('(max-width:600px)');
|
||||
const theme = useTheme();
|
||||
const [open, setOpen] = React.useState(tablet ? false : true);
|
||||
|
||||
const mobile = useMediaQuery('(max-width:340px)');
|
||||
const handleDrawerOpen = () => {
|
||||
if (!mobile) {
|
||||
setOpen(true);
|
||||
} else {
|
||||
SladeMobileHC();
|
||||
}
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
const [open, setOpen] = React.useState(tablet? false : true);
|
||||
const handleDrawerClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleDrawerOpen = () => {
|
||||
if (!mobile) {setOpen(true)}
|
||||
else {SladeMobileHC()}
|
||||
};
|
||||
|
||||
const handleDrawerClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const [sladeMobile, setSladeMobile] = React.useState(false)
|
||||
const SladeMobileHC = () => {
|
||||
if (mobile) {setSladeMobile(old=>!old)}
|
||||
};
|
||||
const [sladeMobile, setSladeMobile] = React.useState(false);
|
||||
const SladeMobileHC = () => {
|
||||
if (mobile) {
|
||||
setSladeMobile((old) => !old);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<CssBaseline />
|
||||
<AppBar open={open}>
|
||||
<AppBar open={open}>
|
||||
<Toolbar
|
||||
sx={{
|
||||
background: '#2f3339',
|
||||
borderBottom: '1px solid',
|
||||
borderColor: '#45494c',
|
||||
}}
|
||||
sx={{
|
||||
background: "#2f3339",
|
||||
borderBottom: "1px solid",
|
||||
borderColor: "#45494c",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
aria-label="open drawer"
|
||||
onClick={handleDrawerOpen}
|
||||
edge="start"
|
||||
sx={{
|
||||
marginRight: 5,
|
||||
...(open && { display: 'none' }),
|
||||
color: "#eaba5b",
|
||||
background: '#2f3339'
|
||||
}}
|
||||
aria-label="open drawer"
|
||||
onClick={handleDrawerOpen}
|
||||
edge="start"
|
||||
sx={{
|
||||
marginRight: 5,
|
||||
...(open && { display: "none" }),
|
||||
color: "#eaba5b",
|
||||
background: "#2f3339",
|
||||
}}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Header />
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
{!mobile ? <Drawer variant="permanent" open={open}>
|
||||
<DrawerHeader style={{minHeight: '86px',}}>
|
||||
<IconButton onClick={handleDrawerClose} sx={{color: "#eaba5b"}}>
|
||||
<ChevronLeftIcon />
|
||||
</IconButton>
|
||||
</DrawerHeader>
|
||||
<Divider />
|
||||
<Navigation visible={open}/>
|
||||
</Drawer> : null}
|
||||
</Box>
|
||||
{sladeMobile ? <Paper
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
background: '#2f3339',
|
||||
zIndex: theme.zIndex.drawer + 3
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{display: 'flex',
|
||||
justifyContent: 'end',
|
||||
padding: '10px'
|
||||
}}
|
||||
>
|
||||
<IconButton onClick={SladeMobileHC} sx={{color: "#eaba5b"}}>
|
||||
<ChevronLeftIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
{!mobile ? (
|
||||
<Drawer variant="permanent" open={open}>
|
||||
<DrawerHeader style={{ minHeight: "86px" }}>
|
||||
<IconButton onClick={handleDrawerClose} sx={{ color: "#eaba5b" }}>
|
||||
<ChevronLeftIcon />
|
||||
</IconButton>
|
||||
</DrawerHeader>
|
||||
<Divider />
|
||||
<Navigation visible={true} SladeMobileHC={SladeMobileHC}/></Paper> : null}
|
||||
<Navigation visible={open} />
|
||||
</Drawer>
|
||||
) : null}
|
||||
</Box>
|
||||
{sladeMobile ? (
|
||||
<Paper
|
||||
sx={{
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
background: "#2f3339",
|
||||
zIndex: theme.zIndex.drawer + 3,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", justifyContent: "end", padding: "10px" }}>
|
||||
<IconButton onClick={SladeMobileHC} sx={{ color: "#eaba5b" }}>
|
||||
<ChevronLeftIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Divider />
|
||||
<Navigation visible={true} SladeMobileHC={SladeMobileHC} />
|
||||
</Paper>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export default Menu;
|
||||
export default Menu;
|
||||
|
@ -1,21 +1,102 @@
|
||||
import axios, { AxiosError, AxiosResponse } from "axios";
|
||||
import { create } from "zustand";
|
||||
import { devtools } from "zustand/middleware";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
type Token = string
|
||||
type Token = string;
|
||||
|
||||
interface AuthStore {
|
||||
token: Token
|
||||
setToken: (data: Token) => void;
|
||||
token: Token;
|
||||
setToken: (data: Token) => void;
|
||||
makeRequest: <TRequest = unknown, TResponse = unknown>(props: FirstRequest<TRequest>) => Promise<TResponse>;
|
||||
clearToken: () => void;
|
||||
}
|
||||
|
||||
interface FirstRequest<T> {
|
||||
method?: string;
|
||||
url: string;
|
||||
body?: T;
|
||||
useToken?: boolean;
|
||||
contentType?: boolean;
|
||||
bearer?: boolean;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export const authStore = create<AuthStore>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
token: "",
|
||||
setToken: newToken => set({ token: newToken })
|
||||
}),
|
||||
{
|
||||
name: "token",
|
||||
}
|
||||
)
|
||||
);
|
||||
persist(
|
||||
(set, get) => ({
|
||||
token: "",
|
||||
setToken: (newToken) => set({ token: newToken }),
|
||||
makeRequest: <TRequest, TResponse>(props: FirstRequest<TRequest>): Promise<TResponse> => {
|
||||
const newProps = { ...props, HC: (newToken: Token) => set({ token: newToken }), token: get().token };
|
||||
|
||||
return makeRequest<TRequest, TResponse>(newProps);
|
||||
},
|
||||
clearToken: () => set({ token: "" }),
|
||||
}),
|
||||
{
|
||||
name: "token",
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
interface MakeRequest<T> extends FirstRequest<T> {
|
||||
HC: (newToken: Token) => void;
|
||||
token: Token;
|
||||
}
|
||||
|
||||
async function makeRequest<TRequest, TResponse>({
|
||||
method = "post",
|
||||
url,
|
||||
body,
|
||||
useToken = true,
|
||||
signal,
|
||||
contentType = false,
|
||||
bearer = false,
|
||||
HC,
|
||||
token,
|
||||
}: MakeRequest<TRequest>) {
|
||||
//В случае 401 рефреш должен попробовать вызваться 1 раз
|
||||
let headers: any = {};
|
||||
if (useToken) headers["Authorization"] = bearer ? "Bearer " + token : token;
|
||||
if (contentType) headers["Content-Type"] = "application/json";
|
||||
|
||||
try {
|
||||
const { data } = await axios<TRequest, AxiosResponse<TResponse & { accessToken?: string }>>({
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
data: body,
|
||||
signal,
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
if (data?.accessToken) {
|
||||
HC(data.accessToken);
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (nativeError: unknown) {
|
||||
const error = nativeError as AxiosError;
|
||||
|
||||
if (error?.response?.status === 401) {
|
||||
const refreshResponse = await refresh(token);
|
||||
if (refreshResponse.data?.accessToken) HC(refreshResponse.data.accessToken);
|
||||
|
||||
headers["Authorization"] = refreshResponse.data.accessToken;
|
||||
const response = await axios<TRequest, AxiosResponse<TResponse>>({ url, method, headers, data: body, signal });
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function refresh(token:Token) {
|
||||
return axios<never, AxiosResponse<{ accessToken: string }>>("https://admin.pena.digital/auth/refresh", {
|
||||
headers: {
|
||||
Authorization: token,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -6,12 +6,22 @@ import { testMessages } from "./mocks/messages";
|
||||
|
||||
interface MessageStore {
|
||||
messages: TicketMessage[];
|
||||
fetchState: "idle" | "fetching" | "all fetched";
|
||||
apiPage: number;
|
||||
messagesPerPage: number;
|
||||
lastMessageId: string | undefined;
|
||||
isPreventAutoscroll: boolean;
|
||||
}
|
||||
|
||||
export const useMessageStore = create<MessageStore>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
messages: testMessages,
|
||||
messagesPerPage: 10,
|
||||
fetchState: "idle",
|
||||
apiPage: 0,
|
||||
lastMessageId: undefined,
|
||||
isPreventAutoscroll: false,
|
||||
}),
|
||||
{
|
||||
name: "Message store (admin)"
|
||||
@ -19,15 +29,39 @@ export const useMessageStore = create<MessageStore>()(
|
||||
)
|
||||
);
|
||||
|
||||
export const setMessages = (messages: TicketMessage[]) => useMessageStore.setState(({ messages }));
|
||||
|
||||
export const addOrUpdateMessages = (receivedMessages: TicketMessage[]) => {
|
||||
const state = useMessageStore.getState();
|
||||
const messageIdToMessageMap: { [messageId: string]: TicketMessage; } = {};
|
||||
|
||||
[...state.messages, ...receivedMessages].forEach(message => messageIdToMessageMap[message.id] = message);
|
||||
|
||||
useMessageStore.setState({ messages: Object.values(messageIdToMessageMap) });
|
||||
const sortedMessages = Object.values(messageIdToMessageMap).sort(sortMessagesByTime);
|
||||
|
||||
useMessageStore.setState({
|
||||
messages: sortedMessages,
|
||||
lastMessageId: sortedMessages.at(-1)?.id,
|
||||
});
|
||||
};
|
||||
|
||||
export const clearMessages = () => useMessageStore.setState({ messages: [] });
|
||||
export const clearMessageState = () => useMessageStore.setState({
|
||||
messages: [],
|
||||
apiPage: 0,
|
||||
lastMessageId: undefined,
|
||||
fetchState: "idle",
|
||||
});
|
||||
|
||||
export const setMessageFetchState = (fetchState: MessageStore["fetchState"]) => useMessageStore.setState({ fetchState });
|
||||
|
||||
export const incrementMessageApiPage = () => {
|
||||
const state = useMessageStore.getState();
|
||||
|
||||
useMessageStore.setState({ apiPage: state.apiPage + 1 });
|
||||
};
|
||||
|
||||
function sortMessagesByTime(ticket1: TicketMessage, ticket2: TicketMessage) {
|
||||
const date1 = new Date(ticket1.created_at).getTime();
|
||||
const date2 = new Date(ticket2.created_at).getTime();
|
||||
return date1 - date2;
|
||||
}
|
||||
|
||||
export const setIsPreventAutoscroll = (isPreventAutoscroll: boolean) => useMessageStore.setState({ isPreventAutoscroll });
|
@ -5,12 +5,18 @@ import { devtools } from "zustand/middleware";
|
||||
|
||||
interface TicketStore {
|
||||
tickets: Ticket[];
|
||||
fetchState: "idle" | "fetching" | "all fetched";
|
||||
apiPage: number;
|
||||
ticketsPerPage: number;
|
||||
}
|
||||
|
||||
export const useTicketStore = create<TicketStore>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
tickets: [],
|
||||
fetchState: "idle",
|
||||
apiPage: 0,
|
||||
ticketsPerPage: 20,
|
||||
}),
|
||||
{
|
||||
name: "Tickets store (admin)"
|
||||
@ -18,6 +24,15 @@ export const useTicketStore = create<TicketStore>()(
|
||||
)
|
||||
);
|
||||
|
||||
export const setTicketsFetchState = (fetchState: TicketStore["fetchState"]) => useTicketStore.setState({ fetchState });
|
||||
|
||||
export const incrementTicketsApiPage = () => {
|
||||
const state = useTicketStore.getState();
|
||||
if (state.fetchState !== "idle") return;
|
||||
|
||||
useTicketStore.setState({ apiPage: state.apiPage + 1 });
|
||||
};
|
||||
|
||||
export const updateTickets = (receivedTickets: Ticket[]) => {
|
||||
const state = useTicketStore.getState();
|
||||
const ticketIdToTicketMap: { [ticketId: string]: Ticket; } = {};
|
||||
|
Loading…
Reference in New Issue
Block a user