forked from VinokurovVE/tests
DashboardLayout changes, refactoring, useSWR
This commit is contained in:
273
frontend_reactjs/package-lock.json
generated
273
frontend_reactjs/package-lock.json
generated
@ -13,7 +13,9 @@
|
||||
"@emotion/styled": "^11.11.5",
|
||||
"@mui/icons-material": "^5.15.20",
|
||||
"@mui/material": "^5.15.20",
|
||||
"@mui/x-charts": "^7.7.1",
|
||||
"@mui/x-data-grid": "^7.7.1",
|
||||
"@tanstack/react-query": "^5.45.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"axios": "^1.7.2",
|
||||
"postcss": "^8.4.38",
|
||||
@ -21,6 +23,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.52.0",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"swr": "^2.2.5",
|
||||
"zod": "^3.23.8",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
@ -2886,6 +2889,44 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-charts": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.7.1.tgz",
|
||||
"integrity": "sha512-qUvkGGxBukHXLUqoTUs2rJZz1t+gK0P+bZzQWFbxN93XQyAhrjrOU8VN0x+G7nO3qcXrC2P6s0nevms7ZRSDrA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.24.7",
|
||||
"@mui/base": "^5.0.0-beta.40",
|
||||
"@mui/system": "^5.15.20",
|
||||
"@mui/utils": "^5.15.20",
|
||||
"@react-spring/rafz": "^9.7.3",
|
||||
"@react-spring/web": "^9.7.3",
|
||||
"clsx": "^2.1.1",
|
||||
"d3-color": "^3.1.0",
|
||||
"d3-delaunay": "^6.0.4",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-shape": "^3.2.0",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.9.0",
|
||||
"@emotion/styled": "^11.8.1",
|
||||
"@mui/material": "^5.15.14",
|
||||
"react": "^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-data-grid": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.7.1.tgz",
|
||||
@ -2965,6 +3006,71 @@
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-spring/animated": {
|
||||
"version": "9.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz",
|
||||
"integrity": "sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==",
|
||||
"dependencies": {
|
||||
"@react-spring/shared": "~9.7.3",
|
||||
"@react-spring/types": "~9.7.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-spring/core": {
|
||||
"version": "9.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.3.tgz",
|
||||
"integrity": "sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==",
|
||||
"dependencies": {
|
||||
"@react-spring/animated": "~9.7.3",
|
||||
"@react-spring/shared": "~9.7.3",
|
||||
"@react-spring/types": "~9.7.3"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/react-spring/donate"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-spring/rafz": {
|
||||
"version": "9.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.3.tgz",
|
||||
"integrity": "sha512-9vzW1zJPcC4nS3aCV+GgcsK/WLaB520Iyvm55ARHfM5AuyBqycjvh1wbmWmgCyJuX4VPoWigzemq1CaaeRSHhQ=="
|
||||
},
|
||||
"node_modules/@react-spring/shared": {
|
||||
"version": "9.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.3.tgz",
|
||||
"integrity": "sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==",
|
||||
"dependencies": {
|
||||
"@react-spring/types": "~9.7.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-spring/types": {
|
||||
"version": "9.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.3.tgz",
|
||||
"integrity": "sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw=="
|
||||
},
|
||||
"node_modules/@react-spring/web": {
|
||||
"version": "9.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.3.tgz",
|
||||
"integrity": "sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==",
|
||||
"dependencies": {
|
||||
"@react-spring/animated": "~9.7.3",
|
||||
"@react-spring/core": "~9.7.3",
|
||||
"@react-spring/shared": "~9.7.3",
|
||||
"@react-spring/types": "~9.7.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz",
|
||||
@ -3475,6 +3581,30 @@
|
||||
"@swc/counter": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.45.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.45.0.tgz",
|
||||
"integrity": "sha512-RVfIZQmFUTdjhSAAblvueimfngYyfN6HlwaJUPK71PKd7yi43Vs1S/rdimmZedPWX/WGppcq/U1HOj7O7FwYxw==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "5.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.45.1.tgz",
|
||||
"integrity": "sha512-mYYfJujKg2kxmkRRjA6nn4YKG3ITsKuH22f1kteJ5IuVQqgKUgbaSQfYwVP0gBS05mhwxO03HVpD0t7BMN7WOA==",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.45.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
||||
@ -4209,6 +4339,11 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/client-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
@ -4351,6 +4486,111 @@
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-delaunay": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
|
||||
"integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
|
||||
"dependencies": {
|
||||
"delaunator": "5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-format": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
|
||||
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-path": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
|
||||
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-scale": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||
"dependencies": {
|
||||
"d3-array": "2.10.0 - 3",
|
||||
"d3-format": "1 - 3",
|
||||
"d3-interpolate": "1.2.0 - 3",
|
||||
"d3-time": "2.1.1 - 3",
|
||||
"d3-time-format": "2 - 4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-shape": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
|
||||
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
|
||||
"dependencies": {
|
||||
"d3-path": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||
"dependencies": {
|
||||
"d3-array": "2 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time-format": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
||||
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||
"dependencies": {
|
||||
"d3-time": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/data-view-buffer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz",
|
||||
@ -4467,6 +4707,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delaunator": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
|
||||
"integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
|
||||
"dependencies": {
|
||||
"robust-predicates": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@ -5556,6 +5804,14 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/internmap": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/is-array-buffer": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
|
||||
@ -7016,6 +7272,11 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/robust-predicates": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
|
||||
"integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.18.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz",
|
||||
@ -7570,6 +7831,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/swr": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz",
|
||||
"integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==",
|
||||
"dependencies": {
|
||||
"client-only": "^0.0.1",
|
||||
"use-sync-external-store": "^1.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.11.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.4",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz",
|
||||
|
@ -15,7 +15,9 @@
|
||||
"@emotion/styled": "^11.11.5",
|
||||
"@mui/icons-material": "^5.15.20",
|
||||
"@mui/material": "^5.15.20",
|
||||
"@mui/x-charts": "^7.7.1",
|
||||
"@mui/x-data-grid": "^7.7.1",
|
||||
"@tanstack/react-query": "^5.45.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"axios": "^1.7.2",
|
||||
"postcss": "^8.4.38",
|
||||
@ -23,6 +25,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.52.0",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"swr": "^2.2.5",
|
||||
"zod": "^3.23.8",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
|
96
frontend_reactjs/src/components/AccountMenu.tsx
Normal file
96
frontend_reactjs/src/components/AccountMenu.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
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 Divider from '@mui/material/Divider';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import PersonAdd from '@mui/icons-material/PersonAdd';
|
||||
import Settings from '@mui/icons-material/Settings';
|
||||
import Logout from '@mui/icons-material/Logout';
|
||||
|
||||
export default function AccountMenu() {
|
||||
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);
|
||||
};
|
||||
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}
|
||||
onClick={handleClose}
|
||||
PaperProps={{
|
||||
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={handleClose}>
|
||||
<Avatar /> Профиль
|
||||
</MenuItem>
|
||||
|
||||
<Divider />
|
||||
|
||||
<MenuItem onClick={handleClose}>
|
||||
<ListItemIcon>
|
||||
<Settings fontSize="small" />
|
||||
</ListItemIcon>
|
||||
Настройки
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleClose}>
|
||||
<ListItemIcon>
|
||||
<Logout fontSize="small" />
|
||||
</ListItemIcon>
|
||||
Выход
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
@ -6,9 +6,10 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function DataTable(props: Props) {
|
||||
|
||||
return (
|
||||
<div style={{ flexGrow: 1, height: "100%", width: '100%' }}>
|
||||
<DataGrid
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
rows={props.rows}
|
||||
columns={props.columns}
|
||||
initialState={{
|
||||
@ -18,7 +19,9 @@ export default function DataTable(props: Props) {
|
||||
}}
|
||||
pageSizeOptions={[10, 20, 50, 100]}
|
||||
checkboxSelection
|
||||
|
||||
disableRowSelectionOnClick
|
||||
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
11
frontend_reactjs/src/components/modals/CreateRoleModal.tsx
Normal file
11
frontend_reactjs/src/components/modals/CreateRoleModal.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
|
||||
const CreateRoleModal = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateRoleModal
|
141
frontend_reactjs/src/components/modals/CreateUserModal.tsx
Normal file
141
frontend_reactjs/src/components/modals/CreateUserModal.tsx
Normal file
@ -0,0 +1,141 @@
|
||||
import { Box, Button, Modal, TextField, Typography } from '@mui/material'
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||
import { ApiResponse } from '../../interfaces/auth';
|
||||
import UserService from '../../services/UserService';
|
||||
import { CreateUserFormData } from '../../interfaces/user';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
setOpen: (state: boolean) => void;
|
||||
}
|
||||
|
||||
const style = {
|
||||
position: 'absolute' as 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 400,
|
||||
bgcolor: 'background.paper',
|
||||
boxShadow: 24,
|
||||
borderRadius: 2,
|
||||
p: 4,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "8px"
|
||||
}
|
||||
|
||||
export default function CreateUserModal({
|
||||
open,
|
||||
setOpen,
|
||||
}: Props) {
|
||||
const { register, handleSubmit, formState: { errors } } = useForm<CreateUserFormData>({
|
||||
defaultValues: {
|
||||
email: '',
|
||||
login: '',
|
||||
phone: '',
|
||||
name: '',
|
||||
surname: '',
|
||||
is_active: true,
|
||||
password: '',
|
||||
}
|
||||
})
|
||||
|
||||
const onSubmit: SubmitHandler<CreateUserFormData> = async (data) => {
|
||||
try {
|
||||
const response: AxiosResponse<ApiResponse> = await UserService.createUser(data)
|
||||
console.log(response.data)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description"
|
||||
>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
|
||||
<Box sx={style}>
|
||||
<Typography variant="h6" component="h6" gutterBottom>
|
||||
Создание пользователя
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Email"
|
||||
required
|
||||
{...register('email', { required: 'Email обязателен' })}
|
||||
error={!!errors.email}
|
||||
helperText={errors.email?.message}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Логин"
|
||||
required
|
||||
{...register('login', { required: 'Логин обязателен' })}
|
||||
error={!!errors.login}
|
||||
helperText={errors.login?.message}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Телефон"
|
||||
{...register('phone')}
|
||||
error={!!errors.phone}
|
||||
helperText={errors.phone?.message}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Имя"
|
||||
{...register('name')}
|
||||
error={!!errors.name}
|
||||
helperText={errors.name?.message}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Фамилия"
|
||||
{...register('surname')}
|
||||
error={!!errors.surname}
|
||||
helperText={errors.surname?.message}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
type="password"
|
||||
label="Пароль"
|
||||
required
|
||||
{...register('password', { required: 'Пароль обязателен' })}
|
||||
error={!!errors.password}
|
||||
helperText={errors.password?.message}
|
||||
/>
|
||||
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
gap: "8px"
|
||||
}}>
|
||||
<Button type="submit" variant="contained" color="primary">
|
||||
Добавить пользователя
|
||||
</Button>
|
||||
|
||||
<Button type="button" variant="outlined" color="primary" onClick={() => setOpen(false)}>
|
||||
Отмена
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</form>
|
||||
</Modal>
|
||||
)
|
||||
}
|
23
frontend_reactjs/src/hooks/swrHooks.ts
Normal file
23
frontend_reactjs/src/hooks/swrHooks.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import useSWR from "swr";
|
||||
import RoleService from "../services/RoleService";
|
||||
import UserService from "../services/UserService";
|
||||
|
||||
export function useRoles() {
|
||||
const { data, error, isLoading } = useSWR(`/auth/roles`, RoleService.getRoles)
|
||||
|
||||
return {
|
||||
roles: data?.data,
|
||||
isLoading,
|
||||
isError: error
|
||||
}
|
||||
}
|
||||
|
||||
export function useUsers() {
|
||||
const { data, error, isLoading } = useSWR(`/auth/user`, UserService.getUsers)
|
||||
|
||||
return {
|
||||
users: data?.data,
|
||||
isLoading,
|
||||
isError: error
|
||||
}
|
||||
}
|
@ -16,23 +16,25 @@ export interface UserCreds extends User {
|
||||
password: string;
|
||||
}
|
||||
|
||||
|
||||
export interface Role {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface RoleCreate {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface AuthState {
|
||||
isAuthenticated: boolean;
|
||||
token: string | null;
|
||||
userData: UserData | {};
|
||||
}
|
||||
|
||||
export interface SignUpFormData {
|
||||
email: string;
|
||||
login: string;
|
||||
phone: string;
|
||||
name: string;
|
||||
surname: string;
|
||||
is_active: boolean;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface SignInFormData {
|
||||
export interface LoginFormData {
|
||||
username: string;
|
||||
password: string;
|
||||
grant_type: string;
|
||||
|
9
frontend_reactjs/src/interfaces/user.ts
Normal file
9
frontend_reactjs/src/interfaces/user.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export interface CreateUserFormData {
|
||||
email: string;
|
||||
login: string;
|
||||
phone: string;
|
||||
name: string;
|
||||
surname: string;
|
||||
is_active: boolean;
|
||||
password: string;
|
||||
}
|
@ -1,55 +1,78 @@
|
||||
// Layout for dashboard with responsive drawer
|
||||
|
||||
import { Link, NavLink, Navigate, 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 { styled, createTheme, ThemeProvider } from '@mui/material/styles';
|
||||
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 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 { Api, ExitToApp, Home, People, Settings, Shield } from "@mui/icons-material";
|
||||
import { getUserData, useAuthStore } from "../store/auth";
|
||||
import { UserData } from "../interfaces/auth";
|
||||
import Divider from '@mui/material/Divider';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Badge from '@mui/material/Badge';
|
||||
import Container from '@mui/material/Container';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
||||
import NotificationsIcon from '@mui/icons-material/Notifications';
|
||||
import { AccountCircle, Api, ExitToApp, Home, People, Settings, Shield } from '@mui/icons-material';
|
||||
import { ListItem, ListItemButton, ListItemIcon, ListItemText, colors } from '@mui/material';
|
||||
import { Outlet, useNavigate } from 'react-router-dom';
|
||||
import { UserData } from '../interfaces/auth';
|
||||
import { getUserData, useAuthStore } from '../store/auth';
|
||||
import { Theme, useTheme } from '@emotion/react';
|
||||
import AccountMenu from '../components/AccountMenu';
|
||||
|
||||
const drawerWidth = 240;
|
||||
const drawerWidth: number = 240;
|
||||
|
||||
export default function DashboardLayout() {
|
||||
const authStore = useAuthStore();
|
||||
const [userData, setUserData] = React.useState<UserData>();
|
||||
interface AppBarProps extends MuiAppBarProps {
|
||||
open?: boolean;
|
||||
}
|
||||
|
||||
const location = useLocation()
|
||||
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 navigate = useNavigate()
|
||||
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),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
//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 = [
|
||||
const pages = [
|
||||
{
|
||||
label: "Главная",
|
||||
path: "/",
|
||||
@ -70,72 +93,29 @@ export default function DashboardLayout() {
|
||||
path: "/api-test",
|
||||
icon: <Api />
|
||||
},
|
||||
]
|
||||
]
|
||||
|
||||
const misc = [
|
||||
{
|
||||
label: "Настройки",
|
||||
path: "/settings",
|
||||
icon: <Settings />
|
||||
},
|
||||
{
|
||||
label: "Выход",
|
||||
path: "/signOut",
|
||||
icon: <ExitToApp />
|
||||
}
|
||||
]
|
||||
export default function DashboardLayout() {
|
||||
const theme = useTheme()
|
||||
const innerTheme = createTheme(theme)
|
||||
|
||||
const drawer = (
|
||||
<div>
|
||||
<Toolbar>
|
||||
<Box>
|
||||
<Typography>{userData?.name} {userData?.surname}</Typography>
|
||||
<Divider />
|
||||
<Typography variant="caption">{userData?.login}</Typography>
|
||||
</Box>
|
||||
</Toolbar>
|
||||
const [open, setOpen] = React.useState(true);
|
||||
const toggleDrawer = () => {
|
||||
setOpen(!open);
|
||||
};
|
||||
|
||||
<Divider />
|
||||
const authStore = useAuthStore();
|
||||
|
||||
<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>
|
||||
const navigate = useNavigate()
|
||||
|
||||
<Divider />
|
||||
const getPageTitle = () => {
|
||||
const currentPath = location.pathname;
|
||||
const allPages = [...pages];
|
||||
const currentPage = allPages.find(page => page.path === currentPath);
|
||||
return currentPage ? currentPage.label : "Dashboard";
|
||||
};
|
||||
|
||||
<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>
|
||||
);
|
||||
const [userData, setUserData] = React.useState<UserData>();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (authStore) {
|
||||
@ -146,87 +126,112 @@ export default function DashboardLayout() {
|
||||
}
|
||||
}, [authStore])
|
||||
|
||||
const getPageTitle = () => {
|
||||
const currentPath = location.pathname;
|
||||
const allPages = [...pages, ...misc];
|
||||
const currentPage = allPages.find(page => page.path === currentPath);
|
||||
return currentPage ? currentPage.label : "Dashboard";
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
height: "100vh"
|
||||
}}>
|
||||
<ThemeProvider theme={innerTheme}>
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<CssBaseline />
|
||||
<AppBar
|
||||
position="fixed"
|
||||
<AppBar position="absolute" open={open}>
|
||||
<Toolbar
|
||||
sx={{
|
||||
width: { sm: `calc(100% - ${drawerWidth}px)` },
|
||||
ml: { sm: `${drawerWidth}px` },
|
||||
pr: '24px', // keep right padding when drawer closed
|
||||
}}
|
||||
>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
edge="start"
|
||||
onClick={handleDrawerToggle}
|
||||
sx={{ mr: 2, display: { sm: 'none' } }}
|
||||
onClick={toggleDrawer}
|
||||
sx={{
|
||||
marginRight: '36px',
|
||||
...(open && { display: 'none' }),
|
||||
}}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" noWrap component="div">
|
||||
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h6"
|
||||
color="inherit"
|
||||
noWrap
|
||||
sx={{ flexGrow: 1 }}
|
||||
>
|
||||
{getPageTitle()}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: "flex", gap: "8px" }}>
|
||||
<Box>
|
||||
<Typography>{userData?.name} {userData?.surname}</Typography>
|
||||
<Divider />
|
||||
<Typography variant="caption">{userData?.login}</Typography>
|
||||
</Box>
|
||||
|
||||
{/* <IconButton color="inherit">
|
||||
<Badge badgeContent={0} color="secondary">
|
||||
<AccountCircle />
|
||||
</Badge>
|
||||
</IconButton> */}
|
||||
|
||||
<AccountMenu/>
|
||||
</Box>
|
||||
|
||||
</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.
|
||||
}}
|
||||
<Drawer variant="permanent" open={open}>
|
||||
<Toolbar
|
||||
sx={{
|
||||
display: { xs: 'block', sm: 'none' },
|
||||
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
px: [1],
|
||||
}}
|
||||
>
|
||||
{drawer}
|
||||
</Drawer>
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
display: { xs: 'none', sm: 'block' },
|
||||
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
|
||||
}}
|
||||
open
|
||||
>
|
||||
{drawer}
|
||||
</Drawer>
|
||||
<Box display="flex" justifyContent={'space-between'} width={"100%"}>
|
||||
<IconButton onClick={toggleDrawer}>
|
||||
<ChevronLeftIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Toolbar>
|
||||
<Divider />
|
||||
|
||||
<List component="nav">
|
||||
{pages.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>
|
||||
{item.icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={item.label} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Drawer>
|
||||
|
||||
<Box
|
||||
component="main"
|
||||
sx={{
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.mode === 'light'
|
||||
? theme.palette.grey[100]
|
||||
: theme.palette.grey[900],
|
||||
flexGrow: 1,
|
||||
p: 3,
|
||||
width: { sm: `calc(100% - ${drawerWidth}px)` }
|
||||
maxHeight: "100vh",
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<Toolbar />
|
||||
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
|
||||
<Outlet />
|
||||
</Container>
|
||||
</Box>
|
||||
</Box>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
252
frontend_reactjs/src/layouts/DashboardLayoutResponsive.tsx
Normal file
252
frontend_reactjs/src/layouts/DashboardLayoutResponsive.tsx
Normal file
@ -0,0 +1,252 @@
|
||||
// Layout for dashboard with responsive drawer
|
||||
|
||||
import { Link, NavLink, Navigate, 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>
|
||||
);
|
||||
}
|
@ -6,6 +6,9 @@ import { registerSW } from 'virtual:pwa-register'
|
||||
import { ThemeProvider } from '@emotion/react'
|
||||
import { createTheme } from '@mui/material'
|
||||
import { ruRU } from '@mui/material/locale'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const theme = createTheme(
|
||||
{
|
||||
@ -30,7 +33,9 @@ const updateSW = registerSW({
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<ThemeProvider theme={theme}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App />
|
||||
</QueryClientProvider>
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
|
@ -1,43 +1,32 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { Button } from "@mui/material"
|
||||
import DataTable from "../components/DataTable"
|
||||
import { GridColDef } from "@mui/x-data-grid"
|
||||
import UserService from "../services/UserService"
|
||||
import { Button, Typography } from "@mui/material"
|
||||
|
||||
export default function ApiTest() {
|
||||
const [users, setUsers] = useState<any>(null)
|
||||
const [data, setData] = useState<any>(null)
|
||||
|
||||
const getUsers = async () => {
|
||||
await UserService.getUsers().then(response => {
|
||||
setUsers(response.data)
|
||||
})
|
||||
function getRealtimeData(data: any) {
|
||||
setData(data)
|
||||
}
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{ field: 'id', headerName: 'ID', type: "number", width: 70 },
|
||||
{ field: 'email', headerName: 'Email', width: 130 },
|
||||
{ field: 'login', headerName: 'Логин', width: 130 },
|
||||
{ field: 'phone', headerName: 'Телефон', width: 90 },
|
||||
{ field: 'name', headerName: 'Имя', width: 90 },
|
||||
{ field: 'surname', headerName: 'Фамилия', width: 90 },
|
||||
{ field: 'is_active', headerName: 'Активен', type: "boolean", width: 90 },
|
||||
{
|
||||
field: 'role_id',
|
||||
headerName: 'Роль',
|
||||
valueGetter: (value, row) => `${value}`,
|
||||
width: 90
|
||||
},
|
||||
];
|
||||
useEffect(() => {
|
||||
const sse = new EventSource(`${import.meta.env.VITE_API_SSE_URL}/stream`)
|
||||
|
||||
sse.onmessage = e => getRealtimeData(e.data)
|
||||
|
||||
sse.onerror = () => {
|
||||
sse.close()
|
||||
}
|
||||
|
||||
return () => {
|
||||
sse.close()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => getUsers()}>
|
||||
Get users
|
||||
</Button>
|
||||
|
||||
{users &&
|
||||
<DataTable rows={users} columns={columns}/>
|
||||
}
|
||||
<Typography>
|
||||
{JSON.stringify(data)}
|
||||
</Typography>
|
||||
</>
|
||||
)
|
||||
}
|
@ -32,7 +32,8 @@ function Roles() {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
gap: '16px'
|
||||
gap: '16px',
|
||||
flexGrow: 1
|
||||
}}>
|
||||
<Button onClick={() => console.log("TODO: Add")}>
|
||||
Добавить роль
|
||||
|
@ -1,43 +1,62 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { Box, Button } from "@mui/material"
|
||||
import { Box, Button, CircularProgress } from "@mui/material"
|
||||
import DataTable from "../components/DataTable"
|
||||
import { GridColDef } from "@mui/x-data-grid"
|
||||
import UserService from "../services/UserService"
|
||||
import { useRoles, useUsers } from "../hooks/swrHooks"
|
||||
import { Role } from "../interfaces/auth"
|
||||
import { useState } from "react"
|
||||
import CreateUserModal from "../components/modals/CreateUserModal"
|
||||
|
||||
export default function Users() {
|
||||
const [users, setUsers] = useState<any>(null)
|
||||
const { users, isError, isLoading } = useUsers()
|
||||
|
||||
const getUsers = async () => {
|
||||
await UserService.getUsers().then(response => {
|
||||
setUsers(response.data)
|
||||
})
|
||||
}
|
||||
const { roles } = useRoles()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{ field: 'id', headerName: 'ID', type: "number", width: 70 },
|
||||
{ field: 'email', headerName: 'Email', width: 130 },
|
||||
{ field: 'login', headerName: 'Логин', width: 130 },
|
||||
{ field: 'phone', headerName: 'Телефон', width: 90 },
|
||||
{ field: 'name', headerName: 'Имя', width: 90 },
|
||||
{ field: 'surname', headerName: 'Фамилия', width: 90 },
|
||||
{ field: 'is_active', headerName: 'Активен', type: "boolean", width: 90 },
|
||||
{ field: 'email', headerName: 'Email', width: 130, editable: true},
|
||||
{ field: 'login', headerName: 'Логин', width: 130, editable: true },
|
||||
{ field: 'phone', headerName: 'Телефон', width: 90, editable: true },
|
||||
{ field: 'name', headerName: 'Имя', width: 90, editable: true },
|
||||
{ field: 'surname', headerName: 'Фамилия', width: 90, editable: true },
|
||||
{ field: 'is_active', headerName: 'Активен', type: "boolean", width: 90, editable: true},
|
||||
{
|
||||
field: 'role_id',
|
||||
headerName: 'Роль',
|
||||
valueGetter: (value, row) => `${value}`,
|
||||
valueGetter: (value, row) => {
|
||||
if (roles) {
|
||||
const roleName = roles.find((role: Role) => role.id === value).name
|
||||
return roleName
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
},
|
||||
width: 90
|
||||
},
|
||||
];
|
||||
|
||||
if (isError) return <div>Произошла ошибка при получении данных.</div>
|
||||
if (isLoading) return <CircularProgress />
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Button onClick={() => getUsers()}>
|
||||
Get users
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
gap: "16px",
|
||||
}}
|
||||
>
|
||||
<Button onClick={() => setOpen(true)}>
|
||||
Добавить пользователя
|
||||
</Button>
|
||||
|
||||
{users &&
|
||||
<DataTable rows={users} columns={columns}/>
|
||||
}
|
||||
<CreateUserModal
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
/>
|
||||
|
||||
<DataTable rows={users} columns={columns} />
|
||||
</Box>
|
||||
)
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||
import { TextField, Button, Container, Typography, Box } from '@mui/material';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { SignInFormData, ApiResponse } from '../../interfaces/auth';
|
||||
import { ApiResponse, LoginFormData } from '../../interfaces/auth';
|
||||
import { login, setUserData } from '../../store/auth';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import AuthService from '../../services/AuthService';
|
||||
import UserService from '../../services/UserService';
|
||||
|
||||
const SignIn = () => {
|
||||
const { register, handleSubmit, formState: { errors } } = useForm<SignInFormData>({
|
||||
const { register, handleSubmit, formState: { errors } } = useForm<LoginFormData>({
|
||||
defaultValues: {
|
||||
username: '',
|
||||
password: '',
|
||||
@ -21,10 +21,10 @@ const SignIn = () => {
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onSubmit: SubmitHandler<SignInFormData> = async (data) => {
|
||||
const onSubmit: SubmitHandler<LoginFormData> = async (data) => {
|
||||
const formBody = new URLSearchParams();
|
||||
for (const key in data) {
|
||||
formBody.append(key, data[key as keyof SignInFormData] as string);
|
||||
formBody.append(key, data[key as keyof LoginFormData] as string);
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||
import { TextField, Button, Container, Typography, Box } from '@mui/material';
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { SignUpFormData, ApiResponse } from '../../interfaces/auth';
|
||||
import axiosInstance from '../../http/axiosInstance';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { ApiResponse } from '../../interfaces/auth';
|
||||
import UserService from '../../services/UserService';
|
||||
import { CreateUserFormData } from '../../interfaces/user';
|
||||
|
||||
const SignUp = () => {
|
||||
const { register, handleSubmit, formState: { errors } } = useForm<SignUpFormData>({
|
||||
const { register, handleSubmit, formState: { errors } } = useForm<CreateUserFormData>({
|
||||
defaultValues: {
|
||||
email: '',
|
||||
login: '',
|
||||
@ -19,7 +19,7 @@ const SignUp = () => {
|
||||
})
|
||||
|
||||
|
||||
const onSubmit: SubmitHandler<SignUpFormData> = async (data) => {
|
||||
const onSubmit: SubmitHandler<CreateUserFormData> = async (data) => {
|
||||
try {
|
||||
const response: AxiosResponse<ApiResponse> = await UserService.createUser(data)
|
||||
console.log('Успешная регистрация:', response.data);
|
||||
|
@ -1,11 +1,21 @@
|
||||
import axiosInstance from "../http/axiosInstance";
|
||||
import { Role, RoleCreate } from "../interfaces/auth";
|
||||
|
||||
export default class RoleService {
|
||||
static async getRoles() {
|
||||
return await axiosInstance.get(`/auth/roles`)
|
||||
}
|
||||
|
||||
static async createRole(data: RoleCreate) {
|
||||
return await axiosInstance.post(`/auth/roles/`, data)
|
||||
}
|
||||
|
||||
|
||||
static async getRoleById(id: number) {
|
||||
return await axiosInstance.get(`/auth/roles/${id}`)
|
||||
}
|
||||
|
||||
// static async deleteRole(id: number) {
|
||||
// return await axiosInstance.delete(`/auth/roles/${id}`)
|
||||
// }
|
||||
}
|
@ -1022,7 +1022,7 @@
|
||||
resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz"
|
||||
integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==
|
||||
|
||||
"@emotion/react@^11.0.0-rc.0", "@emotion/react@^11.11.4", "@emotion/react@^11.4.1", "@emotion/react@^11.5.0":
|
||||
"@emotion/react@^11.0.0-rc.0", "@emotion/react@^11.11.4", "@emotion/react@^11.4.1", "@emotion/react@^11.5.0", "@emotion/react@^11.9.0":
|
||||
version "11.11.4"
|
||||
resolved "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz"
|
||||
integrity sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==
|
||||
@ -1052,7 +1052,7 @@
|
||||
resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz"
|
||||
integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==
|
||||
|
||||
"@emotion/styled@^11.11.5", "@emotion/styled@^11.3.0":
|
||||
"@emotion/styled@^11.11.5", "@emotion/styled@^11.3.0", "@emotion/styled@^11.8.1":
|
||||
version "11.11.5"
|
||||
resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz"
|
||||
integrity sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==
|
||||
@ -1219,7 +1219,7 @@
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@mui/base@5.0.0-beta.40":
|
||||
"@mui/base@^5.0.0-beta.40", "@mui/base@5.0.0-beta.40":
|
||||
version "5.0.0-beta.40"
|
||||
resolved "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz"
|
||||
integrity sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==
|
||||
@ -1310,6 +1310,25 @@
|
||||
prop-types "^15.8.1"
|
||||
react-is "^18.2.0"
|
||||
|
||||
"@mui/x-charts@^7.7.1":
|
||||
version "7.7.1"
|
||||
resolved "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.7.1.tgz"
|
||||
integrity sha512-qUvkGGxBukHXLUqoTUs2rJZz1t+gK0P+bZzQWFbxN93XQyAhrjrOU8VN0x+G7nO3qcXrC2P6s0nevms7ZRSDrA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.24.7"
|
||||
"@mui/base" "^5.0.0-beta.40"
|
||||
"@mui/system" "^5.15.20"
|
||||
"@mui/utils" "^5.15.20"
|
||||
"@react-spring/rafz" "^9.7.3"
|
||||
"@react-spring/web" "^9.7.3"
|
||||
clsx "^2.1.1"
|
||||
d3-color "^3.1.0"
|
||||
d3-delaunay "^6.0.4"
|
||||
d3-interpolate "^3.0.1"
|
||||
d3-scale "^4.0.2"
|
||||
d3-shape "^3.2.0"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
"@mui/x-data-grid@^7.7.1":
|
||||
version "7.7.1"
|
||||
resolved "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.7.1.tgz"
|
||||
@ -1353,6 +1372,50 @@
|
||||
resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz"
|
||||
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||
|
||||
"@react-spring/animated@~9.7.3":
|
||||
version "9.7.3"
|
||||
resolved "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz"
|
||||
integrity sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==
|
||||
dependencies:
|
||||
"@react-spring/shared" "~9.7.3"
|
||||
"@react-spring/types" "~9.7.3"
|
||||
|
||||
"@react-spring/core@~9.7.3":
|
||||
version "9.7.3"
|
||||
resolved "https://registry.npmjs.org/@react-spring/core/-/core-9.7.3.tgz"
|
||||
integrity sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==
|
||||
dependencies:
|
||||
"@react-spring/animated" "~9.7.3"
|
||||
"@react-spring/shared" "~9.7.3"
|
||||
"@react-spring/types" "~9.7.3"
|
||||
|
||||
"@react-spring/rafz@^9.7.3":
|
||||
version "9.7.3"
|
||||
resolved "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.3.tgz"
|
||||
integrity sha512-9vzW1zJPcC4nS3aCV+GgcsK/WLaB520Iyvm55ARHfM5AuyBqycjvh1wbmWmgCyJuX4VPoWigzemq1CaaeRSHhQ==
|
||||
|
||||
"@react-spring/shared@~9.7.3":
|
||||
version "9.7.3"
|
||||
resolved "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.3.tgz"
|
||||
integrity sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==
|
||||
dependencies:
|
||||
"@react-spring/types" "~9.7.3"
|
||||
|
||||
"@react-spring/types@~9.7.3":
|
||||
version "9.7.3"
|
||||
resolved "https://registry.npmjs.org/@react-spring/types/-/types-9.7.3.tgz"
|
||||
integrity sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw==
|
||||
|
||||
"@react-spring/web@^9.7.3":
|
||||
version "9.7.3"
|
||||
resolved "https://registry.npmjs.org/@react-spring/web/-/web-9.7.3.tgz"
|
||||
integrity sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==
|
||||
dependencies:
|
||||
"@react-spring/animated" "~9.7.3"
|
||||
"@react-spring/core" "~9.7.3"
|
||||
"@react-spring/shared" "~9.7.3"
|
||||
"@react-spring/types" "~9.7.3"
|
||||
|
||||
"@remix-run/router@1.16.1":
|
||||
version "1.16.1"
|
||||
resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz"
|
||||
@ -1464,6 +1527,18 @@
|
||||
dependencies:
|
||||
"@swc/counter" "^0.1.3"
|
||||
|
||||
"@tanstack/query-core@5.45.0":
|
||||
version "5.45.0"
|
||||
resolved "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.45.0.tgz"
|
||||
integrity sha512-RVfIZQmFUTdjhSAAblvueimfngYyfN6HlwaJUPK71PKd7yi43Vs1S/rdimmZedPWX/WGppcq/U1HOj7O7FwYxw==
|
||||
|
||||
"@tanstack/react-query@^5.45.1":
|
||||
version "5.45.1"
|
||||
resolved "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.45.1.tgz"
|
||||
integrity sha512-mYYfJujKg2kxmkRRjA6nn4YKG3ITsKuH22f1kteJ5IuVQqgKUgbaSQfYwVP0gBS05mhwxO03HVpD0t7BMN7WOA==
|
||||
dependencies:
|
||||
"@tanstack/query-core" "5.45.0"
|
||||
|
||||
"@types/estree@^1.0.0", "@types/estree@1.0.5":
|
||||
version "1.0.5"
|
||||
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz"
|
||||
@ -1904,6 +1979,11 @@ chokidar@^3.5.3:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
client-only@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz"
|
||||
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
|
||||
|
||||
clsx@^2.1.0, clsx@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz"
|
||||
@ -2012,6 +2092,74 @@ csstype@^3.0.2, csstype@^3.1.3:
|
||||
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz"
|
||||
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
|
||||
|
||||
"d3-array@2 - 3", "d3-array@2.10.0 - 3":
|
||||
version "3.2.4"
|
||||
resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz"
|
||||
integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
|
||||
dependencies:
|
||||
internmap "1 - 2"
|
||||
|
||||
d3-color@^3.1.0, "d3-color@1 - 3":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz"
|
||||
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
|
||||
|
||||
d3-delaunay@^6.0.4:
|
||||
version "6.0.4"
|
||||
resolved "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz"
|
||||
integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==
|
||||
dependencies:
|
||||
delaunator "5"
|
||||
|
||||
"d3-format@1 - 3":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz"
|
||||
integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
|
||||
|
||||
d3-interpolate@^3.0.1, "d3-interpolate@1.2.0 - 3":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz"
|
||||
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
|
||||
dependencies:
|
||||
d3-color "1 - 3"
|
||||
|
||||
d3-path@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz"
|
||||
integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
|
||||
|
||||
d3-scale@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz"
|
||||
integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
|
||||
dependencies:
|
||||
d3-array "2.10.0 - 3"
|
||||
d3-format "1 - 3"
|
||||
d3-interpolate "1.2.0 - 3"
|
||||
d3-time "2.1.1 - 3"
|
||||
d3-time-format "2 - 4"
|
||||
|
||||
d3-shape@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz"
|
||||
integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==
|
||||
dependencies:
|
||||
d3-path "^3.1.0"
|
||||
|
||||
"d3-time-format@2 - 4":
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz"
|
||||
integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
|
||||
dependencies:
|
||||
d3-time "1 - 3"
|
||||
|
||||
"d3-time@1 - 3", "d3-time@2.1.1 - 3":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz"
|
||||
integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
|
||||
dependencies:
|
||||
d3-array "2 - 3"
|
||||
|
||||
data-view-buffer@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz"
|
||||
@ -2074,6 +2222,13 @@ define-properties@^1.2.0, define-properties@^1.2.1:
|
||||
has-property-descriptors "^1.0.0"
|
||||
object-keys "^1.1.1"
|
||||
|
||||
delaunator@5:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz"
|
||||
integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==
|
||||
dependencies:
|
||||
robust-predicates "^3.0.2"
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
|
||||
@ -2759,6 +2914,11 @@ internal-slot@^1.0.7:
|
||||
hasown "^2.0.0"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
"internmap@1 - 2":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz"
|
||||
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
|
||||
|
||||
is-array-buffer@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz"
|
||||
@ -3463,7 +3623,7 @@ randombytes@^2.1.0:
|
||||
dependencies:
|
||||
safe-buffer "^5.1.0"
|
||||
|
||||
"react-dom@^17.0.0 || ^18.0.0", react-dom@^18.2.0, react-dom@>=16.6.0, react-dom@>=16.8, react-dom@>=16.8.0:
|
||||
"react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^17.0.0 || ^18.0.0", react-dom@^18.2.0, react-dom@>=16.6.0, react-dom@>=16.8, react-dom@>=16.8.0:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
|
||||
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
|
||||
@ -3516,7 +3676,7 @@ react-transition-group@^4.4.5:
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
"react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^17.0.0 || ^18.0.0", react@^18.2.0, react@^18.3.1, react@>=16.6.0, react@>=16.8, react@>=16.8.0:
|
||||
"react@^16.11.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^17.0.0 || ^18.0.0", react@^18.0.0, react@^18.2.0, react@^18.3.1, react@>=16.6.0, react@>=16.8, react@>=16.8.0:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
|
||||
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
|
||||
@ -3626,6 +3786,11 @@ rimraf@^3.0.2:
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
robust-predicates@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz"
|
||||
integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==
|
||||
|
||||
"rollup@^1.20.0 || ^2.0.0", rollup@^1.20.0||^2.0.0, rollup@^2.43.1:
|
||||
version "2.79.1"
|
||||
resolved "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz"
|
||||
@ -3957,6 +4122,14 @@ supports-preserve-symlinks-flag@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
swr@^2.2.5:
|
||||
version "2.2.5"
|
||||
resolved "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz"
|
||||
integrity sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==
|
||||
dependencies:
|
||||
client-only "^0.0.1"
|
||||
use-sync-external-store "^1.2.0"
|
||||
|
||||
tailwindcss@^3.4.4:
|
||||
version "3.4.4"
|
||||
resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz"
|
||||
@ -4189,7 +4362,7 @@ uri-js@^4.2.2, uri-js@^4.4.1:
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
use-sync-external-store@1.2.0:
|
||||
use-sync-external-store@^1.2.0, use-sync-external-store@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"
|
||||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
||||
|
Reference in New Issue
Block a user