From c41e59cd864de84902bee4a40cd53799682d10ad Mon Sep 17 00:00:00 2001 From: cracklesparkle Date: Thu, 27 Jun 2024 17:32:12 +0900 Subject: [PATCH] DashboardLayout changes, refactoring, useSWR --- frontend_reactjs/package-lock.json | 273 ++++++++++++ frontend_reactjs/package.json | 3 + .../src/components/AccountMenu.tsx | 96 +++++ frontend_reactjs/src/components/DataTable.tsx | 29 +- .../src/components/modals/CreateRoleModal.tsx | 11 + .../src/components/modals/CreateUserModal.tsx | 141 ++++++ frontend_reactjs/src/hooks/swrHooks.ts | 23 + frontend_reactjs/src/interfaces/auth.ts | 24 +- frontend_reactjs/src/interfaces/user.ts | 9 + .../src/layouts/DashboardLayout.tsx | 405 +++++++++--------- .../src/layouts/DashboardLayoutResponsive.tsx | 252 +++++++++++ frontend_reactjs/src/main.tsx | 7 +- frontend_reactjs/src/pages/ApiTest.tsx | 51 +-- frontend_reactjs/src/pages/Roles.tsx | 3 +- frontend_reactjs/src/pages/Users.tsx | 63 ++- frontend_reactjs/src/pages/auth/SignIn.tsx | 8 +- frontend_reactjs/src/pages/auth/SignUp.tsx | 10 +- frontend_reactjs/src/services/RoleService.ts | 10 + frontend_reactjs/yarn.lock | 185 +++++++- 19 files changed, 1309 insertions(+), 294 deletions(-) create mode 100644 frontend_reactjs/src/components/AccountMenu.tsx create mode 100644 frontend_reactjs/src/components/modals/CreateRoleModal.tsx create mode 100644 frontend_reactjs/src/components/modals/CreateUserModal.tsx create mode 100644 frontend_reactjs/src/hooks/swrHooks.ts create mode 100644 frontend_reactjs/src/interfaces/user.ts create mode 100644 frontend_reactjs/src/layouts/DashboardLayoutResponsive.tsx diff --git a/frontend_reactjs/package-lock.json b/frontend_reactjs/package-lock.json index 9847a4a..1aaf9c7 100644 --- a/frontend_reactjs/package-lock.json +++ b/frontend_reactjs/package-lock.json @@ -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", diff --git a/frontend_reactjs/package.json b/frontend_reactjs/package.json index 382f586..f709ff6 100644 --- a/frontend_reactjs/package.json +++ b/frontend_reactjs/package.json @@ -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" }, diff --git a/frontend_reactjs/src/components/AccountMenu.tsx b/frontend_reactjs/src/components/AccountMenu.tsx new file mode 100644 index 0000000..ec24232 --- /dev/null +++ b/frontend_reactjs/src/components/AccountMenu.tsx @@ -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); + const open = Boolean(anchorEl); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + return ( + + + + + + + + + + + Профиль + + + + + + + + + Настройки + + + + + + Выход + + + + ); +} \ No newline at end of file diff --git a/frontend_reactjs/src/components/DataTable.tsx b/frontend_reactjs/src/components/DataTable.tsx index 219a736..d0611e2 100644 --- a/frontend_reactjs/src/components/DataTable.tsx +++ b/frontend_reactjs/src/components/DataTable.tsx @@ -6,19 +6,22 @@ interface Props { } export default function DataTable(props: Props) { + return ( -
- -
+ ); } \ No newline at end of file diff --git a/frontend_reactjs/src/components/modals/CreateRoleModal.tsx b/frontend_reactjs/src/components/modals/CreateRoleModal.tsx new file mode 100644 index 0000000..b9181a6 --- /dev/null +++ b/frontend_reactjs/src/components/modals/CreateRoleModal.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +const CreateRoleModal = () => { + return ( +
+ +
+ ) +} + +export default CreateRoleModal \ No newline at end of file diff --git a/frontend_reactjs/src/components/modals/CreateUserModal.tsx b/frontend_reactjs/src/components/modals/CreateUserModal.tsx new file mode 100644 index 0000000..9a5be7e --- /dev/null +++ b/frontend_reactjs/src/components/modals/CreateUserModal.tsx @@ -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({ + defaultValues: { + email: '', + login: '', + phone: '', + name: '', + surname: '', + is_active: true, + password: '', + } + }) + + const onSubmit: SubmitHandler = async (data) => { + try { + const response: AxiosResponse = await UserService.createUser(data) + console.log(response.data) + } catch (error) { + console.error(error) + } + } + + return ( + setOpen(false)} + aria-labelledby="modal-modal-title" + aria-describedby="modal-modal-description" + > +
+ + + + Создание пользователя + + + + + + + + + + + + + + + + + + + + +
+
+ ) +} \ No newline at end of file diff --git a/frontend_reactjs/src/hooks/swrHooks.ts b/frontend_reactjs/src/hooks/swrHooks.ts new file mode 100644 index 0000000..c0dbb6a --- /dev/null +++ b/frontend_reactjs/src/hooks/swrHooks.ts @@ -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 + } +} \ No newline at end of file diff --git a/frontend_reactjs/src/interfaces/auth.ts b/frontend_reactjs/src/interfaces/auth.ts index c4ac87f..7a7443b 100644 --- a/frontend_reactjs/src/interfaces/auth.ts +++ b/frontend_reactjs/src/interfaces/auth.ts @@ -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; diff --git a/frontend_reactjs/src/interfaces/user.ts b/frontend_reactjs/src/interfaces/user.ts new file mode 100644 index 0000000..7dcb41c --- /dev/null +++ b/frontend_reactjs/src/interfaces/user.ts @@ -0,0 +1,9 @@ +export interface CreateUserFormData { + email: string; + login: string; + phone: string; + name: string; + surname: string; + is_active: boolean; + password: string; +} \ No newline at end of file diff --git a/frontend_reactjs/src/layouts/DashboardLayout.tsx b/frontend_reactjs/src/layouts/DashboardLayout.tsx index 350624b..c390f1d 100644 --- a/frontend_reactjs/src/layouts/DashboardLayout.tsx +++ b/frontend_reactjs/src/layouts/DashboardLayout.tsx @@ -1,141 +1,121 @@ -// 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; + +interface AppBarProps extends MuiAppBarProps { + open?: boolean; +} + +const AppBar = styled(MuiAppBar, { + shouldForwardProp: (prop) => prop !== 'open', +})(({ 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), + }, + }), + }, + }), +); + +const pages = [ + { + label: "Главная", + path: "/", + icon: + }, + { + label: "Пользователи", + path: "/user", + icon: + }, + { + label: "Роли", + path: "/role", + icon: + }, + { + label: "API Test", + path: "/api-test", + icon: + }, +] export default function DashboardLayout() { - const authStore = useAuthStore(); - const [userData, setUserData] = React.useState(); + const theme = useTheme() + const innerTheme = createTheme(theme) - const location = useLocation() + const [open, setOpen] = React.useState(true); + const toggleDrawer = () => { + setOpen(!open); + }; + + const authStore = useAuthStore(); 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 getPageTitle = () => { + const currentPath = location.pathname; + const allPages = [...pages]; + const currentPage = allPages.find(page => page.path === currentPath); + return currentPage ? currentPage.label : "Dashboard"; }; - const handleDrawerTransitionEnd = () => { - setIsClosing(false); - }; - - const handleDrawerToggle = () => { - if (!isClosing) { - setMobileOpen(!mobileOpen); - } - }; - - const pages = [ - { - label: "Главная", - path: "/", - icon: - }, - { - label: "Пользователи", - path: "/user", - icon: - }, - { - label: "Роли", - path: "/role", - icon: - }, - { - label: "API Test", - path: "/api-test", - icon: - }, - ] - - const misc = [ - { - label: "Настройки", - path: "/settings", - icon: - }, - { - label: "Выход", - path: "/signOut", - icon: - } - ] - - const drawer = ( -
- - - {userData?.name} {userData?.surname} - - {userData?.login} - - - - - - - {pages.map((item, index) => ( - - { - navigate(item.path) - }} - selected={location.pathname === item.path} - > - - {item.icon} - - - - - ))} - - - - - - {misc.map((item, index) => ( - - { - navigate(item.path) - }} - selected={location.pathname === item.path} - > - - {item.icon} - - - - - ))} - -
- ); + const [userData, setUserData] = React.useState(); 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 ( - - - - - + + + + - - - - {getPageTitle()} - - - + + + - - {/* The implementation can be swapped with js to avoid SEO duplication of links. */} - + {getPageTitle()} + + + + + {userData?.name} {userData?.surname} + + {userData?.login} + + + {/* + + + + */} + + + + + + + + + + + + + + + + + + {pages.map((item, index) => ( + + { + navigate(item.path) + }} + style={{ background: location.pathname === item.path ? innerTheme.palette.action.selected : "transparent" }} + selected={location.pathname === item.path} + > + + {item.icon} + + + + + ))} + + + + + theme.palette.mode === 'light' + ? theme.palette.grey[100] + : theme.palette.grey[900], + flexGrow: 1, + maxHeight: "100vh", + overflow: 'auto', }} > - {drawer} - - - {drawer} - + + + + + - - - - - - + ); } \ No newline at end of file diff --git a/frontend_reactjs/src/layouts/DashboardLayoutResponsive.tsx b/frontend_reactjs/src/layouts/DashboardLayoutResponsive.tsx new file mode 100644 index 0000000..66f610a --- /dev/null +++ b/frontend_reactjs/src/layouts/DashboardLayoutResponsive.tsx @@ -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(); + + 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: + }, + { + label: "Пользователи", + path: "/user", + icon: + }, + { + label: "Роли", + path: "/role", + icon: + }, + { + label: "API Test", + path: "/api-test", + icon: + }, + ] + + const misc = [ + { + label: "Настройки", + path: "/settings", + icon: + }, + { + label: "Выход", + path: "/signOut", + icon: + } + ] + + 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 = ( +
+ + + + {userData?.name} {userData?.surname} + + {userData?.login} + + + + + + + + + + + + {pages.map((item, index) => ( + + { + navigate(item.path) + }} + selected={location.pathname === item.path} + > + + {item.icon} + + + + + ))} + + + + + + {misc.map((item, index) => ( + + { + navigate(item.path) + }} + selected={location.pathname === item.path} + > + + {item.icon} + + + + + ))} + +
+ ); + + React.useEffect(() => { + if (authStore) { + const stored = getUserData() + if (stored) { + setUserData(stored) + } + } + }, [authStore]) + + return ( + + + + + + + + + {getPageTitle()} + + + + + + {/* The implementation can be swapped with js to avoid SEO duplication of links. */} + + {drawer} + + + {drawer} + + + + + + + + + ); +} \ No newline at end of file diff --git a/frontend_reactjs/src/main.tsx b/frontend_reactjs/src/main.tsx index d66dc94..ba4508e 100644 --- a/frontend_reactjs/src/main.tsx +++ b/frontend_reactjs/src/main.tsx @@ -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( - + + + , ) diff --git a/frontend_reactjs/src/pages/ApiTest.tsx b/frontend_reactjs/src/pages/ApiTest.tsx index 9b1eba3..c6b5445 100644 --- a/frontend_reactjs/src/pages/ApiTest.tsx +++ b/frontend_reactjs/src/pages/ApiTest.tsx @@ -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(null) + const [data, setData] = useState(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 ( <> - - - {users && - - } + + {JSON.stringify(data)} + ) } \ No newline at end of file diff --git a/frontend_reactjs/src/pages/Roles.tsx b/frontend_reactjs/src/pages/Roles.tsx index a7ff8e4..b918e6c 100644 --- a/frontend_reactjs/src/pages/Roles.tsx +++ b/frontend_reactjs/src/pages/Roles.tsx @@ -32,7 +32,8 @@ function Roles() { display: 'flex', flexDirection: 'column', alignItems: 'flex-start', - gap: '16px' + gap: '16px', + flexGrow: 1 }}> - {users && - - } + + + ) } \ No newline at end of file diff --git a/frontend_reactjs/src/pages/auth/SignIn.tsx b/frontend_reactjs/src/pages/auth/SignIn.tsx index bb4ebb7..f088c27 100644 --- a/frontend_reactjs/src/pages/auth/SignIn.tsx +++ b/frontend_reactjs/src/pages/auth/SignIn.tsx @@ -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({ + const { register, handleSubmit, formState: { errors } } = useForm({ defaultValues: { username: '', password: '', @@ -21,10 +21,10 @@ const SignIn = () => { const navigate = useNavigate(); - const onSubmit: SubmitHandler = async (data) => { + const onSubmit: SubmitHandler = 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 { diff --git a/frontend_reactjs/src/pages/auth/SignUp.tsx b/frontend_reactjs/src/pages/auth/SignUp.tsx index 6f81336..ee9aa14 100644 --- a/frontend_reactjs/src/pages/auth/SignUp.tsx +++ b/frontend_reactjs/src/pages/auth/SignUp.tsx @@ -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({ + const { register, handleSubmit, formState: { errors } } = useForm({ defaultValues: { email: '', login: '', @@ -19,7 +19,7 @@ const SignUp = () => { }) - const onSubmit: SubmitHandler = async (data) => { + const onSubmit: SubmitHandler = async (data) => { try { const response: AxiosResponse = await UserService.createUser(data) console.log('Успешная регистрация:', response.data); diff --git a/frontend_reactjs/src/services/RoleService.ts b/frontend_reactjs/src/services/RoleService.ts index 0dc0bc4..6d10efa 100644 --- a/frontend_reactjs/src/services/RoleService.ts +++ b/frontend_reactjs/src/services/RoleService.ts @@ -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}`) + // } } \ No newline at end of file diff --git a/frontend_reactjs/yarn.lock b/frontend_reactjs/yarn.lock index 9b263b4..519468d 100644 --- a/frontend_reactjs/yarn.lock +++ b/frontend_reactjs/yarn.lock @@ -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==