forked from VinokurovVE/tests
mantine
This commit is contained in:
1440
client/package-lock.json
generated
1440
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,24 +19,44 @@
|
|||||||
"@js-preview/docx": "^1.6.2",
|
"@js-preview/docx": "^1.6.2",
|
||||||
"@js-preview/excel": "^1.7.8",
|
"@js-preview/excel": "^1.7.8",
|
||||||
"@js-preview/pdf": "^2.0.2",
|
"@js-preview/pdf": "^2.0.2",
|
||||||
|
"@mantine/carousel": "^7.13.0",
|
||||||
|
"@mantine/charts": "^7.13.0",
|
||||||
|
"@mantine/code-highlight": "^7.13.0",
|
||||||
|
"@mantine/core": "^7.13.0",
|
||||||
|
"@mantine/dates": "^7.13.0",
|
||||||
|
"@mantine/dropzone": "^7.13.0",
|
||||||
|
"@mantine/form": "^7.13.0",
|
||||||
|
"@mantine/hooks": "^7.13.0",
|
||||||
|
"@mantine/modals": "^7.13.0",
|
||||||
|
"@mantine/notifications": "^7.13.0",
|
||||||
|
"@mantine/nprogress": "^7.13.0",
|
||||||
|
"@mantine/spotlight": "^7.13.0",
|
||||||
|
"@mantine/tiptap": "^7.13.0",
|
||||||
"@mui/icons-material": "^5.15.20",
|
"@mui/icons-material": "^5.15.20",
|
||||||
"@mui/material": "^5.15.20",
|
"@mui/material": "^5.15.20",
|
||||||
"@mui/x-charts": "^7.8.0",
|
"@mui/x-charts": "^7.8.0",
|
||||||
"@mui/x-data-grid": "^7.7.1",
|
"@mui/x-data-grid": "^7.7.1",
|
||||||
|
"@tabler/icons-react": "^3.17.0",
|
||||||
|
"@tiptap/extension-link": "^2.7.3",
|
||||||
|
"@tiptap/react": "^2.7.3",
|
||||||
|
"@tiptap/starter-kit": "^2.7.3",
|
||||||
"@types/ol-ext": "npm:@siedlerchr/types-ol-ext@^3.5.0",
|
"@types/ol-ext": "npm:@siedlerchr/types-ol-ext@^3.5.0",
|
||||||
"@uidotdev/usehooks": "^2.4.1",
|
"@uidotdev/usehooks": "^2.4.1",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"embla-carousel-react": "^8.3.0",
|
||||||
"file-type": "^19.0.0",
|
"file-type": "^19.0.0",
|
||||||
|
"ka-table": "^11.3.0",
|
||||||
"ol": "^10.0.0",
|
"ol": "^10.0.0",
|
||||||
"ol-ext": "^4.0.23",
|
"ol-ext": "^4.0.23",
|
||||||
"postcss": "^8.4.38",
|
|
||||||
"proj4": "^2.12.0",
|
"proj4": "^2.12.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.52.0",
|
"react-hook-form": "^7.52.0",
|
||||||
"react-router-dom": "^6.23.1",
|
"react-router-dom": "^6.23.1",
|
||||||
|
"recharts": "^2.12.7",
|
||||||
"swr": "^2.2.5",
|
"swr": "^2.2.5",
|
||||||
"zustand": "^4.5.2"
|
"zustand": "^4.5.2"
|
||||||
},
|
},
|
||||||
@ -50,6 +70,9 @@
|
|||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.6",
|
"eslint-plugin-react-refresh": "^0.4.6",
|
||||||
|
"postcss": "^8.4.47",
|
||||||
|
"postcss-preset-mantine": "^1.17.0",
|
||||||
|
"postcss-simple-vars": "^7.0.1",
|
||||||
"serve": "^14.2.3",
|
"serve": "^14.2.3",
|
||||||
"tailwindcss": "^3.4.4",
|
"tailwindcss": "^3.4.4",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
export default {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
'postcss-preset-mantine': {},
|
||||||
autoprefixer: {},
|
'postcss-simple-vars': {
|
||||||
|
variables: {
|
||||||
|
'mantine-breakpoint-xs': '36em',
|
||||||
|
'mantine-breakpoint-sm': '48em',
|
||||||
|
'mantine-breakpoint-md': '62em',
|
||||||
|
'mantine-breakpoint-lg': '75em',
|
||||||
|
'mantine-breakpoint-xl': '88em',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -3,31 +3,32 @@ import Main from "./pages/Main"
|
|||||||
import Users from "./pages/Users"
|
import Users from "./pages/Users"
|
||||||
import Roles from "./pages/Roles"
|
import Roles from "./pages/Roles"
|
||||||
import NotFound from "./pages/NotFound"
|
import NotFound from "./pages/NotFound"
|
||||||
import DashboardLayout from "./layouts/DashboardLayout"
|
|
||||||
import MainLayout from "./layouts/MainLayout"
|
import MainLayout from "./layouts/MainLayout"
|
||||||
import SignIn from "./pages/auth/SignIn"
|
import SignIn from "./pages/auth/SignIn"
|
||||||
import ApiTest from "./pages/ApiTest"
|
import ApiTest from "./pages/ApiTest"
|
||||||
import SignUp from "./pages/auth/SignUp"
|
import SignUp from "./pages/auth/SignUp"
|
||||||
import { initAuth, useAuthStore } from "./store/auth"
|
import { initAuth, useAuthStore } from "./store/auth"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { Box, CircularProgress } from "@mui/material"
|
|
||||||
import Documents from "./pages/Documents"
|
import Documents from "./pages/Documents"
|
||||||
import Reports from "./pages/Reports"
|
import Reports from "./pages/Reports"
|
||||||
import Boilers from "./pages/Boilers"
|
import Boilers from "./pages/Boilers"
|
||||||
import Servers from "./pages/Servers"
|
import Servers from "./pages/Servers"
|
||||||
import { Api, Assignment, Cloud, Factory, Home, Login, Map, MonitorHeart, Password, People, Settings as SettingsIcon, Shield, Storage, Warning } from "@mui/icons-material"
|
|
||||||
import Settings from "./pages/Settings"
|
import Settings from "./pages/Settings"
|
||||||
import PasswordReset from "./pages/auth/PasswordReset"
|
import PasswordReset from "./pages/auth/PasswordReset"
|
||||||
import MapTest from "./pages/MapTest"
|
import MapTest from "./pages/MapTest"
|
||||||
import MonitorPage from "./pages/MonitorPage"
|
import MonitorPage from "./pages/MonitorPage"
|
||||||
import ChunkedUpload from "./components/map/ChunkedUpload"
|
import ChunkedUpload from "./components/map/ChunkedUpload"
|
||||||
|
import DashboardLayout from "./layouts/DashboardLayout"
|
||||||
|
import { IconApi, IconBuildingFactory2, IconDeviceDesktopAnalytics, IconFiles, IconFlag2, IconHome, IconLogin, IconLogin2, IconMap, IconPassword, IconReport, IconServer, IconSettings, IconShield, IconTable, IconUsers } from "@tabler/icons-react"
|
||||||
|
import { Box, Loader } from "@mantine/core"
|
||||||
|
import TableTest from "./pages/TableTest"
|
||||||
|
|
||||||
// Определение страниц с путями и компонентом для рендера
|
// Определение страниц с путями и компонентом для рендера
|
||||||
export const pages = [
|
export const pages = [
|
||||||
{
|
{
|
||||||
label: "",
|
label: "",
|
||||||
path: "/auth/signin",
|
path: "/auth/signin",
|
||||||
icon: <Login />,
|
icon: <IconLogin2 />,
|
||||||
component: <SignIn />,
|
component: <SignIn />,
|
||||||
drawer: false,
|
drawer: false,
|
||||||
dashboard: false,
|
dashboard: false,
|
||||||
@ -36,7 +37,7 @@ export const pages = [
|
|||||||
{
|
{
|
||||||
label: "",
|
label: "",
|
||||||
path: "/auth/signup",
|
path: "/auth/signup",
|
||||||
icon: <Login />,
|
icon: <IconLogin />,
|
||||||
component: <SignUp />,
|
component: <SignUp />,
|
||||||
drawer: false,
|
drawer: false,
|
||||||
dashboard: false,
|
dashboard: false,
|
||||||
@ -45,7 +46,7 @@ export const pages = [
|
|||||||
{
|
{
|
||||||
label: "",
|
label: "",
|
||||||
path: "/auth/password-reset",
|
path: "/auth/password-reset",
|
||||||
icon: <Password />,
|
icon: <IconPassword />,
|
||||||
component: <PasswordReset />,
|
component: <PasswordReset />,
|
||||||
drawer: false,
|
drawer: false,
|
||||||
dashboard: false,
|
dashboard: false,
|
||||||
@ -54,7 +55,7 @@ export const pages = [
|
|||||||
{
|
{
|
||||||
label: "Настройки",
|
label: "Настройки",
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
icon: <SettingsIcon />,
|
icon: <IconSettings />,
|
||||||
component: <Settings />,
|
component: <Settings />,
|
||||||
drawer: false,
|
drawer: false,
|
||||||
dashboard: true,
|
dashboard: true,
|
||||||
@ -63,7 +64,7 @@ export const pages = [
|
|||||||
{
|
{
|
||||||
label: "Главная",
|
label: "Главная",
|
||||||
path: "/",
|
path: "/",
|
||||||
icon: <Home />,
|
icon: <IconHome />,
|
||||||
component: <Main />,
|
component: <Main />,
|
||||||
drawer: true,
|
drawer: true,
|
||||||
dashboard: true,
|
dashboard: true,
|
||||||
@ -72,7 +73,7 @@ export const pages = [
|
|||||||
{
|
{
|
||||||
label: "Пользователи",
|
label: "Пользователи",
|
||||||
path: "/user",
|
path: "/user",
|
||||||
icon: <People />,
|
icon: <IconUsers />,
|
||||||
component: <Users />,
|
component: <Users />,
|
||||||
drawer: true,
|
drawer: true,
|
||||||
dashboard: true,
|
dashboard: true,
|
||||||
@ -81,7 +82,7 @@ export const pages = [
|
|||||||
{
|
{
|
||||||
label: "Роли",
|
label: "Роли",
|
||||||
path: "/role",
|
path: "/role",
|
||||||
icon: <Shield />,
|
icon: <IconShield />,
|
||||||
component: <Roles />,
|
component: <Roles />,
|
||||||
drawer: true,
|
drawer: true,
|
||||||
dashboard: true,
|
dashboard: true,
|
||||||
@ -90,7 +91,7 @@ export const pages = [
|
|||||||
{
|
{
|
||||||
label: "Документы",
|
label: "Документы",
|
||||||
path: "/documents",
|
path: "/documents",
|
||||||
icon: <Storage />,
|
icon: <IconFiles />,
|
||||||
component: <Documents />,
|
component: <Documents />,
|
||||||
drawer: true,
|
drawer: true,
|
||||||
dashboard: true,
|
dashboard: true,
|
||||||
@ -99,7 +100,7 @@ export const pages = [
|
|||||||
{
|
{
|
||||||
label: "Отчеты",
|
label: "Отчеты",
|
||||||
path: "/reports",
|
path: "/reports",
|
||||||
icon: <Assignment />,
|
icon: <IconReport />,
|
||||||
component: <Reports />,
|
component: <Reports />,
|
||||||
drawer: true,
|
drawer: true,
|
||||||
dashboard: true,
|
dashboard: true,
|
||||||
@ -108,7 +109,7 @@ export const pages = [
|
|||||||
{
|
{
|
||||||
label: "Серверы",
|
label: "Серверы",
|
||||||
path: "/servers",
|
path: "/servers",
|
||||||
icon: <Cloud />,
|
icon: <IconServer />,
|
||||||
component: <Servers />,
|
component: <Servers />,
|
||||||
drawer: true,
|
drawer: true,
|
||||||
dashboard: true,
|
dashboard: true,
|
||||||
@ -117,7 +118,7 @@ export const pages = [
|
|||||||
{
|
{
|
||||||
label: "Котельные",
|
label: "Котельные",
|
||||||
path: "/boilers",
|
path: "/boilers",
|
||||||
icon: <Factory />,
|
icon: <IconBuildingFactory2 />,
|
||||||
component: <Boilers />,
|
component: <Boilers />,
|
||||||
drawer: true,
|
drawer: true,
|
||||||
dashboard: true,
|
dashboard: true,
|
||||||
@ -126,7 +127,7 @@ export const pages = [
|
|||||||
{
|
{
|
||||||
label: "API Test",
|
label: "API Test",
|
||||||
path: "/api-test",
|
path: "/api-test",
|
||||||
icon: <Api />,
|
icon: <IconApi />,
|
||||||
component: <ApiTest />,
|
component: <ApiTest />,
|
||||||
drawer: true,
|
drawer: true,
|
||||||
dashboard: true,
|
dashboard: true,
|
||||||
@ -135,16 +136,16 @@ export const pages = [
|
|||||||
{
|
{
|
||||||
label: "ИКС",
|
label: "ИКС",
|
||||||
path: "/map-test",
|
path: "/map-test",
|
||||||
icon: <Map />,
|
icon: <IconMap />,
|
||||||
component: <MapTest />,
|
component: <MapTest />,
|
||||||
drawer: true,
|
drawer: true,
|
||||||
dashboard: true,
|
dashboard: true,
|
||||||
enabled: false,
|
enabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Chunk test",
|
label: "Chunk test",
|
||||||
path: "/chunk-test",
|
path: "/chunk-test",
|
||||||
icon: <Warning />,
|
icon: <IconFlag2 />,
|
||||||
component: <ChunkedUpload />,
|
component: <ChunkedUpload />,
|
||||||
drawer: true,
|
drawer: true,
|
||||||
dashboard: true,
|
dashboard: true,
|
||||||
@ -153,12 +154,21 @@ export const pages = [
|
|||||||
{
|
{
|
||||||
label: "Монитор",
|
label: "Монитор",
|
||||||
path: "/monitor",
|
path: "/monitor",
|
||||||
icon: <MonitorHeart />,
|
icon: <IconDeviceDesktopAnalytics />,
|
||||||
component: <MonitorPage />,
|
component: <MonitorPage />,
|
||||||
drawer: true,
|
drawer: true,
|
||||||
dashboard: true,
|
dashboard: true,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Table test",
|
||||||
|
path: "/table-test",
|
||||||
|
icon: <IconTable />,
|
||||||
|
component: <TableTest />,
|
||||||
|
drawer: true,
|
||||||
|
dashboard: true,
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@ -178,14 +188,11 @@ function App() {
|
|||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<CircularProgress />
|
<Loader />
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box w='100%' h='100vh'>
|
||||||
width: "100%",
|
|
||||||
height: "100vh"
|
|
||||||
}}>
|
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<MainLayout />}>
|
<Route element={<MainLayout />}>
|
||||||
@ -194,7 +201,7 @@ function App() {
|
|||||||
))}
|
))}
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route element={auth.isAuthenticated ? <DashboardLayout /> : <Navigate to={"/auth/signin"} />}>
|
<Route element={auth.isAuthenticated ? <DashboardLayout></DashboardLayout> : <Navigate to={"/auth/signin"} />}>
|
||||||
{pages.filter((page) => page.dashboard).filter((page) => page.enabled).map((page, index) => (
|
{pages.filter((page) => page.dashboard).filter((page) => page.enabled).map((page, index) => (
|
||||||
<Route key={`dl-${index}`} path={page.path} element={page.component} />
|
<Route key={`dl-${index}`} path={page.path} element={page.component} />
|
||||||
))}
|
))}
|
||||||
|
@ -1,156 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import Box from '@mui/material/Box';
|
|
||||||
import Avatar from '@mui/material/Avatar';
|
|
||||||
import Menu from '@mui/material/Menu';
|
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
|
||||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
|
||||||
import IconButton from '@mui/material/IconButton';
|
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
|
||||||
import Settings from '@mui/icons-material/Settings';
|
|
||||||
import Logout from '@mui/icons-material/Logout';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { logout } from '../store/auth';
|
|
||||||
import { ListItemText, Switch, styled } from '@mui/material';
|
|
||||||
import { setDarkMode, usePrefStore } from '../store/preferences';
|
|
||||||
|
|
||||||
const Android12Switch = styled(Switch)(({ theme }) => ({
|
|
||||||
padding: 8,
|
|
||||||
'& .MuiSwitch-track': {
|
|
||||||
borderRadius: 22 / 2,
|
|
||||||
'&::before, &::after': {
|
|
||||||
content: '""',
|
|
||||||
position: 'absolute',
|
|
||||||
top: '50%',
|
|
||||||
transform: 'translateY(-50%)',
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
'&::before': {
|
|
||||||
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 24 24"><path fill="${encodeURIComponent(
|
|
||||||
theme.palette.getContrastText(theme.palette.primary.main),
|
|
||||||
)}" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"/></svg>')`,
|
|
||||||
left: 12,
|
|
||||||
},
|
|
||||||
'&::after': {
|
|
||||||
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 24 24"><path fill="${encodeURIComponent(
|
|
||||||
theme.palette.getContrastText(theme.palette.primary.main),
|
|
||||||
)}" d="M19,13H5V11H19V13Z" /></svg>')`,
|
|
||||||
right: 12,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'& .MuiSwitch-thumb': {
|
|
||||||
boxShadow: 'none',
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
margin: 2,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
export default function AccountMenu() {
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
|
||||||
|
|
||||||
const open = Boolean(anchorEl);
|
|
||||||
|
|
||||||
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
|
|
||||||
setAnchorEl(event.currentTarget);
|
|
||||||
};
|
|
||||||
const handleClose = () => {
|
|
||||||
setAnchorEl(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const prefStore = usePrefStore()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', textAlign: 'center' }}>
|
|
||||||
<Tooltip title="Account settings">
|
|
||||||
<IconButton
|
|
||||||
onClick={handleClick}
|
|
||||||
size="small"
|
|
||||||
sx={{ ml: 2 }}
|
|
||||||
aria-controls={open ? 'account-menu' : undefined}
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded={open ? 'true' : undefined}
|
|
||||||
>
|
|
||||||
<Avatar sx={{ width: 32, height: 32 }}></Avatar>
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Menu
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
id="account-menu"
|
|
||||||
open={open}
|
|
||||||
onClose={handleClose}
|
|
||||||
slotProps={{
|
|
||||||
paper: {
|
|
||||||
elevation: 0,
|
|
||||||
sx: {
|
|
||||||
overflow: 'visible',
|
|
||||||
filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
|
|
||||||
mt: 1.5,
|
|
||||||
'& .MuiAvatar-root': {
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
ml: -0.5,
|
|
||||||
mr: 1,
|
|
||||||
},
|
|
||||||
'&::before': {
|
|
||||||
content: '""',
|
|
||||||
display: 'block',
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
right: 14,
|
|
||||||
width: 10,
|
|
||||||
height: 10,
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
transform: 'translateY(-50%) rotate(45deg)',
|
|
||||||
zIndex: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
|
|
||||||
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
|
|
||||||
>
|
|
||||||
<MenuItem onClick={() => {
|
|
||||||
}}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<Android12Switch
|
|
||||||
checked={prefStore.darkMode}
|
|
||||||
onChange={(e) => {
|
|
||||||
setDarkMode(e.target.checked)
|
|
||||||
}} />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText>
|
|
||||||
Тема: {prefStore.darkMode ? "темная" : "светлая"}
|
|
||||||
</ListItemText>
|
|
||||||
</MenuItem>
|
|
||||||
|
|
||||||
|
|
||||||
<MenuItem onClick={() => {
|
|
||||||
navigate('/settings')
|
|
||||||
}}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<Settings fontSize="small" />
|
|
||||||
</ListItemIcon>
|
|
||||||
Настройки
|
|
||||||
</MenuItem>
|
|
||||||
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => {
|
|
||||||
logout()
|
|
||||||
navigate("/auth/signin")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
|
||||||
<Logout fontSize="small" />
|
|
||||||
</ListItemIcon>
|
|
||||||
Выход
|
|
||||||
</MenuItem>
|
|
||||||
</Menu>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,11 +1,13 @@
|
|||||||
import { useDocuments, useDownload, useFolders } from '../hooks/swrHooks'
|
import { useDocuments, useDownload, useFolders } from '../hooks/swrHooks'
|
||||||
import { IDocument, IDocumentFolder } from '../interfaces/documents'
|
import { IDocument, IDocumentFolder } from '../interfaces/documents'
|
||||||
import { Box, Breadcrumbs, Button, CircularProgress, Divider, IconButton, Link, List, ListItemButton, SxProps } from '@mui/material'
|
import { Box, CircularProgress, Divider, SxProps } from '@mui/material'
|
||||||
import { Cancel, Close, Download, Folder, InsertDriveFile, Upload, UploadFile } from '@mui/icons-material'
|
import { Folder, InsertDriveFile } from '@mui/icons-material'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import DocumentService from '../services/DocumentService'
|
import DocumentService from '../services/DocumentService'
|
||||||
import { mutate } from 'swr'
|
import { mutate } from 'swr'
|
||||||
import FileViewer from './modals/FileViewer'
|
import FileViewer from './modals/FileViewer'
|
||||||
|
import { ActionIcon, Anchor, Breadcrumbs, Button, FileButton, Flex, Loader, RingProgress, Table, Text } from '@mantine/core'
|
||||||
|
import { IconCancel, IconDownload, IconFile, IconFilePlus, IconFileUpload, IconX } from '@tabler/icons-react'
|
||||||
|
|
||||||
interface FolderProps {
|
interface FolderProps {
|
||||||
folder: IDocumentFolder;
|
folder: IDocumentFolder;
|
||||||
@ -31,7 +33,7 @@ const FileItemStyle: SxProps = {
|
|||||||
|
|
||||||
function ItemFolder({ folder, handleFolderClick, ...props }: FolderProps) {
|
function ItemFolder({ folder, handleFolderClick, ...props }: FolderProps) {
|
||||||
return (
|
return (
|
||||||
<ListItemButton
|
<Flex
|
||||||
onClick={() => handleFolderClick(folder)}
|
onClick={() => handleFolderClick(folder)}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@ -41,7 +43,7 @@ function ItemFolder({ folder, handleFolderClick, ...props }: FolderProps) {
|
|||||||
<Folder />
|
<Folder />
|
||||||
{folder.name}
|
{folder.name}
|
||||||
</Box>
|
</Box>
|
||||||
</ListItemButton>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +71,7 @@ function ItemDocument({ doc, index, handleDocumentClick, ...props }: DocumentPro
|
|||||||
}, [shouldFetch, file])
|
}, [shouldFetch, file])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItemButton>
|
<Flex align='center'>
|
||||||
<Box
|
<Box
|
||||||
sx={FileItemStyle}
|
sx={FileItemStyle}
|
||||||
onClick={() => handleDocumentClick(index)}
|
onClick={() => handleDocumentClick(index)}
|
||||||
@ -79,22 +81,21 @@ function ItemDocument({ doc, index, handleDocumentClick, ...props }: DocumentPro
|
|||||||
{doc.name}
|
{doc.name}
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<IconButton
|
<ActionIcon
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isLoading) {
|
if (!isLoading) {
|
||||||
setShouldFetch(true)
|
setShouldFetch(true)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
sx={{ ml: 'auto' }}
|
variant='subtle'>
|
||||||
>
|
|
||||||
{isLoading ?
|
{isLoading ?
|
||||||
<CircularProgress size={24} variant='indeterminate' />
|
<Loader size='sm' />
|
||||||
:
|
:
|
||||||
<Download />
|
<IconDownload />
|
||||||
}
|
}
|
||||||
</IconButton>
|
</ActionIcon>
|
||||||
</Box>
|
</Box>
|
||||||
</ListItemButton>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +106,6 @@ export default function FolderViewer() {
|
|||||||
const { documents, isLoading: documentsLoading } = useDocuments(currentFolder?.id)
|
const { documents, isLoading: documentsLoading } = useDocuments(currentFolder?.id)
|
||||||
const [uploadProgress, setUploadProgress] = useState(0)
|
const [uploadProgress, setUploadProgress] = useState(0)
|
||||||
const [isUploading, setIsUploading] = useState(false)
|
const [isUploading, setIsUploading] = useState(false)
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
||||||
const [fileViewerModal, setFileViewerModal] = useState(false)
|
const [fileViewerModal, setFileViewerModal] = useState(false)
|
||||||
const [currentFileNo, setCurrentFileNo] = useState<number>(-1)
|
const [currentFileNo, setCurrentFileNo] = useState<number>(-1)
|
||||||
|
|
||||||
@ -128,12 +128,6 @@ export default function FolderViewer() {
|
|||||||
setCurrentFolder(newBreadcrumbs[newBreadcrumbs.length - 1])
|
setCurrentFolder(newBreadcrumbs[newBreadcrumbs.length - 1])
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUploadClick = () => {
|
|
||||||
if (fileInputRef.current) {
|
|
||||||
fileInputRef.current.click()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDragOver = (e: React.DragEvent) => {
|
const handleDragOver = (e: React.DragEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setDragOver(true)
|
setDragOver(true)
|
||||||
@ -150,9 +144,10 @@ export default function FolderViewer() {
|
|||||||
setFilesToUpload((prevFiles) => [...prevFiles, ...files])
|
setFilesToUpload((prevFiles) => [...prevFiles, ...files])
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileInput = (files: File[] | null) => {
|
||||||
const files = Array.from(e.target.files || [])
|
if (files !== null) {
|
||||||
setFilesToUpload((prevFiles) => [...prevFiles, ...files])
|
setFilesToUpload((prevFiles) => [...prevFiles, ...files])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const uploadFiles = async () => {
|
const uploadFiles = async () => {
|
||||||
@ -196,28 +191,21 @@ export default function FolderViewer() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<Link
|
<Anchor
|
||||||
underline='hover'
|
|
||||||
color='inherit'
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentFolder(null)
|
setCurrentFolder(null)
|
||||||
setBreadcrumbs([])
|
setBreadcrumbs([])
|
||||||
}}
|
}}
|
||||||
sx={{ cursor: 'pointer' }}
|
|
||||||
>
|
>
|
||||||
Главная
|
Главная
|
||||||
</Link>
|
</Anchor>
|
||||||
|
|
||||||
{breadcrumbs.map((breadcrumb, index) => (
|
{breadcrumbs.map((breadcrumb, index) => (
|
||||||
<Link
|
<Anchor
|
||||||
key={breadcrumb.id}
|
key={breadcrumb.id}
|
||||||
underline="hover"
|
|
||||||
color="inherit"
|
|
||||||
onClick={() => handleBreadcrumbClick(index)}
|
onClick={() => handleBreadcrumbClick(index)}
|
||||||
sx={{ cursor: 'pointer' }}
|
|
||||||
>
|
>
|
||||||
{breadcrumb.name}
|
{breadcrumb.name}
|
||||||
</Link>
|
</Anchor>
|
||||||
))}
|
))}
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
|
|
||||||
@ -232,44 +220,23 @@ export default function FolderViewer() {
|
|||||||
p: '16px'
|
p: '16px'
|
||||||
}}>
|
}}>
|
||||||
<Box sx={{ display: 'flex', gap: '16px' }}>
|
<Box sx={{ display: 'flex', gap: '16px' }}>
|
||||||
<Button
|
<FileButton multiple onChange={handleFileInput}>
|
||||||
LinkComponent="label"
|
{(props) => <Button variant='filled' leftSection={isUploading ? <Loader /> : <IconFilePlus />} {...props}>Добавить</Button>}
|
||||||
role={undefined}
|
</FileButton>
|
||||||
variant="outlined"
|
|
||||||
tabIndex={-1}
|
|
||||||
startIcon={
|
|
||||||
isUploading ? <CircularProgress sx={{ maxHeight: "20px", maxWidth: "20px" }} variant="determinate" value={uploadProgress} /> : <UploadFile />
|
|
||||||
}
|
|
||||||
onClick={handleUploadClick}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type='file'
|
|
||||||
ref={fileInputRef}
|
|
||||||
style={{ display: 'none' }}
|
|
||||||
onChange={handleFileInput}
|
|
||||||
onClick={(e) => {
|
|
||||||
if (e.currentTarget) {
|
|
||||||
e.currentTarget.value = ''
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
Добавить
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{filesToUpload.length > 0 &&
|
{filesToUpload.length > 0 &&
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant='filled'
|
||||||
color="primary"
|
leftSection={isUploading ? <RingProgress sections={[{ value: uploadProgress, color: 'blue' }]} /> : <IconFileUpload />}
|
||||||
startIcon={<Upload />}
|
|
||||||
onClick={uploadFiles}
|
onClick={uploadFiles}
|
||||||
>
|
>
|
||||||
Загрузить все
|
Загрузить все
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant='outlined'
|
variant='outline'
|
||||||
startIcon={<Cancel />}
|
leftSection={<IconCancel />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFilesToUpload([])
|
setFilesToUpload([])
|
||||||
}}
|
}}
|
||||||
@ -283,62 +250,69 @@ export default function FolderViewer() {
|
|||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
{filesToUpload.length > 0 &&
|
{filesToUpload.length > 0 &&
|
||||||
<Box>
|
<Flex direction='column'>
|
||||||
{filesToUpload.map((file, index) => (
|
{filesToUpload.map((file, index) => (
|
||||||
<Box key={index} sx={{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '8px' }}>
|
<Flex key={index} p='8px'>
|
||||||
<Box>
|
<Flex gap='sm' direction='row' align='center'>
|
||||||
<InsertDriveFile />
|
<IconFile />
|
||||||
<span>{file.name}</span>
|
<Text>{file.name}</Text>
|
||||||
</Box>
|
</Flex>
|
||||||
|
|
||||||
<IconButton sx={{ ml: 'auto' }} onClick={() => {
|
<ActionIcon onClick={() => {
|
||||||
setFilesToUpload(prev => {
|
setFilesToUpload(prev => {
|
||||||
return prev.filter((_, i) => i != index)
|
return prev.filter((_, i) => i != index)
|
||||||
})
|
})
|
||||||
}}>
|
}} ml='auto' variant='subtle'>
|
||||||
<Close />
|
<IconX />
|
||||||
</IconButton>
|
</ActionIcon>
|
||||||
</Box>
|
</Flex>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Flex>
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
|
|
||||||
<List
|
<Table
|
||||||
dense
|
|
||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
onDragLeave={handleDragLeave}
|
onDragLeave={handleDragLeave}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
sx={{
|
bg={dragOver ? 'rgba(0, 0, 0, 0.1)' : 'inherit'}
|
||||||
backgroundColor: dragOver ? 'rgba(0, 0, 0, 0.1)' : 'inherit'
|
highlightOnHover>
|
||||||
}}
|
<Table.Thead>
|
||||||
>
|
<Table.Tr>
|
||||||
{currentFolder ? (
|
|
||||||
documents?.map((doc: IDocument, index: number) => (
|
</Table.Tr>
|
||||||
<div key={`${doc.id}-${doc.name}`}>
|
</Table.Thead>
|
||||||
<ItemDocument
|
|
||||||
doc={doc}
|
<Table.Tbody>
|
||||||
index={index}
|
{currentFolder ? (
|
||||||
handleDocumentClick={handleDocumentClick}
|
documents?.map((doc: IDocument, index: number) => (
|
||||||
/>
|
<Table.Tr key={doc.id}>
|
||||||
{index < documents.length - 1 && <Divider />}
|
<Table.Td>
|
||||||
</div>
|
<ItemDocument
|
||||||
))
|
doc={doc}
|
||||||
) : (
|
index={index}
|
||||||
folders?.map((folder: IDocumentFolder, index: number) => (
|
handleDocumentClick={handleDocumentClick}
|
||||||
<div key={`${folder.id}-${folder.name}`}>
|
/>
|
||||||
<ItemFolder
|
</Table.Td>
|
||||||
folder={folder}
|
</Table.Tr>
|
||||||
index={index}
|
))
|
||||||
handleFolderClick={handleFolderClick}
|
) : (
|
||||||
/>
|
folders?.map((folder: IDocumentFolder, index: number) => (
|
||||||
{index < folders.length - 1 && <Divider />}
|
<Table.Tr key={folder.id}>
|
||||||
</div>
|
<Table.Td>
|
||||||
))
|
<ItemFolder
|
||||||
)}
|
folder={folder}
|
||||||
</List>
|
index={index}
|
||||||
|
handleFolderClick={handleFolderClick}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { SubmitHandler, useForm } from 'react-hook-form'
|
import { SubmitHandler, useForm } from 'react-hook-form'
|
||||||
import { CreateField } from '../interfaces/create'
|
import { CreateField } from '../interfaces/create'
|
||||||
import { Box, Button, CircularProgress, Stack, SxProps, TextField, Typography } from '@mui/material';
|
|
||||||
import { AxiosResponse } from 'axios';
|
import { AxiosResponse } from 'axios';
|
||||||
|
import { Button, Loader, Stack, Text, TextInput } from '@mantine/core';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -11,7 +11,6 @@ interface Props {
|
|||||||
mutateHandler?: any;
|
mutateHandler?: any;
|
||||||
defaultValues?: {};
|
defaultValues?: {};
|
||||||
watchValues?: string[];
|
watchValues?: string[];
|
||||||
sx?: SxProps | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function FormFields({
|
function FormFields({
|
||||||
@ -20,8 +19,7 @@ function FormFields({
|
|||||||
fields,
|
fields,
|
||||||
submitButtonText = 'Сохранить',
|
submitButtonText = 'Сохранить',
|
||||||
mutateHandler,
|
mutateHandler,
|
||||||
defaultValues,
|
defaultValues
|
||||||
sx
|
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const getDefaultValues = (fields: CreateField[]) => {
|
const getDefaultValues = (fields: CreateField[]) => {
|
||||||
let result: { [key: string]: string | boolean } = {}
|
let result: { [key: string]: string | boolean } = {}
|
||||||
@ -53,20 +51,20 @@ function FormFields({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Stack sx={sx} spacing={2} width='100%'>
|
<Stack gap='sm' w='100%'>
|
||||||
<Typography variant="h6" component="h6" gutterBottom>
|
{title.length > 0 &&
|
||||||
{title}
|
<Text size="xl" fw={500}>
|
||||||
</Typography>
|
{title}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
|
||||||
{fields.map((field: CreateField) => {
|
{fields.map((field: CreateField) => {
|
||||||
return (
|
return (
|
||||||
<TextField
|
<TextInput
|
||||||
fullWidth
|
|
||||||
margin='normal'
|
|
||||||
key={field.key}
|
key={field.key}
|
||||||
type={field.inputType ? field.inputType : 'text'}
|
|
||||||
label={field.headerName || field.key.charAt(0).toUpperCase() + field.key.slice(1)}
|
label={field.headerName || field.key.charAt(0).toUpperCase() + field.key.slice(1)}
|
||||||
required={field.required || false}
|
//placeholder="Your name"
|
||||||
|
type={field.inputType ? field.inputType : 'text'}
|
||||||
{...register(field.key, {
|
{...register(field.key, {
|
||||||
required: field.required ? `${field.headerName} обязателен` : false,
|
required: field.required ? `${field.headerName} обязателен` : false,
|
||||||
validate: (val: string | boolean) => {
|
validate: (val: string | boolean) => {
|
||||||
@ -77,21 +75,17 @@ function FormFields({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
error={!!errors[field.key]}
|
radius="md"
|
||||||
helperText={errors[field.key]?.message}
|
required={field.required || false}
|
||||||
|
error={errors[field.key]?.message}
|
||||||
|
errorProps={errors[field.key]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<Box sx={{
|
<Button disabled={isSubmitting || Object.keys(dirtyFields).length === 0 || !isValid} type='submit'>
|
||||||
display: "flex",
|
{isSubmitting ? <Loader size={16} /> : submitButtonText}
|
||||||
justifyContent: "space-between",
|
</Button>
|
||||||
gap: "8px"
|
|
||||||
}}>
|
|
||||||
<Button disabled={isSubmitting || Object.keys(dirtyFields).length === 0 || !isValid} type="submit" variant="contained" color="primary">
|
|
||||||
{isSubmitting ? <CircularProgress size={16} /> : submitButtonText}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Box } from '@mui/material'
|
import { Box } from '@mui/material'
|
||||||
import { IServer } from '../interfaces/servers'
|
import { IServer } from '../interfaces/servers'
|
||||||
import { useServerIps } from '../hooks/swrHooks'
|
import { useServerIps } from '../hooks/swrHooks'
|
||||||
import FullFeaturedCrudGrid from './TableEditable'
|
|
||||||
import { GridColDef } from '@mui/x-data-grid'
|
import { GridColDef } from '@mui/x-data-grid'
|
||||||
|
import { Table } from '@mantine/core'
|
||||||
|
|
||||||
function ServerData({ id }: IServer) {
|
function ServerData({ id }: IServer) {
|
||||||
const { serverIps } = useServerIps(id, 0, 10)
|
const { serverIps } = useServerIps(id, 0, 10)
|
||||||
@ -19,18 +19,34 @@ function ServerData({ id }: IServer) {
|
|||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', p: '16px' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', p: '16px' }}>
|
||||||
{serverIps &&
|
{serverIps &&
|
||||||
<FullFeaturedCrudGrid
|
// <FullFeaturedCrudGrid
|
||||||
initialRows={serverIps}
|
// initialRows={serverIps}
|
||||||
columns={serverIpsColumns}
|
// columns={serverIpsColumns}
|
||||||
actions
|
// actions
|
||||||
onRowClick={() => {
|
// onRowClick={() => {
|
||||||
//setCurrentServerData(params.row)
|
// //setCurrentServerData(params.row)
|
||||||
//setServerDataOpen(true)
|
// //setServerDataOpen(true)
|
||||||
}}
|
// }}
|
||||||
onSave={undefined}
|
// onSave={undefined}
|
||||||
onDelete={undefined}
|
// onDelete={undefined}
|
||||||
loading={false}
|
// loading={false}
|
||||||
/>
|
// />
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
{serverIpsColumns.map(column => (
|
||||||
|
<Table.Th key={column.field}>{column.headerName}</Table.Th>
|
||||||
|
))}
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
<Table.Tr>
|
||||||
|
{serverIpsColumns.map(column => (
|
||||||
|
<Table.Td key={column.field}>{serverIps ? serverIps[column.field] : ''}</Table.Td>
|
||||||
|
))}
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import { AppBar, Autocomplete, CircularProgress, Dialog, IconButton, TextField, Toolbar } from '@mui/material'
|
import { AppBar, CircularProgress, Dialog, IconButton, Toolbar } from '@mui/material'
|
||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
import { IRegion } from '../interfaces/fuel'
|
import { IRegion } from '../interfaces/fuel'
|
||||||
import { useHardwares, useServers } from '../hooks/swrHooks'
|
import { useHardwares, useServers } from '../hooks/swrHooks'
|
||||||
import FullFeaturedCrudGrid from './TableEditable'
|
|
||||||
import ServerService from '../services/ServersService'
|
import ServerService from '../services/ServersService'
|
||||||
import { GridColDef } from '@mui/x-data-grid'
|
import { GridColDef } from '@mui/x-data-grid'
|
||||||
import { Close } from '@mui/icons-material'
|
import { Close } from '@mui/icons-material'
|
||||||
import ServerData from './ServerData'
|
import ServerData from './ServerData'
|
||||||
|
import { Autocomplete, CloseButton, Table } from '@mantine/core'
|
||||||
|
import { IServer } from '../interfaces/servers'
|
||||||
|
|
||||||
export default function ServerHardware() {
|
export default function ServerHardware() {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [selectedOption, setSelectedOption] = useState<IRegion | null>(null)
|
const [selectedOption, setSelectedOption] = useState<number | null>(null)
|
||||||
const { servers, isLoading } = useServers()
|
const { servers, isLoading } = useServers()
|
||||||
|
|
||||||
const [serverDataOpen, setServerDataOpen] = useState(false)
|
const [serverDataOpen, setServerDataOpen] = useState(false)
|
||||||
@ -71,54 +72,96 @@ export default function ServerHardware() {
|
|||||||
}
|
}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<Autocomplete
|
||||||
|
placeholder="Сервер"
|
||||||
|
flex={'1'}
|
||||||
|
data={servers ? servers.map((item: IServer) => ({ label: item.name, value: item.id.toString() })) : []}
|
||||||
|
onSelect={(e) => console.log(e.currentTarget.value)}
|
||||||
|
//onChange={(value) => setSearch(value)}
|
||||||
|
onOptionSubmit={(value) => setSelectedOption(Number(value))}
|
||||||
|
rightSection={
|
||||||
|
//search !== '' &&
|
||||||
|
(
|
||||||
|
<CloseButton
|
||||||
|
size="sm"
|
||||||
|
onMouseDown={(event) => event.preventDefault()}
|
||||||
|
onClick={() => {
|
||||||
|
//setSearch('')
|
||||||
|
setSelectedOption(null)
|
||||||
|
}}
|
||||||
|
aria-label="Clear value"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
//value={search}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
|
||||||
{serversLoading ?
|
{serversLoading ?
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
:
|
:
|
||||||
<FullFeaturedCrudGrid
|
// <FullFeaturedCrudGrid
|
||||||
autoComplete={
|
// autoComplete={
|
||||||
<Autocomplete
|
// <Autocomplete
|
||||||
open={open}
|
// open={open}
|
||||||
onOpen={() => {
|
// onOpen={() => {
|
||||||
setOpen(true)
|
// setOpen(true)
|
||||||
}}
|
// }}
|
||||||
onClose={() => {
|
// onClose={() => {
|
||||||
setOpen(false)
|
// setOpen(false)
|
||||||
}}
|
// }}
|
||||||
onInputChange={(_, value) => handleInputChange(value)}
|
// onInputChange={(_, value) => handleInputChange(value)}
|
||||||
onChange={(_, value) => handleOptionChange(value)}
|
// onChange={(_, value) => handleOptionChange(value)}
|
||||||
filterOptions={(x) => x}
|
// filterOptions={(x) => x}
|
||||||
isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name}
|
// isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name}
|
||||||
getOptionLabel={(option: IRegion) => option.name ? option.name : ""}
|
// getOptionLabel={(option: IRegion) => option.name ? option.name : ""}
|
||||||
options={servers || []}
|
// options={servers || []}
|
||||||
loading={isLoading}
|
// loading={isLoading}
|
||||||
value={selectedOption}
|
// value={selectedOption}
|
||||||
renderInput={(params) => (
|
// renderInput={(params) => (
|
||||||
<TextField
|
// <TextField
|
||||||
{...params}
|
// {...params}
|
||||||
label="Сервер"
|
// label="Сервер"
|
||||||
size='small'
|
// size='small'
|
||||||
InputProps={{
|
// InputProps={{
|
||||||
...params.InputProps,
|
// ...params.InputProps,
|
||||||
endAdornment: (
|
// endAdornment: (
|
||||||
<Fragment>
|
// <Fragment>
|
||||||
{isLoading ? <CircularProgress color="inherit" size={20} /> : null}
|
// {isLoading ? <CircularProgress color="inherit" size={20} /> : null}
|
||||||
{params.InputProps.endAdornment}
|
// {params.InputProps.endAdornment}
|
||||||
</Fragment>
|
// </Fragment>
|
||||||
)
|
// )
|
||||||
}} />
|
// }} />
|
||||||
)} />}
|
// )} />}
|
||||||
onSave={() => {
|
// onSave={() => {
|
||||||
}}
|
// }}
|
||||||
onDelete={ServerService.removeServer}
|
// onDelete={ServerService.removeServer}
|
||||||
initialRows={hardwares || []}
|
// initialRows={hardwares || []}
|
||||||
columns={hardwareColumns}
|
// columns={hardwareColumns}
|
||||||
actions
|
// actions
|
||||||
onRowClick={(params) => {
|
// onRowClick={(params) => {
|
||||||
setCurrentServerData(params.row)
|
// setCurrentServerData(params.row)
|
||||||
setServerDataOpen(true)
|
// setServerDataOpen(true)
|
||||||
}}
|
// }}
|
||||||
loading={false}
|
// loading={false}
|
||||||
/>
|
// />
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
{hardwareColumns.map(column => (
|
||||||
|
<Table.Th key={column.field}>{column.headerName}</Table.Th>
|
||||||
|
))}
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
<Table.Tr>
|
||||||
|
{hardwareColumns.map(column => (
|
||||||
|
<Table.Td key={column.field}>{hardwares ? hardwares[column.field] : ''}</Table.Td>
|
||||||
|
))}
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import { AppBar, Autocomplete, CircularProgress, Dialog, IconButton, TextField, Toolbar } from '@mui/material'
|
import { AppBar, CircularProgress, Dialog, IconButton, TextField, Toolbar } from '@mui/material'
|
||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
import { IRegion } from '../interfaces/fuel'
|
import { IRegion } from '../interfaces/fuel'
|
||||||
import { useServerIps, useServers } from '../hooks/swrHooks'
|
import { useServerIps, useServers } from '../hooks/swrHooks'
|
||||||
import FullFeaturedCrudGrid from './TableEditable'
|
|
||||||
import ServerService from '../services/ServersService'
|
import ServerService from '../services/ServersService'
|
||||||
import { GridColDef } from '@mui/x-data-grid'
|
import { GridColDef } from '@mui/x-data-grid'
|
||||||
import { Close } from '@mui/icons-material'
|
import { Close } from '@mui/icons-material'
|
||||||
import ServerData from './ServerData'
|
import ServerData from './ServerData'
|
||||||
|
import { Autocomplete, CloseButton, Table } from '@mantine/core'
|
||||||
|
import { IServer } from '../interfaces/servers'
|
||||||
|
|
||||||
export default function ServerIpsView() {
|
export default function ServerIpsView() {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [selectedOption, setSelectedOption] = useState<IRegion | null>(null)
|
const [selectedOption, setSelectedOption] = useState<number | null>(null)
|
||||||
const { servers, isLoading } = useServers()
|
const { servers, isLoading } = useServers()
|
||||||
|
|
||||||
const [serverDataOpen, setServerDataOpen] = useState(false)
|
const [serverDataOpen, setServerDataOpen] = useState(false)
|
||||||
@ -69,52 +70,97 @@ export default function ServerIpsView() {
|
|||||||
}
|
}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<Autocomplete
|
||||||
|
placeholder="Сервер"
|
||||||
|
flex={'1'}
|
||||||
|
data={servers ? servers.map((item: IServer) => ({ label: item.name, value: item.id.toString() })) : []}
|
||||||
|
onSelect={(e) => console.log(e.currentTarget.value)}
|
||||||
|
//onChange={(value) => setSearch(value)}
|
||||||
|
onOptionSubmit={(value) => setSelectedOption(Number(value))}
|
||||||
|
rightSection={
|
||||||
|
//search !== '' &&
|
||||||
|
(
|
||||||
|
<CloseButton
|
||||||
|
size="sm"
|
||||||
|
onMouseDown={(event) => event.preventDefault()}
|
||||||
|
onClick={() => {
|
||||||
|
//setSearch('')
|
||||||
|
setSelectedOption(null)
|
||||||
|
}}
|
||||||
|
aria-label="Clear value"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
//value={search}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
|
||||||
{serversLoading ?
|
{serversLoading ?
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
:
|
:
|
||||||
<FullFeaturedCrudGrid
|
// <FullFeaturedCrudGrid
|
||||||
autoComplete={
|
// autoComplete={
|
||||||
<Autocomplete
|
// <Autocomplete
|
||||||
open={open}
|
// open={open}
|
||||||
onOpen={() => {
|
// onOpen={() => {
|
||||||
setOpen(true)
|
// setOpen(true)
|
||||||
}}
|
// }}
|
||||||
onClose={() => {
|
// onClose={() => {
|
||||||
setOpen(false)
|
// setOpen(false)
|
||||||
}}
|
// }}
|
||||||
onInputChange={(_, value) => handleInputChange(value)}
|
// onInputChange={(_, value) => handleInputChange(value)}
|
||||||
onChange={(_, value) => handleOptionChange(value)}
|
// onChange={(_, value) => handleOptionChange(value)}
|
||||||
filterOptions={(x) => x}
|
// filterOptions={(x) => x}
|
||||||
isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name}
|
// isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name}
|
||||||
getOptionLabel={(option: IRegion) => option.name ? option.name : ""}
|
// getOptionLabel={(option: IRegion) => option.name ? option.name : ""}
|
||||||
options={servers || []}
|
// options={servers || []}
|
||||||
loading={isLoading}
|
// loading={isLoading}
|
||||||
value={selectedOption}
|
// value={selectedOption}
|
||||||
renderInput={(params) => (
|
// renderInput={(params) => (
|
||||||
<TextField
|
// <TextField
|
||||||
{...params}
|
// {...params}
|
||||||
size='small'
|
// size='small'
|
||||||
label="Сервер"
|
// label="Сервер"
|
||||||
InputProps={{
|
// InputProps={{
|
||||||
...params.InputProps,
|
// ...params.InputProps,
|
||||||
endAdornment: (
|
// endAdornment: (
|
||||||
<Fragment>
|
// <Fragment>
|
||||||
{isLoading ? <CircularProgress color="inherit" size={20} /> : null}
|
// {isLoading ? <CircularProgress color="inherit" size={20} /> : null}
|
||||||
{params.InputProps.endAdornment}
|
// {params.InputProps.endAdornment}
|
||||||
</Fragment>
|
// </Fragment>
|
||||||
)
|
// )
|
||||||
}} />
|
// }} />
|
||||||
)} />}
|
// )} />}
|
||||||
onSave={() => {
|
// onSave={() => {
|
||||||
}}
|
// }}
|
||||||
onDelete={ServerService.removeServer}
|
// onDelete={ServerService.removeServer}
|
||||||
initialRows={serverIps || []}
|
// initialRows={serverIps || []}
|
||||||
columns={serverIpsColumns}
|
// columns={serverIpsColumns}
|
||||||
actions
|
// actions
|
||||||
onRowClick={(params) => {
|
// onRowClick={(params) => {
|
||||||
setCurrentServerData(params.row)
|
// setCurrentServerData(params.row)
|
||||||
setServerDataOpen(true)
|
// setServerDataOpen(true)
|
||||||
}} loading={false} />
|
// }} loading={false}
|
||||||
|
// />
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
{serverIpsColumns.map(column => (
|
||||||
|
<Table.Th key={column.field}>{column.headerName}</Table.Th>
|
||||||
|
))}
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
<Table.Tr
|
||||||
|
//bg={selectedRows.includes(element.position) ? 'var(--mantine-color-blue-light)' : undefined}
|
||||||
|
>
|
||||||
|
{serverIpsColumns.map(column => (
|
||||||
|
<Table.Td key={column.field}>{servers ? servers[column.field] : ''}</Table.Td>
|
||||||
|
))}
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { AppBar, Autocomplete, Box, CircularProgress, Dialog, Grid, IconButton, TextField, Toolbar } from '@mui/material'
|
import { AppBar, Box, CircularProgress, Dialog, Grid, IconButton, TextField, Toolbar } from '@mui/material'
|
||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
import { IRegion } from '../interfaces/fuel'
|
import { IRegion } from '../interfaces/fuel'
|
||||||
import { useRegions, useServers, useServersInfo } from '../hooks/swrHooks'
|
import { useRegions, useServers, useServersInfo } from '../hooks/swrHooks'
|
||||||
import FullFeaturedCrudGrid from './TableEditable'
|
|
||||||
import ServerService from '../services/ServersService'
|
import ServerService from '../services/ServersService'
|
||||||
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid'
|
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid'
|
||||||
import { Close, Cloud, CloudOff } from '@mui/icons-material'
|
import { Close, Cloud, CloudOff } from '@mui/icons-material'
|
||||||
@ -12,22 +11,23 @@ import CardInfo from './CardInfo/CardInfo'
|
|||||||
import CardInfoLabel from './CardInfo/CardInfoLabel'
|
import CardInfoLabel from './CardInfo/CardInfoLabel'
|
||||||
import CardInfoChip from './CardInfo/CardInfoChip'
|
import CardInfoChip from './CardInfo/CardInfoChip'
|
||||||
import { useDebounce } from '@uidotdev/usehooks'
|
import { useDebounce } from '@uidotdev/usehooks'
|
||||||
|
import { Autocomplete, CloseButton, Table } from '@mantine/core'
|
||||||
|
|
||||||
export default function ServersView() {
|
export default function ServersView() {
|
||||||
const [search, setSearch] = useState<string | null>("")
|
const [search, setSearch] = useState<string | undefined>("")
|
||||||
|
|
||||||
const debouncedSearch = useDebounce(search, 500)
|
const debouncedSearch = useDebounce(search, 500)
|
||||||
|
|
||||||
const [selectedOption, setSelectedOption] = useState<IRegion | null>(null)
|
const [selectedOption, setSelectedOption] = useState<number | null>(null)
|
||||||
|
|
||||||
const { regions, isLoading } = useRegions(10, 1, debouncedSearch)
|
const { regions, isLoading } = useRegions(10, 1, debouncedSearch)
|
||||||
|
|
||||||
const { serversInfo } = useServersInfo(selectedOption?.id)
|
const { serversInfo } = useServersInfo(selectedOption)
|
||||||
|
|
||||||
const [serverDataOpen, setServerDataOpen] = useState(false)
|
const [serverDataOpen, setServerDataOpen] = useState(false)
|
||||||
const [currentServerData, setCurrentServerData] = useState<any | null>(null)
|
const [currentServerData, setCurrentServerData] = useState<any | null>(null)
|
||||||
|
|
||||||
const { servers, isLoading: serversLoading } = useServers(selectedOption?.id, 0, 10)
|
const { servers, isLoading: serversLoading } = useServers(selectedOption, 0, 10)
|
||||||
|
|
||||||
const serversColumns: GridColDef[] = [
|
const serversColumns: GridColDef[] = [
|
||||||
//{ field: 'id', headerName: 'ID', type: "number" },
|
//{ field: 'id', headerName: 'ID', type: "number" },
|
||||||
@ -37,42 +37,43 @@ export default function ServersView() {
|
|||||||
{
|
{
|
||||||
field: 'region_id',
|
field: 'region_id',
|
||||||
editable: true,
|
editable: true,
|
||||||
|
headerName: 'region_id',
|
||||||
renderCell: (params) => (
|
renderCell: (params) => (
|
||||||
<div>
|
<div>
|
||||||
{params.value}
|
{params.value}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
renderEditCell: (params: GridRenderCellParams) => (
|
// renderEditCell: (params: GridRenderCellParams) => (
|
||||||
<Autocomplete
|
// <Autocomplete
|
||||||
sx={{ display: 'flex', flexGrow: '1' }}
|
// sx={{ display: 'flex', flexGrow: '1' }}
|
||||||
onInputChange={(_, value) => setSearch(value)}
|
// onInputChange={(_, value) => setSearch(value)}
|
||||||
onChange={(_, value) => {
|
// onChange={(_, value) => {
|
||||||
params.value = value
|
// params.value = value
|
||||||
}}
|
// }}
|
||||||
isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name}
|
// isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name}
|
||||||
getOptionLabel={(option: IRegion) => option.name ? option.name : ""}
|
// getOptionLabel={(option: IRegion) => option.name ? option.name : ""}
|
||||||
options={regions || []}
|
// options={regions || []}
|
||||||
loading={isLoading}
|
// loading={isLoading}
|
||||||
value={params.value}
|
// value={params.value}
|
||||||
renderInput={(params) => (
|
// renderInput={(params) => (
|
||||||
<TextField
|
// <TextField
|
||||||
{...params}
|
// {...params}
|
||||||
size='small'
|
// size='small'
|
||||||
variant='standard'
|
// variant='standard'
|
||||||
label="Район"
|
// label="Район"
|
||||||
InputProps={{
|
// InputProps={{
|
||||||
...params.InputProps,
|
// ...params.InputProps,
|
||||||
endAdornment: (
|
// endAdornment: (
|
||||||
<Fragment>
|
// <Fragment>
|
||||||
{isLoading ? <CircularProgress color="inherit" size={20} /> : null}
|
// {isLoading ? <CircularProgress color="inherit" size={20} /> : null}
|
||||||
{params.InputProps.endAdornment}
|
// {params.InputProps.endAdornment}
|
||||||
</Fragment>
|
// </Fragment>
|
||||||
)
|
// )
|
||||||
}}
|
// }}
|
||||||
/>
|
// />
|
||||||
)}
|
// )}
|
||||||
/>
|
// />
|
||||||
),
|
// ),
|
||||||
flex: 1
|
flex: 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -132,7 +133,50 @@ export default function ServersView() {
|
|||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
|
|
||||||
<FullFeaturedCrudGrid
|
<form>
|
||||||
|
<Autocomplete
|
||||||
|
placeholder="Район"
|
||||||
|
flex={'1'}
|
||||||
|
data={regions ? regions.map((item: IRegion) => ({ label: item.name, value: item.id.toString() })) : []}
|
||||||
|
onSelect={(e) => console.log(e.currentTarget.value)}
|
||||||
|
onChange={(value) => setSearch(value)}
|
||||||
|
onOptionSubmit={(value) => setSelectedOption(Number(value))}
|
||||||
|
rightSection={
|
||||||
|
search !== '' && (
|
||||||
|
<CloseButton
|
||||||
|
size="sm"
|
||||||
|
onMouseDown={(event) => event.preventDefault()}
|
||||||
|
onClick={() => {
|
||||||
|
setSearch('')
|
||||||
|
setSelectedOption(null)
|
||||||
|
}}
|
||||||
|
aria-label="Clear value"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value={search}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
{serversColumns.map(column => (
|
||||||
|
<Table.Th key={column.field}>{column.headerName}</Table.Th>
|
||||||
|
))}
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
<Table.Tr>
|
||||||
|
{serversColumns.map(column => (
|
||||||
|
<Table.Td key={column.field}>{servers ? servers[column.field] : ''}</Table.Td>
|
||||||
|
))}
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
{/* <FullFeaturedCrudGrid
|
||||||
loading={serversLoading}
|
loading={serversLoading}
|
||||||
autoComplete={
|
autoComplete={
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
@ -171,7 +215,7 @@ export default function ServersView() {
|
|||||||
setCurrentServerData(params.row)
|
setCurrentServerData(params.row)
|
||||||
setServerDataOpen(true)
|
setServerDataOpen(true)
|
||||||
}}
|
}}
|
||||||
/>
|
/> */}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -6,8 +6,7 @@ import View from 'ol/View'
|
|||||||
import { Draw, Modify, Select, Snap, Translate } from 'ol/interaction'
|
import { Draw, Modify, Select, Snap, Translate } from 'ol/interaction'
|
||||||
import { ImageStatic, OSM, Vector as VectorSource, XYZ } from 'ol/source'
|
import { ImageStatic, OSM, Vector as VectorSource, XYZ } from 'ol/source'
|
||||||
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'
|
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'
|
||||||
import { Divider, IconButton, Slider, Stack, Select as MUISelect, MenuItem, Box, Typography, Accordion, AccordionSummary, AccordionDetails, SxProps, Theme } from '@mui/material'
|
import { Stack, Typography } from '@mui/material'
|
||||||
import { Add, Adjust, Api, CircleOutlined, ExpandMore, OpenWith, RectangleOutlined, Straighten, Timeline, Undo, Upload, Warning } from '@mui/icons-material'
|
|
||||||
import { Type } from 'ol/geom/Geometry'
|
import { Type } from 'ol/geom/Geometry'
|
||||||
import { click, never, noModifierKeys, platformModifierKeyOnly, primaryAction, shiftKeyOnly } from 'ol/events/condition'
|
import { click, never, noModifierKeys, platformModifierKeyOnly, primaryAction, shiftKeyOnly } from 'ol/events/condition'
|
||||||
import Feature from 'ol/Feature'
|
import Feature from 'ol/Feature'
|
||||||
@ -30,6 +29,8 @@ import { useCities } from '../../hooks/swrHooks'
|
|||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { fetcher } from '../../http/axiosInstance'
|
import { fetcher } from '../../http/axiosInstance'
|
||||||
import { BASE_URL } from '../../constants'
|
import { BASE_URL } from '../../constants'
|
||||||
|
import { Accordion, ActionIcon, Box, Flex, Select as MantineSelect, MantineStyleProp, rem, Slider, useMantineColorScheme } from '@mantine/core'
|
||||||
|
import { IconApi, IconArrowBackUp, IconArrowsMove, IconCircle, IconExclamationCircle, IconLine, IconPlus, IconPoint, IconPolygon, IconRuler, IconTable, IconUpload } from '@tabler/icons-react'
|
||||||
|
|
||||||
const MapComponent = () => {
|
const MapComponent = () => {
|
||||||
const { cities } = useCities(100, 1)
|
const { cities } = useCities(100, 1)
|
||||||
@ -775,14 +776,17 @@ const MapComponent = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapControlsStyle: SxProps<Theme> = {
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
|
const mapControlsStyle: MantineStyleProp = {
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
zIndex: '1',
|
zIndex: '1',
|
||||||
backgroundColor: (theme) =>
|
// backgroundColor: (theme) =>
|
||||||
theme.palette.mode === 'light'
|
// theme.palette.mode === 'light'
|
||||||
? '#FFFFFFAA'
|
// ? '#FFFFFFAA'
|
||||||
: '#000000AA',
|
// : '#000000AA',
|
||||||
|
backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA',
|
||||||
backdropFilter: 'blur(8px)'
|
backdropFilter: 'blur(8px)'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -808,134 +812,118 @@ const MapComponent = () => {
|
|||||||
}, [nodes])
|
}, [nodes])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box height={'calc(100% - 64px)'} maxHeight={'100%'} flex={'1'} flexGrow={'1'} position={'relative'}>
|
<Box w={'100%'} h={'calc(100% - 64px)'} mah={'100%'} flex={'1'} pos={'relative'}>
|
||||||
<Stack
|
<ActionIcon.Group orientation='vertical' pos='absolute' top='8px' right='8px' style={{ zIndex: 1, backdropFilter: 'blur(8px)', backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA', borderRadius: '4px' }}>
|
||||||
direction={'column'}
|
<ActionIcon size='lg' variant='transparent' onClick={() => {
|
||||||
sx={{
|
|
||||||
...mapControlsStyle,
|
|
||||||
top: '8px',
|
|
||||||
right: '8px',
|
|
||||||
}}
|
|
||||||
divider={<Divider orientation='horizontal' flexItem />}>
|
|
||||||
<IconButton onClick={() => {
|
|
||||||
fetch(`${import.meta.env.VITE_API_EMS_URL}/hello`, { method: 'GET' }).then(res => console.log(res))
|
fetch(`${import.meta.env.VITE_API_EMS_URL}/hello`, { method: 'GET' }).then(res => console.log(res))
|
||||||
}}>
|
}}>
|
||||||
<Api />
|
<IconApi />
|
||||||
</IconButton>
|
</ActionIcon>
|
||||||
|
|
||||||
<IconButton onClick={() => {
|
<ActionIcon size='lg' variant='transparent' onClick={() => {
|
||||||
saveFeatures()
|
saveFeatures()
|
||||||
}}>
|
}}>
|
||||||
<Warning />
|
<IconExclamationCircle />
|
||||||
</IconButton>
|
</ActionIcon>
|
||||||
|
|
||||||
<IconButton
|
<ActionIcon size='lg' variant='transparent' onClick={() => {
|
||||||
|
draw.current?.removeLastPoint()
|
||||||
|
}}>
|
||||||
|
<IconArrowBackUp />
|
||||||
|
</ActionIcon>
|
||||||
|
|
||||||
|
<ActionIcon
|
||||||
|
size='lg'
|
||||||
|
variant={currentTool === 'Point' ? 'filled' : 'transparent'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
draw.current?.removeLastPoint()
|
handleToolSelect('Point')
|
||||||
}}>
|
}}>
|
||||||
<Undo />
|
<IconPoint />
|
||||||
</IconButton>
|
</ActionIcon>
|
||||||
|
|
||||||
<IconButton
|
<ActionIcon
|
||||||
sx={{ backgroundColor: currentTool === 'Point' ? 'Highlight' : 'transparent' }}
|
size='lg'
|
||||||
onClick={() => handleToolSelect('Point')}>
|
variant={currentTool === 'LineString' ? 'filled' : 'transparent'}
|
||||||
<Adjust />
|
onClick={() => {
|
||||||
</IconButton>
|
handleToolSelect('LineString')
|
||||||
|
}}>
|
||||||
|
<IconLine />
|
||||||
|
</ActionIcon>
|
||||||
|
|
||||||
<IconButton
|
<ActionIcon
|
||||||
sx={{ backgroundColor: currentTool === 'LineString' ? 'Highlight' : 'transparent' }}
|
size='lg'
|
||||||
onClick={() => handleToolSelect('LineString')}>
|
variant={currentTool === 'Polygon' ? 'filled' : 'transparent'}
|
||||||
<Timeline />
|
onClick={() => {
|
||||||
</IconButton>
|
handleToolSelect('Polygon')
|
||||||
|
}}>
|
||||||
|
<IconPolygon />
|
||||||
|
</ActionIcon>
|
||||||
|
|
||||||
<IconButton
|
<ActionIcon
|
||||||
sx={{ backgroundColor: currentTool === 'Polygon' ? 'Highlight' : 'transparent' }}
|
size='lg'
|
||||||
onClick={() => handleToolSelect('Polygon')}>
|
variant={currentTool === 'Circle' ? 'filled' : 'transparent'}
|
||||||
<RectangleOutlined />
|
onClick={() => {
|
||||||
</IconButton>
|
handleToolSelect('Circle')
|
||||||
|
}}>
|
||||||
|
<IconCircle />
|
||||||
|
</ActionIcon>
|
||||||
|
|
||||||
<IconButton
|
<ActionIcon
|
||||||
sx={{ backgroundColor: currentTool === 'Circle' ? 'Highlight' : 'transparent' }}
|
size='lg'
|
||||||
onClick={() => handleToolSelect('Circle')}>
|
variant='transparent'
|
||||||
<CircleOutlined />
|
|
||||||
</IconButton>
|
|
||||||
|
|
||||||
<IconButton
|
|
||||||
onClick={() => map?.current?.addInteraction(new Translate())}
|
onClick={() => map?.current?.addInteraction(new Translate())}
|
||||||
>
|
>
|
||||||
<OpenWith />
|
<IconArrowsMove />
|
||||||
</IconButton>
|
</ActionIcon>
|
||||||
|
|
||||||
<IconButton>
|
<ActionIcon
|
||||||
<Straighten />
|
size='lg'
|
||||||
</IconButton>
|
variant='transparent'
|
||||||
</Stack>
|
>
|
||||||
|
<IconRuler />
|
||||||
|
</ActionIcon>
|
||||||
|
</ActionIcon.Group>
|
||||||
|
|
||||||
<Stack
|
<Flex direction='column' style={{
|
||||||
direction={'column'}
|
...mapControlsStyle,
|
||||||
sx={{
|
maxWidth: '300px',
|
||||||
...mapControlsStyle,
|
width: '100%',
|
||||||
maxWidth: '300px',
|
top: '8px',
|
||||||
width: '100%',
|
left: '8px'
|
||||||
top: '8px',
|
}}>
|
||||||
left: '8px',
|
<Flex direction='row'>
|
||||||
}} divider={<Divider orientation='horizontal' flexItem />}
|
<ActionIcon
|
||||||
>
|
size='lg'
|
||||||
<Stack direction={'row'}>
|
variant='transparent'
|
||||||
<IconButton onClick={() => submitOverlay()}>
|
onClick={() => submitOverlay()}
|
||||||
<Upload />
|
|
||||||
</IconButton>
|
|
||||||
|
|
||||||
<IconButton title='Добавить подложку'>
|
|
||||||
<Add />
|
|
||||||
</IconButton>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
|
|
||||||
<Stack direction={'row'} padding={'8px'} spacing={4}>
|
|
||||||
<Slider size='small' aria-label="Opacity" min={0} max={1} step={0.001} defaultValue={satelliteOpacity} value={satelliteOpacity} onChange={(_, value) => setSatelliteOpacity(Array.isArray(value) ? value[0] : value)} />
|
|
||||||
|
|
||||||
<MUISelect
|
|
||||||
variant='standard'
|
|
||||||
labelId="demo-simple-select-label"
|
|
||||||
id="demo-simple-select"
|
|
||||||
value={satMapsProvider}
|
|
||||||
label="Satellite Provider"
|
|
||||||
onChange={(e) => setSatMapsProvider(e.target.value as SatelliteMapsProvider)}
|
|
||||||
>
|
>
|
||||||
<MenuItem value={'google'}>Google</MenuItem>
|
<IconUpload style={{ width: rem(20), height: rem(20) }} />
|
||||||
<MenuItem value={'yandex'}>Яндекс</MenuItem>
|
</ActionIcon>
|
||||||
<MenuItem value={'custom'}>Custom</MenuItem>
|
|
||||||
</MUISelect>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<Accordion disableGutters sx={{ backgroundColor: 'transparent' }} defaultExpanded>
|
<ActionIcon
|
||||||
<AccordionSummary
|
size='lg'
|
||||||
expandIcon={<ExpandMore />}
|
variant='transparent'
|
||||||
aria-controls="panel1-content"
|
title='Добавить подложку'
|
||||||
id="panel1-header"
|
|
||||||
>
|
>
|
||||||
<Typography>Объекты</Typography>
|
<IconPlus style={{ width: rem(20), height: rem(20) }} />
|
||||||
</AccordionSummary>
|
</ActionIcon>
|
||||||
<AccordionDetails>
|
</Flex>
|
||||||
<Typography>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
|
<Flex align='center' direction='row' p='sm' gap='sm'>
|
||||||
malesuada lacus ex, sit amet blandit leo lobortis eget.
|
<Slider w='100%' min={0} max={1} step={0.001} value={satelliteOpacity} defaultValue={satelliteOpacity} onChange={(value) => setSatelliteOpacity(Array.isArray(value) ? value[0] : value)} />
|
||||||
</Typography>
|
|
||||||
</AccordionDetails>
|
<MantineSelect variant='filled' value={satMapsProvider} data={[{ label: 'Google', value: 'google' }, { label: 'Yandex', value: 'yandex' }, { label: 'Custom', value: 'custom' }]} onChange={(value) => setSatMapsProvider(value as SatelliteMapsProvider)} />
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Accordion variant='filled' style={{ backgroundColor: 'transparent' }} defaultValue='Объекты'>
|
||||||
|
<Accordion.Item key={'s'} value={'Объекты'}>
|
||||||
|
<Accordion.Control icon={<IconTable />}>{'Объекты'}</Accordion.Control>
|
||||||
|
<Accordion.Panel>{'ASd'}</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
</Stack>
|
<Flex direction='row' pos='absolute' bottom='8px' left='8px' style={{ ...mapControlsStyle }}>
|
||||||
|
|
||||||
<Stack direction={'row'}
|
|
||||||
sx={{
|
|
||||||
...mapControlsStyle,
|
|
||||||
bottom: '8px',
|
|
||||||
left: '8px',
|
|
||||||
}}
|
|
||||||
|
|
||||||
divider={<Divider orientation='vertical' flexItem />}
|
|
||||||
>
|
|
||||||
<Stack>
|
<Stack>
|
||||||
<Typography>
|
<Typography>
|
||||||
x: {currentCoordinate?.[0]}
|
x: {currentCoordinate?.[0]}
|
||||||
@ -950,20 +938,11 @@ const MapComponent = () => {
|
|||||||
X={currentX}
|
X={currentX}
|
||||||
Y={currentY}
|
Y={currentY}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Flex>
|
||||||
|
|
||||||
<Stack direction={'row'}
|
<Flex direction='row' style={{ ...mapControlsStyle, bottom: '8px', right: '8px' }}>
|
||||||
sx={{
|
{statusText}
|
||||||
...mapControlsStyle,
|
</Flex>
|
||||||
bottom: '8px',
|
|
||||||
right: '8px',
|
|
||||||
}}
|
|
||||||
|
|
||||||
divider={<Divider orientation='vertical' flexItem />}>
|
|
||||||
<Stack>
|
|
||||||
{statusText}
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id="map-container"
|
id="map-container"
|
||||||
|
@ -1,171 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import AppBar from '@mui/material/AppBar';
|
|
||||||
import Box from '@mui/material/Box';
|
|
||||||
import CssBaseline from '@mui/material/CssBaseline';
|
|
||||||
import Divider from '@mui/material/Divider';
|
|
||||||
import Drawer from '@mui/material/Drawer';
|
|
||||||
import IconButton from '@mui/material/IconButton';
|
|
||||||
import InboxIcon from '@mui/icons-material/MoveToInbox';
|
|
||||||
import List from '@mui/material/List';
|
|
||||||
import ListItem from '@mui/material/ListItem';
|
|
||||||
import ListItemButton from '@mui/material/ListItemButton';
|
|
||||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
|
||||||
import ListItemText from '@mui/material/ListItemText';
|
|
||||||
import MailIcon from '@mui/icons-material/Mail';
|
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
|
||||||
import Toolbar from '@mui/material/Toolbar';
|
|
||||||
import Typography from '@mui/material/Typography';
|
|
||||||
|
|
||||||
const drawerWidth = 240;
|
|
||||||
|
|
||||||
export default function ResponsiveDrawer() {
|
|
||||||
//const { window } = props;
|
|
||||||
const [mobileOpen, setMobileOpen] = React.useState(false);
|
|
||||||
const [isClosing, setIsClosing] = React.useState(false);
|
|
||||||
|
|
||||||
const handleDrawerClose = () => {
|
|
||||||
setIsClosing(true);
|
|
||||||
setMobileOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDrawerTransitionEnd = () => {
|
|
||||||
setIsClosing(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDrawerToggle = () => {
|
|
||||||
if (!isClosing) {
|
|
||||||
setMobileOpen(!mobileOpen);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const drawer = (
|
|
||||||
<div>
|
|
||||||
<Toolbar />
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<List>
|
|
||||||
{['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => (
|
|
||||||
<ListItem key={text} disablePadding>
|
|
||||||
<ListItemButton>
|
|
||||||
<ListItemIcon>
|
|
||||||
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary={text} />
|
|
||||||
</ListItemButton>
|
|
||||||
</ListItem>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<List>
|
|
||||||
{['All mail', 'Trash', 'Spam'].map((text, index) => (
|
|
||||||
<ListItem key={text} disablePadding>
|
|
||||||
<ListItemButton>
|
|
||||||
<ListItemIcon>
|
|
||||||
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary={text} />
|
|
||||||
</ListItemButton>
|
|
||||||
</ListItem>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ display: 'flex' }}>
|
|
||||||
<CssBaseline />
|
|
||||||
<AppBar
|
|
||||||
position="fixed"
|
|
||||||
sx={{
|
|
||||||
width: { sm: `calc(100% - ${drawerWidth}px)` },
|
|
||||||
ml: { sm: `${drawerWidth}px` },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Toolbar>
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
aria-label="open drawer"
|
|
||||||
edge="start"
|
|
||||||
onClick={handleDrawerToggle}
|
|
||||||
sx={{ mr: 2, display: { sm: 'none' } }}
|
|
||||||
>
|
|
||||||
<MenuIcon />
|
|
||||||
</IconButton>
|
|
||||||
<Typography variant="h6" noWrap component="div">
|
|
||||||
Dashboard
|
|
||||||
</Typography>
|
|
||||||
</Toolbar>
|
|
||||||
</AppBar>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
component="nav"
|
|
||||||
sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}
|
|
||||||
aria-label="mailbox folders"
|
|
||||||
>
|
|
||||||
{/* The implementation can be swapped with js to avoid SEO duplication of links. */}
|
|
||||||
<Drawer
|
|
||||||
variant="temporary"
|
|
||||||
open={mobileOpen}
|
|
||||||
onTransitionEnd={handleDrawerTransitionEnd}
|
|
||||||
onClose={handleDrawerClose}
|
|
||||||
ModalProps={{
|
|
||||||
keepMounted: true, // Better open performance on mobile.
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
display: { xs: 'block', sm: 'none' },
|
|
||||||
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{drawer}
|
|
||||||
</Drawer>
|
|
||||||
<Drawer
|
|
||||||
variant="permanent"
|
|
||||||
sx={{
|
|
||||||
display: { xs: 'none', sm: 'block' },
|
|
||||||
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
|
|
||||||
}}
|
|
||||||
open
|
|
||||||
>
|
|
||||||
{drawer}
|
|
||||||
</Drawer>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
component="main"
|
|
||||||
sx={{ flexGrow: 1, p: 3, width: { sm: `calc(100% - ${drawerWidth}px)` } }}
|
|
||||||
>
|
|
||||||
<Toolbar />
|
|
||||||
<Typography paragraph>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
|
||||||
tempor incididunt ut labore et dolore magna aliqua. Rhoncus dolor purus non
|
|
||||||
enim praesent elementum facilisis leo vel. Risus at ultrices mi tempus
|
|
||||||
imperdiet. Semper risus in hendrerit gravida rutrum quisque non tellus.
|
|
||||||
Convallis convallis tellus id interdum velit laoreet id donec ultrices.
|
|
||||||
Odio morbi quis commodo odio aenean sed adipiscing. Amet nisl suscipit
|
|
||||||
adipiscing bibendum est ultricies integer quis. Cursus euismod quis viverra
|
|
||||||
nibh cras. Metus vulputate eu scelerisque felis imperdiet proin fermentum
|
|
||||||
leo. Mauris commodo quis imperdiet massa tincidunt. Cras tincidunt lobortis
|
|
||||||
feugiat vivamus at augue. At augue eget arcu dictum varius duis at
|
|
||||||
consectetur lorem. Velit sed ullamcorper morbi tincidunt. Lorem donec massa
|
|
||||||
sapien faucibus et molestie ac.
|
|
||||||
</Typography>
|
|
||||||
<Typography paragraph>
|
|
||||||
Consequat mauris nunc congue nisi vitae suscipit. Fringilla est ullamcorper
|
|
||||||
eget nulla facilisi etiam dignissim diam. Pulvinar elementum integer enim
|
|
||||||
neque volutpat ac tincidunt. Ornare suspendisse sed nisi lacus sed viverra
|
|
||||||
tellus. Purus sit amet volutpat consequat mauris. Elementum eu facilisis
|
|
||||||
sed odio morbi. Euismod lacinia at quis risus sed vulputate odio. Morbi
|
|
||||||
tincidunt ornare massa eget egestas purus viverra accumsan in. In hendrerit
|
|
||||||
gravida rutrum quisque non tellus orci ac. Pellentesque nec nam aliquam sem
|
|
||||||
et tortor. Habitant morbi tristique senectus et. Adipiscing elit duis
|
|
||||||
tristique sollicitudin nibh sit. Ornare aenean euismod elementum nisi quis
|
|
||||||
eleifend. Commodo viverra maecenas accumsan lacus vel facilisis. Nulla
|
|
||||||
posuere sollicitudin aliquam ultrices sagittis orci a.
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
import { Tab, Tabs } from "@mui/material"
|
|
||||||
import { Link, matchPath, useLocation } from "react-router-dom"
|
|
||||||
|
|
||||||
function useRouteMatch(patterns: readonly string[]) {
|
|
||||||
const { pathname } = useLocation()
|
|
||||||
|
|
||||||
for (let i = 0; i < patterns.length; i += 1) {
|
|
||||||
const pattern = patterns[i]
|
|
||||||
const possibleMatch = matchPath(pattern, pathname)
|
|
||||||
if (possibleMatch !== null) {
|
|
||||||
return possibleMatch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function NavTabs() {
|
|
||||||
const routeMatch = useRouteMatch(['/', '/user', '/role']);
|
|
||||||
const currentTab = routeMatch?.pattern?.path;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tabs value={currentTab}>
|
|
||||||
<Tab label="Главная" value="/" to="/" component={Link} />
|
|
||||||
<Tab label="Пользователи" value="/user" to="/user" component={Link} />
|
|
||||||
<Tab label="Роли" value="/role" to="/role" component={Link} />
|
|
||||||
</Tabs>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
@ -198,7 +198,7 @@ export function useServers(region_id?: number | null, offset?: number, limit?: n
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useServersInfo(region_id?: number, offset?: number, limit?: number) {
|
export function useServersInfo(region_id?: number | null, offset?: number, limit?: number) {
|
||||||
const { data, error, isLoading } = useSWR(
|
const { data, error, isLoading } = useSWR(
|
||||||
region_id ? `/api/servers_info?region_id=${region_id}&offset=${offset || 0}&limit=${limit || 10}` : `/api/servers_info?offset=${offset || 0}&limit=${limit || 10}`,
|
region_id ? `/api/servers_info?region_id=${region_id}&offset=${offset || 0}&limit=${limit || 10}` : `/api/servers_info?offset=${offset || 0}&limit=${limit || 10}`,
|
||||||
(url: string) => fetcher(url, BASE_URL.servers),
|
(url: string) => fetcher(url, BASE_URL.servers),
|
||||||
|
@ -1,96 +1,28 @@
|
|||||||
import * as React from 'react';
|
import { AppShell, Avatar, Burger, Button, Flex, Group, Menu, NavLink, rem, Text, useMantineColorScheme } from '@mantine/core';
|
||||||
import { styled, createTheme, ThemeProvider } from '@mui/material/styles';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import CssBaseline from '@mui/material/CssBaseline';
|
|
||||||
import MuiDrawer from '@mui/material/Drawer';
|
|
||||||
import Box from '@mui/material/Box';
|
|
||||||
import MuiAppBar, { AppBarProps as MuiAppBarProps } from '@mui/material/AppBar';
|
|
||||||
import Toolbar from '@mui/material/Toolbar';
|
|
||||||
import List from '@mui/material/List';
|
|
||||||
import Typography from '@mui/material/Typography';
|
|
||||||
import Divider from '@mui/material/Divider';
|
|
||||||
import IconButton from '@mui/material/IconButton';
|
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
|
||||||
import { colors, ListItem, ListItemButton, ListItemIcon, ListItemText, } from '@mui/material';
|
|
||||||
import { Outlet, useNavigate } from 'react-router-dom';
|
import { Outlet, useNavigate } from 'react-router-dom';
|
||||||
import { UserData } from '../interfaces/auth';
|
|
||||||
import { getUserData, useAuthStore } from '../store/auth';
|
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import AccountMenu from '../components/AccountMenu';
|
|
||||||
import { pages } from '../App';
|
import { pages } from '../App';
|
||||||
|
import { IconChevronDown, IconLogout, IconSettings, IconMoon, IconSun } from '@tabler/icons-react';
|
||||||
|
import { getUserData, logout, useAuthStore } from '../store/auth';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { UserData } from '../interfaces/auth';
|
||||||
|
|
||||||
const drawerWidth: number = 240;
|
function DashboardLayout() {
|
||||||
|
const [mobileOpened, { toggle: toggleMobile }] = useDisclosure()
|
||||||
interface AppBarProps extends MuiAppBarProps {
|
const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true)
|
||||||
open?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AppBar = styled(MuiAppBar, {
|
|
||||||
shouldForwardProp: (prop) => prop !== 'open',
|
|
||||||
})<AppBarProps>(({ theme, open }) => ({
|
|
||||||
zIndex: theme.zIndex.drawer + 1,
|
|
||||||
transition: theme.transitions.create(['width', 'margin'], {
|
|
||||||
easing: theme.transitions.easing.sharp,
|
|
||||||
duration: theme.transitions.duration.leavingScreen,
|
|
||||||
}),
|
|
||||||
...(open && {
|
|
||||||
marginLeft: drawerWidth,
|
|
||||||
width: `calc(100% - ${drawerWidth}px)`,
|
|
||||||
transition: theme.transitions.create(['width', 'margin'], {
|
|
||||||
easing: theme.transitions.easing.sharp,
|
|
||||||
duration: theme.transitions.duration.enteringScreen,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })(
|
|
||||||
({ theme, open }) => ({
|
|
||||||
'& .MuiDrawer-paper': {
|
|
||||||
position: 'relative',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
width: drawerWidth,
|
|
||||||
transition: theme.transitions.create('width', {
|
|
||||||
easing: theme.transitions.easing.sharp,
|
|
||||||
duration: theme.transitions.duration.enteringScreen,
|
|
||||||
}),
|
|
||||||
boxSizing: 'border-box',
|
|
||||||
...(!open && {
|
|
||||||
overflowX: 'hidden',
|
|
||||||
transition: theme.transitions.create('width', {
|
|
||||||
easing: theme.transitions.easing.sharp,
|
|
||||||
duration: theme.transitions.duration.leavingScreen,
|
|
||||||
}),
|
|
||||||
width: theme.spacing(7),
|
|
||||||
[theme.breakpoints.up('sm')]: {
|
|
||||||
//width: theme.spacing(9),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
export default function DashboardLayout() {
|
|
||||||
const theme = useTheme()
|
|
||||||
const innerTheme = createTheme(theme)
|
|
||||||
|
|
||||||
const [open, setOpen] = React.useState(true);
|
|
||||||
const toggleDrawer = () => {
|
|
||||||
setOpen(!open);
|
|
||||||
};
|
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const getPageTitle = () => {
|
const getPageTitle = () => {
|
||||||
const currentPath = location.pathname;
|
const currentPath = location.pathname
|
||||||
const allPages = [...pages];
|
const allPages = [...pages]
|
||||||
const currentPage = allPages.find(page => page.path === currentPath);
|
const currentPage = allPages.find(page => page.path === currentPath)
|
||||||
return currentPage ? currentPage.label : "Dashboard";
|
return currentPage ? currentPage.label : "Панель управления"
|
||||||
};
|
}
|
||||||
|
|
||||||
const [userData, setUserData] = React.useState<UserData>();
|
const authStore = useAuthStore()
|
||||||
|
const [userData, setUserData] = useState<UserData>()
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (authStore) {
|
if (authStore) {
|
||||||
const stored = getUserData()
|
const stored = getUserData()
|
||||||
if (stored) {
|
if (stored) {
|
||||||
@ -99,113 +31,95 @@ export default function DashboardLayout() {
|
|||||||
}
|
}
|
||||||
}, [authStore])
|
}, [authStore])
|
||||||
|
|
||||||
|
const { colorScheme, setColorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={innerTheme}>
|
<AppShell
|
||||||
<Box sx={{
|
header={{ height: 60 }}
|
||||||
display: 'flex',
|
navbar={{
|
||||||
height: "100%"
|
width: desktopOpened ? 200 : 50,
|
||||||
}}>
|
breakpoint: 'sm',
|
||||||
<CssBaseline />
|
collapsed: { mobile: !mobileOpened },
|
||||||
<AppBar position="absolute" open={open}>
|
}}
|
||||||
<Toolbar
|
>
|
||||||
sx={{
|
<AppShell.Header>
|
||||||
pr: '24px', // keep right padding when drawer closed
|
<Flex h="100%" px="md" w='100%' align='center' gap='sm'>
|
||||||
}}
|
<Group>
|
||||||
>
|
<Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" />
|
||||||
<IconButton
|
<Burger opened={desktopOpened} onClick={toggleDesktop} visibleFrom="sm" size="sm" />
|
||||||
edge="start"
|
</Group>
|
||||||
color="inherit"
|
|
||||||
aria-label="open drawer"
|
<Group w='100%'>
|
||||||
onClick={toggleDrawer}
|
{getPageTitle()}
|
||||||
sx={{
|
</Group>
|
||||||
marginRight: '36px',
|
|
||||||
//...(open && { display: 'none' }),
|
<Group style={{ flexShrink: 0 }}>
|
||||||
}}
|
<Menu
|
||||||
|
width={260}
|
||||||
|
position="bottom-end"
|
||||||
|
transitionProps={{ transition: 'pop-top-right' }}
|
||||||
|
withinPortal
|
||||||
>
|
>
|
||||||
<MenuIcon />
|
<Menu.Target>
|
||||||
</IconButton>
|
<Button variant='transparent'>
|
||||||
|
<Group gap={7}>
|
||||||
<Typography
|
<Avatar name={`${userData?.name} ${userData?.surname}`} radius="xl" size={30} />
|
||||||
component="h1"
|
<Text fw={500} size="sm" lh={1} mr={3}>
|
||||||
variant="h6"
|
{`${userData?.name} ${userData?.surname}`}
|
||||||
color="inherit"
|
</Text>
|
||||||
noWrap
|
<IconChevronDown style={{ width: rem(12), height: rem(12) }} stroke={1.5} />
|
||||||
sx={{ flexGrow: 1 }}
|
</Group>
|
||||||
>
|
</Button>
|
||||||
{getPageTitle()}
|
</Menu.Target>
|
||||||
</Typography>
|
<Menu.Dropdown>
|
||||||
|
<Menu.Label>{userData?.login}</Menu.Label>
|
||||||
<Box sx={{ display: "flex", gap: "8px" }}>
|
<Menu.Item
|
||||||
<Box>
|
leftSection={
|
||||||
<Typography>{userData?.name} {userData?.surname}</Typography>
|
colorScheme === 'dark' ? <IconMoon style={{ width: rem(16), height: rem(16) }} stroke={1.5} /> : <IconSun style={{ width: rem(16), height: rem(16) }} stroke={1.5} />
|
||||||
<Divider />
|
}
|
||||||
<Typography variant="caption">{userData?.login}</Typography>
|
onClick={() => colorScheme === 'dark' ? setColorScheme('light') : setColorScheme('dark')}
|
||||||
</Box>
|
|
||||||
|
|
||||||
<AccountMenu />
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</Toolbar>
|
|
||||||
</AppBar>
|
|
||||||
|
|
||||||
<Drawer variant="permanent" open={open}>
|
|
||||||
<Toolbar
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
px: [1],
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box display="flex" justifyContent={'space-between'} width={"100%"}>
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Toolbar>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<List component="nav">
|
|
||||||
{pages.filter((page) => page.drawer).filter((page) => page.enabled).map((item, index) => (
|
|
||||||
<ListItem
|
|
||||||
key={index}
|
|
||||||
disablePadding
|
|
||||||
>
|
|
||||||
<ListItemButton
|
|
||||||
onClick={() => {
|
|
||||||
navigate(item.path)
|
|
||||||
}}
|
|
||||||
style={{ background: location.pathname === item.path ? innerTheme.palette.action.selected : "transparent" }}
|
|
||||||
selected={location.pathname === item.path}
|
|
||||||
>
|
>
|
||||||
<ListItemIcon>
|
Тема: {colorScheme === 'dark' ? 'тёмная' : 'светлая'}
|
||||||
{item.icon}
|
</Menu.Item>
|
||||||
</ListItemIcon>
|
<Menu.Item
|
||||||
<ListItemText
|
leftSection={
|
||||||
primary={item.label}
|
<IconSettings style={{ width: rem(16), height: rem(16) }} stroke={1.5} />
|
||||||
sx={{ color: location.pathname === item.path ? colors.blue[700] : innerTheme.palette.text.primary }}
|
}
|
||||||
/>
|
onClick={() => navigate('/settings')}
|
||||||
</ListItemButton>
|
>
|
||||||
</ListItem>
|
Настройки профиля
|
||||||
))}
|
</Menu.Item>
|
||||||
</List>
|
<Menu.Item
|
||||||
</Drawer>
|
onClick={() => {
|
||||||
|
logout()
|
||||||
|
navigate("/auth/signin")
|
||||||
|
}}
|
||||||
|
leftSection={<IconLogout style={{ width: rem(16), height: rem(16) }} stroke={1.5} />}
|
||||||
|
>
|
||||||
|
Выход
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
|
</Group>
|
||||||
|
</Flex>
|
||||||
|
</AppShell.Header>
|
||||||
|
<AppShell.Navbar style={{ transition: "width 0.2s ease" }}>
|
||||||
|
{pages.filter((page) => page.drawer).filter((page) => page.enabled).map((item) => (
|
||||||
|
<NavLink
|
||||||
|
key={item.path}
|
||||||
|
onClick={() => navigate(item.path)}
|
||||||
|
label={item.label}
|
||||||
|
leftSection={item.icon}
|
||||||
|
active={location.pathname === item.path}
|
||||||
|
style={{textWrap: 'nowrap'}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</AppShell.Navbar>
|
||||||
|
<AppShell.Main>
|
||||||
|
<Outlet />
|
||||||
|
</AppShell.Main>
|
||||||
|
</AppShell>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
<Box
|
export default DashboardLayout
|
||||||
component="main"
|
|
||||||
sx={{
|
|
||||||
backgroundColor: (theme) =>
|
|
||||||
theme.palette.mode === 'light'
|
|
||||||
? theme.palette.grey[100]
|
|
||||||
: theme.palette.grey[900],
|
|
||||||
flexGrow: 1,
|
|
||||||
maxHeight: "100vh",
|
|
||||||
overflow: 'auto',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Toolbar />
|
|
||||||
<Outlet />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,252 +0,0 @@
|
|||||||
// Layout for dashboard with responsive drawer
|
|
||||||
|
|
||||||
import { Outlet, useLocation, useNavigate } from "react-router-dom"
|
|
||||||
import * as React from 'react';
|
|
||||||
import AppBar from '@mui/material/AppBar';
|
|
||||||
import Box from '@mui/material/Box';
|
|
||||||
import CssBaseline from '@mui/material/CssBaseline';
|
|
||||||
import Divider from '@mui/material/Divider';
|
|
||||||
import Drawer from '@mui/material/Drawer';
|
|
||||||
import IconButton from '@mui/material/IconButton';
|
|
||||||
import List from '@mui/material/List';
|
|
||||||
import ListItem from '@mui/material/ListItem';
|
|
||||||
import ListItemButton from '@mui/material/ListItemButton';
|
|
||||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
|
||||||
import ListItemText from '@mui/material/ListItemText';
|
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
|
||||||
import Toolbar from '@mui/material/Toolbar';
|
|
||||||
import Typography from '@mui/material/Typography';
|
|
||||||
import { Api, ExitToApp, Home, People, Settings, Shield } from "@mui/icons-material";
|
|
||||||
import { getUserData, useAuthStore } from "../store/auth";
|
|
||||||
import { UserData } from "../interfaces/auth";
|
|
||||||
|
|
||||||
const drawerWidth = 240;
|
|
||||||
|
|
||||||
export default function DashboardLayoutResponsive() {
|
|
||||||
const [open, setOpen] = React.useState(true);
|
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
const [userData, setUserData] = React.useState<UserData>();
|
|
||||||
|
|
||||||
const location = useLocation()
|
|
||||||
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
//const { window } = props;
|
|
||||||
const [mobileOpen, setMobileOpen] = React.useState(false);
|
|
||||||
const [isClosing, setIsClosing] = React.useState(false);
|
|
||||||
|
|
||||||
const handleDrawerClose = () => {
|
|
||||||
setIsClosing(true);
|
|
||||||
setMobileOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDrawerTransitionEnd = () => {
|
|
||||||
setIsClosing(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDrawerToggle = () => {
|
|
||||||
if (!isClosing) {
|
|
||||||
setMobileOpen(!mobileOpen);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const pages = [
|
|
||||||
{
|
|
||||||
label: "Главная",
|
|
||||||
path: "/",
|
|
||||||
icon: <Home />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Пользователи",
|
|
||||||
path: "/user",
|
|
||||||
icon: <People />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Роли",
|
|
||||||
path: "/role",
|
|
||||||
icon: <Shield />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "API Test",
|
|
||||||
path: "/api-test",
|
|
||||||
icon: <Api />
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const misc = [
|
|
||||||
{
|
|
||||||
label: "Настройки",
|
|
||||||
path: "/settings",
|
|
||||||
icon: <Settings />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Выход",
|
|
||||||
path: "/signOut",
|
|
||||||
icon: <ExitToApp />
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const getPageTitle = () => {
|
|
||||||
const currentPath = location.pathname;
|
|
||||||
const allPages = [...pages, ...misc];
|
|
||||||
const currentPage = allPages.find(page => page.path === currentPath);
|
|
||||||
return currentPage ? currentPage.label : "Dashboard";
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleDrawer = () => {
|
|
||||||
setOpen(!open);
|
|
||||||
};
|
|
||||||
|
|
||||||
const drawer = (
|
|
||||||
<div>
|
|
||||||
<Toolbar>
|
|
||||||
<Box display="flex" justifyContent={'space-between'} width={"100%"}>
|
|
||||||
<Box>
|
|
||||||
<Typography>{userData?.name} {userData?.surname}</Typography>
|
|
||||||
<Divider />
|
|
||||||
<Typography variant="caption">{userData?.login}</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<IconButton
|
|
||||||
edge="start"
|
|
||||||
color="inherit"
|
|
||||||
aria-label="open drawer"
|
|
||||||
onClick={toggleDrawer}
|
|
||||||
sx={{
|
|
||||||
...(open && { display: 'none' }),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MenuIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
</Toolbar>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<List>
|
|
||||||
{pages.map((item, index) => (
|
|
||||||
<ListItem key={index} disablePadding>
|
|
||||||
<ListItemButton
|
|
||||||
onClick={() => {
|
|
||||||
navigate(item.path)
|
|
||||||
}}
|
|
||||||
selected={location.pathname === item.path}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
|
||||||
{item.icon}
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary={item.label} />
|
|
||||||
</ListItemButton>
|
|
||||||
</ListItem>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<List>
|
|
||||||
{misc.map((item, index) => (
|
|
||||||
<ListItem key={index} disablePadding>
|
|
||||||
<ListItemButton
|
|
||||||
onClick={() => {
|
|
||||||
navigate(item.path)
|
|
||||||
}}
|
|
||||||
selected={location.pathname === item.path}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
|
||||||
{item.icon}
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary={item.label} />
|
|
||||||
</ListItemButton>
|
|
||||||
</ListItem>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
</div >
|
|
||||||
);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (authStore) {
|
|
||||||
const stored = getUserData()
|
|
||||||
if (stored) {
|
|
||||||
setUserData(stored)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [authStore])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{
|
|
||||||
display: 'flex',
|
|
||||||
flexGrow: 1,
|
|
||||||
height: "100vh"
|
|
||||||
}}>
|
|
||||||
<CssBaseline />
|
|
||||||
<AppBar
|
|
||||||
position="fixed"
|
|
||||||
sx={{
|
|
||||||
width: { sm: `calc(100% - ${drawerWidth}px)` },
|
|
||||||
ml: { sm: `${drawerWidth}px` },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Toolbar>
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
aria-label="open drawer"
|
|
||||||
edge="start"
|
|
||||||
onClick={handleDrawerToggle}
|
|
||||||
sx={{ mr: 2, display: { sm: 'none' } }}
|
|
||||||
>
|
|
||||||
<MenuIcon />
|
|
||||||
</IconButton>
|
|
||||||
<Typography variant="h6" noWrap component="div">
|
|
||||||
{getPageTitle()}
|
|
||||||
</Typography>
|
|
||||||
</Toolbar>
|
|
||||||
</AppBar>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
component="nav"
|
|
||||||
sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}
|
|
||||||
aria-label="mailbox folders"
|
|
||||||
>
|
|
||||||
{/* The implementation can be swapped with js to avoid SEO duplication of links. */}
|
|
||||||
<Drawer
|
|
||||||
variant="temporary"
|
|
||||||
open={mobileOpen}
|
|
||||||
onTransitionEnd={handleDrawerTransitionEnd}
|
|
||||||
onClose={handleDrawerClose}
|
|
||||||
ModalProps={{
|
|
||||||
keepMounted: true, // Better open performance on mobile.
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
display: { xs: 'block', sm: 'none' },
|
|
||||||
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{drawer}
|
|
||||||
</Drawer>
|
|
||||||
<Drawer
|
|
||||||
variant="permanent"
|
|
||||||
sx={{
|
|
||||||
display: { xs: 'none', sm: 'block' },
|
|
||||||
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
|
|
||||||
}}
|
|
||||||
open
|
|
||||||
>
|
|
||||||
{drawer}
|
|
||||||
</Drawer>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
component="main"
|
|
||||||
sx={{
|
|
||||||
flexGrow: 1,
|
|
||||||
p: 3,
|
|
||||||
width: { sm: `calc(100% - ${drawerWidth}px)` }
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Toolbar />
|
|
||||||
<Outlet />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,31 +1,12 @@
|
|||||||
// Layout for fullscreen pages
|
// Layout for fullscreen pages
|
||||||
|
|
||||||
import { Box, createTheme, ThemeProvider, useTheme } from "@mui/material";
|
import { Flex } from "@mantine/core";
|
||||||
import { Outlet } from "react-router-dom";
|
import { Outlet } from "react-router-dom";
|
||||||
|
|
||||||
export default function MainLayout() {
|
export default function MainLayout() {
|
||||||
const theme = useTheme()
|
|
||||||
const innerTheme = createTheme(theme)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={innerTheme}>
|
<Flex align='center' justify='center' h='100%' w='100%'>
|
||||||
<Box
|
<Outlet />
|
||||||
sx={{
|
</Flex>
|
||||||
color: (theme) => theme.palette.mode === 'light'
|
|
||||||
? theme.palette.grey[900]
|
|
||||||
: theme.palette.grey[100],
|
|
||||||
backgroundColor: (theme) =>
|
|
||||||
theme.palette.mode === 'light'
|
|
||||||
? theme.palette.grey[100]
|
|
||||||
: theme.palette.grey[900],
|
|
||||||
flexGrow: 1,
|
|
||||||
maxHeight: "100vh",
|
|
||||||
height: '100%',
|
|
||||||
overflow: 'auto',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Outlet />
|
|
||||||
</Box>
|
|
||||||
</ThemeProvider>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,98 +1,15 @@
|
|||||||
import "@fontsource/inter";
|
import "@fontsource/inter";
|
||||||
import React, { useEffect } from 'react'
|
import '@mantine/core/styles.css';
|
||||||
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import { ThemeProvider } from '@emotion/react'
|
import { MantineProvider } from '@mantine/core';
|
||||||
import { createTheme } from '@mui/material'
|
|
||||||
import { ruRU } from '@mui/material/locale'
|
|
||||||
import { getDarkMode, usePrefStore } from "./store/preferences.ts";
|
|
||||||
|
|
||||||
const mainTheme = createTheme(
|
|
||||||
{
|
|
||||||
typography: {
|
|
||||||
fontFamily: [
|
|
||||||
'Inter'
|
|
||||||
].join(',')
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
MuiAppBar: {
|
|
||||||
// styleOverrides: {
|
|
||||||
// colorPrimary: {
|
|
||||||
// backgroundColor: 'gray'
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
MuiListItemButton: {
|
|
||||||
defaultProps: {
|
|
||||||
//disableRipple: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MuiButton: {
|
|
||||||
defaultProps: {
|
|
||||||
//disableRipple: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MuiButtonBase: {
|
|
||||||
defaultProps: {
|
|
||||||
//disableRipple: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MuiButtonGroup: {
|
|
||||||
defaultProps: {
|
|
||||||
//disableRipple: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MuiIconButton: {
|
|
||||||
defaultProps: {
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MuiIcon: {
|
|
||||||
defaultProps: {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ruRU
|
|
||||||
)
|
|
||||||
|
|
||||||
const darkTheme = createTheme(
|
|
||||||
{
|
|
||||||
...mainTheme,
|
|
||||||
palette: {
|
|
||||||
mode: "dark",
|
|
||||||
primary: { main: '#1976d2' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const lightTheme = createTheme(
|
|
||||||
{
|
|
||||||
...mainTheme,
|
|
||||||
palette: {
|
|
||||||
mode: "light",
|
|
||||||
primary: { main: '#1976d2' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
function ThemedApp() {
|
|
||||||
const prefStore = usePrefStore()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getDarkMode()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThemeProvider theme={prefStore.darkMode ? darkTheme : lightTheme}>
|
|
||||||
<App />
|
|
||||||
</ThemeProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ThemedApp />
|
<MantineProvider defaultColorScheme="light">
|
||||||
|
<App />
|
||||||
|
</MantineProvider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
)
|
)
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { Box, Typography } from '@mui/material'
|
import { GridColDef } from '@mui/x-data-grid'
|
||||||
import { DataGrid, GridColDef } from '@mui/x-data-grid'
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { IBoiler } from '../interfaces/fuel'
|
|
||||||
import { useBoilers } from '../hooks/swrHooks'
|
import { useBoilers } from '../hooks/swrHooks'
|
||||||
|
import { Badge, Flex, Table, Text } from '@mantine/core'
|
||||||
|
|
||||||
function Boilers() {
|
function Boilers() {
|
||||||
const [boilersPage, setBoilersPage] = useState(1)
|
const [boilersPage, setBoilersPage] = useState(1)
|
||||||
@ -26,7 +25,7 @@ function Boilers() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const boilersColumns: GridColDef[] = [
|
const boilersColumns: GridColDef[] = [
|
||||||
{ field: 'id', headerName: 'ID', type: "number" },
|
{ field: 'id_object', headerName: 'ID', type: "number" },
|
||||||
{ field: 'boiler_name', headerName: 'Название', type: "string", flex: 1 },
|
{ field: 'boiler_name', headerName: 'Название', type: "string", flex: 1 },
|
||||||
{ field: 'boiler_code', headerName: 'Код', type: "string", flex: 1 },
|
{ field: 'boiler_code', headerName: 'Код', type: "string", flex: 1 },
|
||||||
{ field: 'id_city', headerName: 'Город', type: "string", flex: 1 },
|
{ field: 'id_city', headerName: 'Город', type: "string", flex: 1 },
|
||||||
@ -34,23 +33,52 @@ function Boilers() {
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
|
<Flex direction='column' gap='sm' p='sm'>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%', p: '16px' }}>
|
<Text size="xl" fw={600}>
|
||||||
<Typography variant='h6' fontWeight='600'>
|
Котельные
|
||||||
Котельные
|
</Text>
|
||||||
</Typography>
|
|
||||||
|
|
||||||
{boilers &&
|
{boilers &&
|
||||||
<DataGrid
|
<Table highlightOnHover>
|
||||||
rows={boilers.map((boiler: IBoiler) => {
|
<Table.Thead>
|
||||||
return { ...boiler, id: boiler.id_object }
|
<Table.Tr>
|
||||||
})}
|
{boilersColumns.map(column => (
|
||||||
columns={boilersColumns}
|
<Table.Th key={column.field}>{column.headerName}</Table.Th>
|
||||||
/>
|
))}
|
||||||
}
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{boilers.map((boiler: any) => (
|
||||||
|
<Table.Tr key={boiler.id_object}>
|
||||||
|
{boilersColumns.map(column => {
|
||||||
|
if (column.field === 'activity') {
|
||||||
|
return (
|
||||||
|
boiler.activity ? (
|
||||||
|
<Table.Td key={`${boiler.id_object}-${boiler[column.field]}`}>
|
||||||
|
<Badge fullWidth variant="light">
|
||||||
|
Активен
|
||||||
|
</Badge>
|
||||||
|
</Table.Td>
|
||||||
|
|
||||||
</Box>
|
) : (
|
||||||
</Box>
|
<Table.Td key={`${boiler.id_object}-${boiler[column.field]}`}>
|
||||||
|
<Badge color="gray" fullWidth variant="light">
|
||||||
|
Отключен
|
||||||
|
</Badge>
|
||||||
|
</Table.Td>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else return (
|
||||||
|
<Table.Td key={`${boiler.id_object}-${boiler[column.field]}`}>{boiler[column.field]}</Table.Td>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Table.Tr>
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
}
|
||||||
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,54 @@
|
|||||||
import { Box, Card, Typography } from "@mui/material";
|
import { Card, Flex, SimpleGrid, Text } from "@mantine/core";
|
||||||
|
import { IconBuildingFactory2, IconFiles, IconMap, IconReport, IconServer, IconShield, IconUsers } from "@tabler/icons-react";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export default function Main() {
|
export default function Main() {
|
||||||
return (
|
const navigate = useNavigate()
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', p: '16px' }}>
|
|
||||||
<Typography variant='h6' fontWeight='700'>
|
|
||||||
Последние файлы
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Card>
|
interface CustomCardProps {
|
||||||
|
link: string;
|
||||||
|
icon: ReactNode;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
const CustomCard = ({
|
||||||
|
link,
|
||||||
|
icon,
|
||||||
|
label
|
||||||
|
}: CustomCardProps) => {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
onClick={() => navigate(link)}
|
||||||
|
withBorder
|
||||||
|
style={{ cursor: 'pointer', userSelect: 'none' }}
|
||||||
|
>
|
||||||
|
<Flex mih='50'>
|
||||||
|
{icon}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Text fw={500} size="lg" mt="md">
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
</Card>
|
</Card>
|
||||||
</Box>
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex direction='column' gap='sm' p='sm'>
|
||||||
|
<Text size="xl" fw={700}>
|
||||||
|
Главная
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<SimpleGrid cols={{ xs: 1, md: 3 }}>
|
||||||
|
<CustomCard link="/user" icon={<IconUsers size='50' color="#6495ED" />} label="Пользователи" />
|
||||||
|
<CustomCard link="/role" icon={<IconShield size='50' color="#6495ED" />} label="Роли" />
|
||||||
|
<CustomCard link="/documents" icon={<IconFiles size='50' color="#6495ED" />} label="Документы" />
|
||||||
|
<CustomCard link="/reports" icon={<IconReport size='50' color="#6495ED" />} label="Отчеты" />
|
||||||
|
<CustomCard link="/servers" icon={<IconServer size='50' color="#6495ED" />} label="Серверы" />
|
||||||
|
<CustomCard link="/boilers" icon={<IconBuildingFactory2 size='50' color="#6495ED" />} label="Котельные" />
|
||||||
|
<CustomCard link="/map-test" icon={<IconMap size='50' color="#6495ED" />} label="ИКС" />
|
||||||
|
</SimpleGrid>
|
||||||
|
|
||||||
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,13 +1,16 @@
|
|||||||
import { Error } from "@mui/icons-material";
|
import { Flex, Text } from "@mantine/core";
|
||||||
import { Box, Typography } from "@mui/material";
|
import { IconError404 } from "@tabler/icons-react";
|
||||||
|
|
||||||
export default function NotFound() {
|
export default function NotFound() {
|
||||||
return (
|
return (
|
||||||
<>
|
<Flex p='sm' gap='sm' align='center' justify='center'>
|
||||||
<Box sx={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
|
<Flex direction='column' gap='sm' align='center'>
|
||||||
<Error />
|
<IconError404 size={100} />
|
||||||
<Typography>Запрашиваемая страница не найдена.</Typography>
|
<Text size="xl" fw={500} ta='center'>
|
||||||
</Box>
|
Запрашиваемая страница не найдена.
|
||||||
</>
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,26 +1,25 @@
|
|||||||
import { Fragment, useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { Autocomplete, Box, Button, CircularProgress, IconButton, TextField } from "@mui/material"
|
|
||||||
import { DataGrid } from "@mui/x-data-grid"
|
|
||||||
import { useCities, useReport, useReportExport } from "../hooks/swrHooks"
|
import { useCities, useReport, useReportExport } from "../hooks/swrHooks"
|
||||||
import { useDebounce } from "@uidotdev/usehooks"
|
import { useDebounce } from "@uidotdev/usehooks"
|
||||||
import { ICity } from "../interfaces/fuel"
|
import { ICity } from "../interfaces/fuel"
|
||||||
import { Update } from "@mui/icons-material"
|
|
||||||
import { mutate } from "swr"
|
import { mutate } from "swr"
|
||||||
|
import { ActionIcon, Autocomplete, Badge, Button, CloseButton, Flex, Table } from "@mantine/core"
|
||||||
|
import { IconRefresh } from "@tabler/icons-react"
|
||||||
|
|
||||||
export default function Reports() {
|
export default function Reports() {
|
||||||
const [download, setDownload] = useState(false)
|
const [download, setDownload] = useState(false)
|
||||||
|
|
||||||
const [search, setSearch] = useState<string | null>("")
|
const [search, setSearch] = useState<string | undefined>("")
|
||||||
const debouncedSearch = useDebounce(search, 500)
|
const debouncedSearch = useDebounce(search, 500)
|
||||||
const [selectedOption, setSelectedOption] = useState<ICity | null>(null)
|
const [selectedOption, setSelectedOption] = useState<number | null>(null)
|
||||||
const { cities, isLoading } = useCities(10, 1, debouncedSearch)
|
const { cities } = useCities(10, 1, debouncedSearch)
|
||||||
|
|
||||||
const { report, isLoading: reportLoading } = useReport(selectedOption?.id)
|
const { report } = useReport(selectedOption)
|
||||||
|
|
||||||
const { reportExported } = useReportExport(selectedOption?.id, download)
|
const { reportExported } = useReportExport(selectedOption, download)
|
||||||
|
|
||||||
const refreshReport = async () => {
|
const refreshReport = async () => {
|
||||||
mutate(`/info/reports/${selectedOption?.id}?to_export=false`)
|
mutate(`/info/reports/${selectedOption}?to_export=false`)
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -41,9 +40,32 @@ export default function Reports() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', p: '16px' }}>
|
<Flex direction='column' gap='sm' p='sm'>
|
||||||
<Box sx={{ display: 'flex', gap: '16px' }}>
|
<Flex component="form" gap={'sm'}>
|
||||||
|
{/* <SearchableSelect /> */}
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
|
placeholder="Населенный пункт"
|
||||||
|
flex={'1'}
|
||||||
|
data={cities ? cities.map((item: ICity) => ({ label: item.name, value: item.id.toString() })) : []}
|
||||||
|
onSelect={(e) => console.log(e.currentTarget.value)}
|
||||||
|
onChange={(value) => setSearch(value)}
|
||||||
|
onOptionSubmit={(value) => setSelectedOption(Number(value))}
|
||||||
|
rightSection={
|
||||||
|
search !== '' && (
|
||||||
|
<CloseButton
|
||||||
|
size="sm"
|
||||||
|
onMouseDown={(event) => event.preventDefault()}
|
||||||
|
onClick={() => {
|
||||||
|
setSearch('')
|
||||||
|
setSelectedOption(null)
|
||||||
|
}}
|
||||||
|
aria-label="Clear value"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value={search}
|
||||||
|
/>
|
||||||
|
{/* <Autocomplete
|
||||||
fullWidth
|
fullWidth
|
||||||
onInputChange={(_, value) => setSearch(value)}
|
onInputChange={(_, value) => setSearch(value)}
|
||||||
onChange={(_, value) => setSelectedOption(value)}
|
onChange={(_, value) => setSelectedOption(value)}
|
||||||
@ -68,18 +90,78 @@ export default function Reports() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/> */}
|
||||||
|
|
||||||
<IconButton onClick={() => refreshReport()}>
|
<ActionIcon size='auto' variant='transparent' onClick={() => refreshReport()}>
|
||||||
<Update />
|
<IconRefresh />
|
||||||
</IconButton>
|
</ActionIcon>
|
||||||
|
|
||||||
<Button onClick={() => exportReport()}>
|
<Button disabled={!selectedOption} onClick={() => exportReport()}>
|
||||||
Экспорт
|
Экспорт
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Flex>
|
||||||
|
|
||||||
<DataGrid
|
{report &&
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
{[
|
||||||
|
{ field: 'id', headerName: '№', width: 70 },
|
||||||
|
...Object.keys(report).map(key => ({
|
||||||
|
field: key,
|
||||||
|
headerName: key.charAt(0).toUpperCase() + key.slice(1),
|
||||||
|
width: 150
|
||||||
|
}))
|
||||||
|
].map(column => (
|
||||||
|
<Table.Th key={column.headerName}>{column.headerName}</Table.Th>
|
||||||
|
))}
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{[...new Set(Object.keys(report).flatMap(key => Object.keys(report[key])))].map(id => {
|
||||||
|
const row: any = { id: Number(id) };
|
||||||
|
Object.keys(report).forEach(key => {
|
||||||
|
row[key] = report[key][id];
|
||||||
|
});
|
||||||
|
return (<Table.Tr key={row.id}>
|
||||||
|
{[
|
||||||
|
{ field: 'id', headerName: '№', width: 70 },
|
||||||
|
...Object.keys(report).map(key => ({
|
||||||
|
field: key,
|
||||||
|
headerName: key.charAt(0).toUpperCase() + key.slice(1),
|
||||||
|
width: 150
|
||||||
|
}))
|
||||||
|
].map(column => {
|
||||||
|
if (column.field === 'Активность') {
|
||||||
|
return (
|
||||||
|
row['Активность'] ? (
|
||||||
|
<Table.Td key={`${row.id}-${column.headerName}`}>
|
||||||
|
<Badge fullWidth variant="light">
|
||||||
|
Активен
|
||||||
|
</Badge>
|
||||||
|
</Table.Td>
|
||||||
|
|
||||||
|
) : (
|
||||||
|
<Table.Td key={`${row.id}-${column.headerName}`}>
|
||||||
|
<Badge color="gray" fullWidth variant="light">
|
||||||
|
Отключен
|
||||||
|
</Badge>
|
||||||
|
</Table.Td>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Table.Td key={`${row.id}-${column.headerName}`}>{row[column.field]}</Table.Td>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Table.Tr>)
|
||||||
|
})}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{/* <DataGrid
|
||||||
autoHeight
|
autoHeight
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
loading={reportLoading}
|
loading={reportLoading}
|
||||||
@ -118,7 +200,7 @@ export default function Reports() {
|
|||||||
|
|
||||||
onProcessRowUpdateError={() => {
|
onProcessRowUpdateError={() => {
|
||||||
}}
|
}}
|
||||||
/>
|
/> */}
|
||||||
</Box>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,15 +1,15 @@
|
|||||||
import { useState } from 'react'
|
import { GridColDef } from '@mui/x-data-grid'
|
||||||
import { Box, Button, CircularProgress, Modal } from '@mui/material'
|
|
||||||
import { DataGrid, GridColDef } from '@mui/x-data-grid'
|
|
||||||
import { useRoles } from '../hooks/swrHooks'
|
import { useRoles } from '../hooks/swrHooks'
|
||||||
import { CreateField } from '../interfaces/create'
|
import { CreateField } from '../interfaces/create'
|
||||||
import RoleService from '../services/RoleService'
|
import RoleService from '../services/RoleService'
|
||||||
import FormFields from '../components/FormFields'
|
import FormFields from '../components/FormFields'
|
||||||
|
import { Button, Flex, Loader, Modal, Table } from '@mantine/core'
|
||||||
|
import { useDisclosure } from '@mantine/hooks'
|
||||||
|
|
||||||
export default function Roles() {
|
export default function Roles() {
|
||||||
const { roles, isError, isLoading } = useRoles()
|
const { roles, isError, isLoading } = useRoles()
|
||||||
|
|
||||||
const [open, setOpen] = useState(false)
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
|
|
||||||
const createFields: CreateField[] = [
|
const createFields: CreateField[] = [
|
||||||
{ key: 'name', headerName: 'Название', type: 'string', required: true, defaultValue: '' },
|
{ key: 'name', headerName: 'Название', type: 'string', required: true, defaultValue: '' },
|
||||||
@ -23,43 +23,42 @@ export default function Roles() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (isError) return <div>Произошла ошибка при получении данных.</div>
|
if (isError) return <div>Произошла ошибка при получении данных.</div>
|
||||||
if (isLoading) return <CircularProgress />
|
if (isLoading) return <Loader />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Flex direction='column' align='flex-start' gap='sm' p='sm'>
|
||||||
display: 'flex',
|
<Button onClick={open}>
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
gap: '16px',
|
|
||||||
flexGrow: 1,
|
|
||||||
p: '16px'
|
|
||||||
}}>
|
|
||||||
<Button onClick={() => setOpen(true)}>
|
|
||||||
Добавить роль
|
Добавить роль
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Modal
|
<Modal opened={opened} onClose={close} title="Создание роли" centered>
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
>
|
|
||||||
<FormFields
|
<FormFields
|
||||||
sx={{
|
|
||||||
position: 'absolute' as 'absolute',
|
|
||||||
top: '50%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
width: 400,
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
boxShadow: 24,
|
|
||||||
p: 4,
|
|
||||||
}}
|
|
||||||
fields={createFields}
|
fields={createFields}
|
||||||
submitHandler={RoleService.createRole}
|
submitHandler={RoleService.createRole}
|
||||||
title="Создание роли"
|
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<DataGrid
|
<Table highlightOnHover>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
{columns.map(column => (
|
||||||
|
<Table.Th key={column.field}>{column.headerName}</Table.Th>
|
||||||
|
))}
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>{roles.map((role: any) => (
|
||||||
|
<Table.Tr
|
||||||
|
key={role.id}
|
||||||
|
//bg={selectedRows.includes(element.position) ? 'var(--mantine-color-blue-light)' : undefined}
|
||||||
|
>
|
||||||
|
{columns.map(column => (
|
||||||
|
<Table.Td key={column.field}>{role[column.field]}</Table.Td>
|
||||||
|
))}
|
||||||
|
</Table.Tr>
|
||||||
|
))}</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
{/* <DataGrid
|
||||||
autoHeight
|
autoHeight
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
rows={roles}
|
rows={roles}
|
||||||
@ -78,7 +77,7 @@ export default function Roles() {
|
|||||||
|
|
||||||
onProcessRowUpdateError={() => {
|
onProcessRowUpdateError={() => {
|
||||||
}}
|
}}
|
||||||
/>
|
/> */}
|
||||||
</Box>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,67 +1,41 @@
|
|||||||
import { Box, Tab, Tabs } from "@mui/material"
|
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import ServersView from "../components/ServersView"
|
import ServersView from "../components/ServersView"
|
||||||
import ServerIpsView from "../components/ServerIpsView"
|
import ServerIpsView from "../components/ServerIpsView"
|
||||||
import ServerHardware from "../components/ServerHardware"
|
import ServerHardware from "../components/ServerHardware"
|
||||||
import ServerStorage from "../components/ServerStorages"
|
import ServerStorage from "../components/ServerStorages"
|
||||||
|
import { Flex, Tabs } from "@mantine/core"
|
||||||
|
|
||||||
export default function Servers() {
|
export default function Servers() {
|
||||||
const [currentTab, setCurrentTab] = useState(0)
|
const [currentTab, setCurrentTab] = useState<string | null>('0')
|
||||||
|
|
||||||
const handleTabChange = (newValue: number) => {
|
|
||||||
setCurrentTab(newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TabPanelProps {
|
|
||||||
children?: React.ReactNode;
|
|
||||||
index: number;
|
|
||||||
value: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function CustomTabPanel(props: TabPanelProps) {
|
|
||||||
const { children, value, index, ...other } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
role="tabpanel"
|
|
||||||
hidden={value !== index}
|
|
||||||
id={`simple-tabpanel-${index}`}
|
|
||||||
aria-labelledby={`simple-tab-${index}`}
|
|
||||||
{...other}
|
|
||||||
>
|
|
||||||
{value === index && <Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>{children}</Box>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%', p: '16px' }}>
|
<Flex gap='sm' p='sm' direction='column'>
|
||||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
<Flex gap='sm' direction='column'>
|
||||||
<Tabs value={currentTab} onChange={(_, value) =>
|
<Tabs value={currentTab} onChange={setCurrentTab}>
|
||||||
handleTabChange(value)
|
<Tabs.List>
|
||||||
} aria-label="basic tabs example">
|
<Tabs.Tab value="0">Серверы</Tabs.Tab>
|
||||||
<Tab label="Серверы" />
|
<Tabs.Tab value="1">IP-адреса</Tabs.Tab>
|
||||||
<Tab label="IP-адреса" />
|
<Tabs.Tab value="3">Hardware</Tabs.Tab>
|
||||||
<Tab label="Hardware" />
|
<Tabs.Tab value="4">Storages</Tabs.Tab>
|
||||||
<Tab label="Storages" />
|
</Tabs.List>
|
||||||
|
|
||||||
|
<Tabs.Panel value="0" pt='sm'>
|
||||||
|
<ServersView />
|
||||||
|
</Tabs.Panel>
|
||||||
|
|
||||||
|
<Tabs.Panel value="1" pt='sm'>
|
||||||
|
<ServerIpsView />
|
||||||
|
</Tabs.Panel>
|
||||||
|
|
||||||
|
<Tabs.Panel value="2" pt='sm'>
|
||||||
|
<ServerHardware />
|
||||||
|
</Tabs.Panel>
|
||||||
|
|
||||||
|
<Tabs.Panel value="3" pt='sm'>
|
||||||
|
<ServerStorage />
|
||||||
|
</Tabs.Panel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Flex>
|
||||||
|
|
||||||
<CustomTabPanel value={currentTab} index={0}>
|
|
||||||
<ServersView />
|
|
||||||
</CustomTabPanel>
|
|
||||||
|
|
||||||
<CustomTabPanel value={currentTab} index={1}>
|
|
||||||
<ServerIpsView />
|
|
||||||
</CustomTabPanel>
|
|
||||||
|
|
||||||
<CustomTabPanel value={currentTab} index={2}>
|
|
||||||
<ServerHardware />
|
|
||||||
</CustomTabPanel>
|
|
||||||
|
|
||||||
<CustomTabPanel value={currentTab} index={3}>
|
|
||||||
<ServerStorage />
|
|
||||||
</CustomTabPanel>
|
|
||||||
|
|
||||||
{/* <BarChart
|
{/* <BarChart
|
||||||
xAxis={[{ scaleType: 'band', data: ['group A', 'group B', 'group C'] }]}
|
xAxis={[{ scaleType: 'band', data: ['group A', 'group B', 'group C'] }]}
|
||||||
@ -69,6 +43,6 @@ export default function Servers() {
|
|||||||
width={500}
|
width={500}
|
||||||
height={300}
|
height={300}
|
||||||
/> */}
|
/> */}
|
||||||
</Box>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,4 +1,3 @@
|
|||||||
import { Box, Stack } from "@mui/material"
|
|
||||||
import UserService from "../services/UserService"
|
import UserService from "../services/UserService"
|
||||||
import { setUserData, useAuthStore } from "../store/auth"
|
import { setUserData, useAuthStore } from "../store/auth"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
@ -6,6 +5,7 @@ import { CreateField } from "../interfaces/create"
|
|||||||
import { IUser } from "../interfaces/user"
|
import { IUser } from "../interfaces/user"
|
||||||
import FormFields from "../components/FormFields"
|
import FormFields from "../components/FormFields"
|
||||||
import AuthService from "../services/AuthService"
|
import AuthService from "../services/AuthService"
|
||||||
|
import { Flex } from "@mantine/core"
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const { token } = useAuthStore()
|
const { token } = useAuthStore()
|
||||||
@ -39,37 +39,31 @@ export default function Settings() {
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Flex
|
||||||
sx={{
|
direction='column'
|
||||||
display: "flex",
|
align='flex-start'
|
||||||
flexDirection: "column",
|
gap='sm'
|
||||||
alignItems: "flex-start",
|
p='sm'
|
||||||
gap: "16px",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{currentUser &&
|
{currentUser &&
|
||||||
<Stack spacing={2} width='100%'>
|
<Flex direction='column' gap='sm' w='100%'>
|
||||||
<Stack width='100%'>
|
<FormFields
|
||||||
<FormFields
|
fields={profileFields}
|
||||||
fields={profileFields}
|
defaultValues={currentUser}
|
||||||
defaultValues={currentUser}
|
mutateHandler={(data: any) => {
|
||||||
mutateHandler={(data: any) => {
|
setUserData(data)
|
||||||
setUserData(data)
|
}}
|
||||||
}}
|
submitHandler={(data) => UserService.updateUser({ id: currentUser.id, ...data })}
|
||||||
submitHandler={(data) => UserService.updateUser({ id: currentUser.id, ...data })}
|
title="Пользователь"
|
||||||
title="Пользователь"
|
/>
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<Stack width='100%'>
|
<FormFields
|
||||||
<FormFields
|
fields={passwordFields}
|
||||||
fields={passwordFields}
|
submitHandler={(data) => AuthService.updatePassword({ id: currentUser.id, ...data })}
|
||||||
submitHandler={(data) => AuthService.updatePassword({ id: currentUser.id, ...data })}
|
title="Смена пароля"
|
||||||
title="Смена пароля"
|
/>
|
||||||
/>
|
</Flex>
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
}
|
}
|
||||||
</Box>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
10
client/src/pages/TableTest.module.scss
Normal file
10
client/src/pages/TableTest.module.scss
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
$ka-background-color: #2c2c2c;
|
||||||
|
$ka-border-color: #4d4d4d;
|
||||||
|
$ka-cell-hover-background-color: transparentize(#fff, 0.8);
|
||||||
|
$ka-color-base: #fefefe;
|
||||||
|
$ka-input-background-color: $ka-background-color;
|
||||||
|
$ka-input-border-color: $ka-border-color;
|
||||||
|
$ka-input-color: $ka-color-base;
|
||||||
|
$ka-row-hover-background-color: transparentize(#fff, 0.9);
|
||||||
|
$ka-thead-background-color: #1b1b1b;
|
||||||
|
$ka-thead-color: #c5c5c5;
|
32
client/src/pages/TableTest.tsx
Normal file
32
client/src/pages/TableTest.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Table, DataType } from 'ka-table'
|
||||||
|
import 'ka-table/style.css';
|
||||||
|
import { Flex } from '@mantine/core';
|
||||||
|
import styles from './TableTest.module.scss'
|
||||||
|
|
||||||
|
function TableTest() {
|
||||||
|
return (
|
||||||
|
<Flex direction='column' align='flex-start' gap='sm' p='sm'>
|
||||||
|
<Table
|
||||||
|
columns={[
|
||||||
|
{ key: 'column1', title: 'Column 1', dataType: DataType.String },
|
||||||
|
{ key: 'column2', title: 'Column 2', dataType: DataType.String },
|
||||||
|
{ key: 'column3', title: 'Column 3', dataType: DataType.String },
|
||||||
|
{ key: 'column4', title: 'Column 4', dataType: DataType.String },
|
||||||
|
]}
|
||||||
|
data={Array(100).fill(undefined).map(
|
||||||
|
(_, index) => ({
|
||||||
|
column1: `column:1 row:${index}`,
|
||||||
|
column2: `column:2 row:${index}`,
|
||||||
|
column3: `column:3 row:${index}`,
|
||||||
|
column4: `column:4 row:${index}`,
|
||||||
|
id: index,
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
rowKeyField={'id'}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TableTest
|
@ -1,18 +1,27 @@
|
|||||||
import { Box, Button, CircularProgress, Modal } from "@mui/material"
|
import { GridColDef } from "@mui/x-data-grid"
|
||||||
import { DataGrid, GridColDef } from "@mui/x-data-grid"
|
|
||||||
import { useRoles, useUsers } from "../hooks/swrHooks"
|
import { useRoles, useUsers } from "../hooks/swrHooks"
|
||||||
import { IRole } from "../interfaces/role"
|
import { IRole } from "../interfaces/role"
|
||||||
import { useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { CreateField } from "../interfaces/create"
|
import { CreateField } from "../interfaces/create"
|
||||||
import UserService from "../services/UserService"
|
import UserService from "../services/UserService"
|
||||||
import FormFields from "../components/FormFields"
|
import FormFields from "../components/FormFields"
|
||||||
|
import { Badge, Button, Flex, Loader, Modal, Pagination, Select, Table } from "@mantine/core"
|
||||||
|
import { useDisclosure } from "@mantine/hooks"
|
||||||
|
|
||||||
export default function Users() {
|
export default function Users() {
|
||||||
const { users, isError, isLoading } = useUsers()
|
const { users, isError, isLoading } = useUsers()
|
||||||
|
|
||||||
const { roles } = useRoles()
|
const { roles } = useRoles()
|
||||||
|
|
||||||
const [open, setOpen] = useState(false)
|
const [roleOptions, setRoleOptions] = useState<any>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (Array.isArray(roles)) {
|
||||||
|
setRoleOptions(roles.map((role: IRole) => ({ label: role.name, value: role.id.toString() })))
|
||||||
|
}
|
||||||
|
}, [roles])
|
||||||
|
|
||||||
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
|
|
||||||
const createFields: CreateField[] = [
|
const createFields: CreateField[] = [
|
||||||
{ key: 'email', headerName: 'E-mail', type: 'string', required: true, defaultValue: '' },
|
{ key: 'email', headerName: 'E-mail', type: 'string', required: true, defaultValue: '' },
|
||||||
@ -30,7 +39,7 @@ export default function Users() {
|
|||||||
{ field: 'phone', headerName: 'Телефон', flex: 1, editable: true },
|
{ field: 'phone', headerName: 'Телефон', flex: 1, editable: true },
|
||||||
{ field: 'name', headerName: 'Имя', flex: 1, editable: true },
|
{ field: 'name', headerName: 'Имя', flex: 1, editable: true },
|
||||||
{ field: 'surname', headerName: 'Фамилия', flex: 1, editable: true },
|
{ field: 'surname', headerName: 'Фамилия', flex: 1, editable: true },
|
||||||
{ field: 'is_active', headerName: 'Активен', type: "boolean", flex: 1, editable: true },
|
{ field: 'is_active', headerName: 'Статус', type: "boolean", flex: 1, editable: true },
|
||||||
{
|
{
|
||||||
field: 'role_id',
|
field: 'role_id',
|
||||||
headerName: 'Роль',
|
headerName: 'Роль',
|
||||||
@ -41,44 +50,92 @@ export default function Users() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isError) return <div>Произошла ошибка при получении данных.</div>
|
if (isError) return (
|
||||||
if (isLoading) return <CircularProgress />
|
<div>
|
||||||
|
Произошла ошибка при получении данных.
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<Flex direction='column' align='flex-start' gap='sm' p='sm'>
|
||||||
|
<Loader />
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Flex direction='column' align='flex-start' gap='sm' p='sm'>
|
||||||
display: "flex",
|
<Button onClick={open}>
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "flex-start",
|
|
||||||
gap: "16px",
|
|
||||||
p: '16px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button onClick={() => setOpen(true)}>
|
|
||||||
Добавить пользователя
|
Добавить пользователя
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Modal
|
<Modal opened={opened} onClose={close} title="Регистрация пользователя" centered>
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
>
|
|
||||||
<FormFields
|
<FormFields
|
||||||
fields={createFields}
|
fields={createFields}
|
||||||
submitHandler={UserService.createUser}
|
submitHandler={UserService.createUser}
|
||||||
title="Создание пользователя"
|
|
||||||
sx={{
|
|
||||||
position: 'absolute' as 'absolute',
|
|
||||||
top: '50%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
width: 400,
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
boxShadow: 24,
|
|
||||||
p: 4,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<DataGrid
|
{Array.isArray(roleOptions) &&
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
{columns.map(column => (
|
||||||
|
<Table.Th key={column.field}>{column.headerName}</Table.Th>
|
||||||
|
))}
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{users.map((user: any) => (
|
||||||
|
<Table.Tr
|
||||||
|
key={user.id}
|
||||||
|
//bg={selectedRows.includes(element.position) ? 'var(--mantine-color-blue-light)' : undefined}
|
||||||
|
>
|
||||||
|
{columns.map(column => {
|
||||||
|
if (column.field === 'is_active') {
|
||||||
|
return (
|
||||||
|
user.is_active ? (
|
||||||
|
<Table.Td key={column.field}>
|
||||||
|
<Badge fullWidth variant="light">
|
||||||
|
Активен
|
||||||
|
</Badge>
|
||||||
|
</Table.Td>
|
||||||
|
|
||||||
|
) : (
|
||||||
|
<Table.Td key={column.field}>
|
||||||
|
<Badge color="gray" fullWidth variant="light">
|
||||||
|
Отключен
|
||||||
|
</Badge>
|
||||||
|
</Table.Td>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else if (column.field === 'role_id') {
|
||||||
|
return (
|
||||||
|
<Table.Td key={column.field}>
|
||||||
|
<Select
|
||||||
|
data={roleOptions}
|
||||||
|
defaultValue={user.role_id.toString()}
|
||||||
|
variant="unstyled"
|
||||||
|
allowDeselect={false}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else return (
|
||||||
|
<Table.Td key={column.field}>{user[column.field]}</Table.Td>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Table.Tr>
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
}
|
||||||
|
|
||||||
|
<Pagination total={10} />
|
||||||
|
|
||||||
|
{/* <DataGrid
|
||||||
density="compact"
|
density="compact"
|
||||||
autoHeight
|
autoHeight
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
@ -99,7 +156,7 @@ export default function Users() {
|
|||||||
|
|
||||||
onProcessRowUpdateError={() => {
|
onProcessRowUpdateError={() => {
|
||||||
}}
|
}}
|
||||||
/>
|
/> */}
|
||||||
</Box>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,8 +1,9 @@
|
|||||||
import { Box, Button, CircularProgress, Container, Fade, Grow, Stack, TextField, Typography } from '@mui/material'
|
import { CircularProgress, Fade, Grow } from '@mui/material'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||||
import AuthService from '../../services/AuthService';
|
import AuthService from '../../services/AuthService';
|
||||||
import { CheckCircle } from '@mui/icons-material';
|
import { CheckCircle } from '@mui/icons-material';
|
||||||
|
import { Button, Flex, Paper, Text, TextInput } from '@mantine/core';
|
||||||
|
|
||||||
interface PasswordResetProps {
|
interface PasswordResetProps {
|
||||||
email: string;
|
email: string;
|
||||||
@ -31,61 +32,58 @@ function PasswordReset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="sm">
|
<Paper flex={1} maw='500' withBorder radius='md' p='xl'>
|
||||||
<Box my={4}>
|
<Flex direction='column' gap='sm'>
|
||||||
<Typography variant="h4" component="h1" gutterBottom>
|
<Text size="xl" fw={500}>
|
||||||
Восстановление пароля
|
Восстановление пароля
|
||||||
</Typography>
|
</Text>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
{!success && <Fade in={!success}>
|
{!success && <Fade in={!success}>
|
||||||
<Stack spacing={2}>
|
<Flex direction='column' gap={'md'}>
|
||||||
<Typography>
|
<Text>
|
||||||
Введите адрес электронной почты, на который будут отправлены новые данные для авторизации:
|
Введите адрес электронной почты, на который будут отправлены новые данные для авторизации:
|
||||||
</Typography>
|
</Text>
|
||||||
|
|
||||||
<TextField
|
<TextInput
|
||||||
fullWidth
|
label='E-mail'
|
||||||
margin="normal"
|
|
||||||
label="E-mail"
|
|
||||||
required
|
required
|
||||||
{...register('email', { required: 'Введите E-mail' })}
|
{...register('email', { required: 'Введите E-mail' })}
|
||||||
error={!!errors.email}
|
error={errors.email?.message}
|
||||||
helperText={errors.email?.message}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', gap: '16px' }}>
|
<Flex gap='sm'>
|
||||||
<Button fullWidth type="submit" disabled={isSubmitting || watch('email').length == 0} variant="contained" color="primary">
|
<Button flex={1} type="submit" disabled={isSubmitting || watch('email').length == 0} variant='filled'>
|
||||||
{isSubmitting ? <CircularProgress size={16} /> : 'Восстановить пароль'}
|
{isSubmitting ? <CircularProgress size={16} /> : 'Восстановить пароль'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button fullWidth href="/auth/signin" type="button" variant="text" color="primary">
|
<Button flex={1} component='a' href="/auth/signin" type="button" variant='light'>
|
||||||
Назад
|
Назад
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Flex>
|
||||||
|
|
||||||
</Stack>
|
</Flex>
|
||||||
</Fade>}
|
</Fade>}
|
||||||
{success &&
|
{success &&
|
||||||
<Grow in={success}>
|
<Grow in={success}>
|
||||||
<Stack spacing={2}>
|
<Flex direction='column' gap='sm'>
|
||||||
<Stack direction='row' alignItems='center' spacing={2}>
|
<Flex align='center' gap='sm'>
|
||||||
<CheckCircle color='success' />
|
<CheckCircle color='success' />
|
||||||
<Typography>
|
<Text>
|
||||||
На указанный адрес было отправлено письмо с новыми данными для авторизации.
|
На указанный адрес было отправлено письмо с новыми данными для авторизации.
|
||||||
</Typography>
|
</Text>
|
||||||
</Stack>
|
</Flex>
|
||||||
<Box sx={{ display: 'flex', gap: '16px' }}>
|
<Flex gap='sm'>
|
||||||
<Button fullWidth href="/auth/signin" type="button" variant="contained" color="primary">
|
<Button component='a' href="/auth/signin" type="button">
|
||||||
Войти
|
Войти
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Flex>
|
||||||
</Stack>
|
</Flex>
|
||||||
</Grow>
|
</Grow>
|
||||||
}
|
}
|
||||||
</form>
|
</form>
|
||||||
</Box>
|
</Flex>
|
||||||
</Container>
|
</Paper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||||
import { TextField, Button, Container, Typography, Box, Stack, Link, CircularProgress } from '@mui/material';
|
|
||||||
import { AxiosResponse } from 'axios';
|
import { AxiosResponse } from 'axios';
|
||||||
import { ApiResponse, LoginFormData } from '../../interfaces/auth';
|
import { ApiResponse, LoginFormData } from '../../interfaces/auth';
|
||||||
import { login, setUserData } from '../../store/auth';
|
import { login, setUserData } from '../../store/auth';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import AuthService from '../../services/AuthService';
|
import AuthService from '../../services/AuthService';
|
||||||
import UserService from '../../services/UserService';
|
import UserService from '../../services/UserService';
|
||||||
|
import { Button, Flex, Loader, Paper, Text, TextInput } from '@mantine/core';
|
||||||
|
|
||||||
const SignIn = () => {
|
const SignIn = () => {
|
||||||
const { register, handleSubmit, setError, formState: { errors, isSubmitting } } = useForm<LoginFormData>({
|
const { register, handleSubmit, setError, formState: { errors, isSubmitting, isValid } } = useForm<LoginFormData>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
@ -47,54 +47,48 @@ const SignIn = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="sm">
|
<Paper flex={1} maw='500' withBorder radius='md' p='xl'>
|
||||||
<Box my={4}>
|
<Flex direction='column' gap='sm'>
|
||||||
<Typography variant="h4" component="h1" gutterBottom>
|
<Text size="xl" fw={500}>
|
||||||
Вход
|
Вход
|
||||||
</Typography>
|
</Text>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Stack spacing={2}>
|
<Flex direction='column' gap='sm'>
|
||||||
<TextField
|
<TextInput
|
||||||
fullWidth
|
label='Логин'
|
||||||
margin="normal"
|
|
||||||
label="Логин"
|
|
||||||
required
|
required
|
||||||
{...register('username', { required: 'Введите логин' })}
|
{...register('username', { required: 'Введите логин' })}
|
||||||
error={!!errors.username}
|
error={errors.username?.message}
|
||||||
helperText={errors.username?.message}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextInput
|
||||||
fullWidth
|
label='Пароль'
|
||||||
margin="normal"
|
type='password'
|
||||||
type="password"
|
|
||||||
label="Пароль"
|
|
||||||
required
|
required
|
||||||
{...register('password', { required: 'Введите пароль' })}
|
{...register('password', { required: 'Введите пароль' })}
|
||||||
error={!!errors.password}
|
error={errors.password?.message}
|
||||||
helperText={errors.password?.message}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', gap: '16px', justifyContent: 'flex-end' }}>
|
<Flex justify='flex-end' gap='sm'>
|
||||||
<Link href="/auth/password-reset" color="primary">
|
<Button component='a' href='/auth/password-reset' variant='transparent'>
|
||||||
Восстановить пароль
|
Восстановить пароль
|
||||||
</Link>
|
</Button>
|
||||||
</Box>
|
</Flex>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', gap: '16px' }}>
|
<Flex gap='sm'>
|
||||||
<Button fullWidth type="submit" variant="contained" color="primary">
|
<Button disabled={!isValid} type="submit" flex={1} variant='filled'>
|
||||||
{isSubmitting ? <CircularProgress size={16} /> : 'Вход'}
|
{isSubmitting ? <Loader size={16} /> : 'Вход'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* <Button fullWidth href="/auth/signup" type="button" variant="text" color="primary">
|
{/* <Button component='a' flex={1} href='/auth/signup' type="button" variant='light'>
|
||||||
Регистрация
|
Регистрация
|
||||||
</Button> */}
|
</Button> */}
|
||||||
</Box>
|
</Flex>
|
||||||
</Stack>
|
</Flex>
|
||||||
</form>
|
</form>
|
||||||
</Box>
|
</Flex>
|
||||||
</Container>
|
</Paper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
920
client/yarn.lock
920
client/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user