From 2c04d11ff94f35ec55027ead9f40e2c778173a93 Mon Sep 17 00:00:00 2001 From: Max Ivanov Date: Wed, 10 Jul 2024 10:25:28 +0200 Subject: [PATCH 01/11] saving --- dashboard/src/api/hooks/useGetProjects.ts | 25 ++++++++++++ dashboard/src/api/mocks/projects.mock.ts | 4 +- dashboard/src/constants/env.ts | 50 +++++++++++++++++++++++ dashboard/src/interfaces/projects.ts | 2 +- dashboard/src/views/Projects/index.tsx | 8 ++-- 5 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 dashboard/src/api/hooks/useGetProjects.ts create mode 100644 dashboard/src/constants/env.ts diff --git a/dashboard/src/api/hooks/useGetProjects.ts b/dashboard/src/api/hooks/useGetProjects.ts new file mode 100644 index 0000000..5021da1 --- /dev/null +++ b/dashboard/src/api/hooks/useGetProjects.ts @@ -0,0 +1,25 @@ +import { useQuery } from '@tanstack/react-query'; +import { ENV } from '../../constants/env'; +import { GetProjectsResponse } from '../../interfaces/projects'; +import './useGetApplicationCategories.mock'; + + +// Get application categories that have been defined on the license +const getApplicationCategories = async () => { + const response = await client.get( + `/device-monitor/v1/admin/settings/application-categories`, + { + // TODO: replave with the real one + baseURL: ENV.MOCKED, + }, + ); + + return response.data; +}; + +export const useGetApplicationCategories = () => { + return useQuery( + ['device-monitor', 'v1', 'admin', 'settings', 'application-categories'], + () => getApplicationCategories(), + ); +}; diff --git a/dashboard/src/api/mocks/projects.mock.ts b/dashboard/src/api/mocks/projects.mock.ts index 390f352..77fb499 100644 --- a/dashboard/src/api/mocks/projects.mock.ts +++ b/dashboard/src/api/mocks/projects.mock.ts @@ -1,6 +1,6 @@ -import { getProjectsResponse } from "../../interfaces/projects"; +import { GetProjectsResponse } from "../../interfaces/projects"; -export const mockedProjectsResponse: getProjectsResponse = { +export const mockedProjectsResponse: GetProjectsResponse = { projects: [ { id: 1, diff --git a/dashboard/src/constants/env.ts b/dashboard/src/constants/env.ts new file mode 100644 index 0000000..9e14452 --- /dev/null +++ b/dashboard/src/constants/env.ts @@ -0,0 +1,50 @@ +interface Environment { + MOCKED: boolean; +} + +const ENV: Environment = { + MOCKED: false, +}; + +// eslint-disable-next-line +const covertToType = (value: any) => { + if (value === "true") { + return true; + } + + if (value === "false") { + return false; + } + + if (value === "null") { + return null; + } + + if (value === "undefined") { + return undefined; + } + + if (!isNaN(value)) { + return Number(value); + } + + return value; +}; + +/* eslint-disable */ +for (const key in ENV) { + if (import.meta.env?.hasOwnProperty(`VITE_${key}`)) { + (ENV as any)[key] = covertToType(import.meta.env[`VITE_${key}`]); + } + + if ((window as any).SERVER_DATA?.hasOwnProperty(key)) { + (ENV as any)[key] = covertToType((window as any).SERVER_DATA[key]); + } +} + +(window as any).environment = ENV; +/* eslint-enable */ + +console.table(ENV); + +export { ENV }; diff --git a/dashboard/src/interfaces/projects.ts b/dashboard/src/interfaces/projects.ts index 8b6a945..c1ab703 100644 --- a/dashboard/src/interfaces/projects.ts +++ b/dashboard/src/interfaces/projects.ts @@ -1,4 +1,4 @@ -export interface getProjectsResponse { +export interface GetProjectsResponse { projects: Project[]; } diff --git a/dashboard/src/views/Projects/index.tsx b/dashboard/src/views/Projects/index.tsx index 9ff9e97..151595b 100644 --- a/dashboard/src/views/Projects/index.tsx +++ b/dashboard/src/views/Projects/index.tsx @@ -1,17 +1,17 @@ +import SearchIcon from '@mui/icons-material/Search'; import { Box, FormControl, FormLabel, Input, Sheet, Table } from "@mui/joy"; import { useEffect, useState } from "react"; import { mockedProjectsResponse } from "../../api/mocks/projects.mock"; -import { getProjectsResponse } from "../../interfaces/projects"; -import { ProjectTableRow } from "./components/Row"; +import { GetProjectsResponse } from "../../interfaces/projects"; import { PaginationSection } from "./components/PaginationSection/PaginationSection"; -import SearchIcon from '@mui/icons-material/Search'; +import { ProjectTableRow } from "./components/Row"; export const ProjectsOverview = () => { const [searchBar, setSearchBar] = useState(""); //TODO: replace mocked data with the response from the API when react query hooks are implemented const projects = mockedProjectsResponse; - const [projectsList, setProjectsList] = useState( + const [projectsList, setProjectsList] = useState( mockedProjectsResponse, ); From aaf1579498c704a65a81abaa879bb331505e9a97 Mon Sep 17 00:00:00 2001 From: Max Ivanov Date: Wed, 10 Jul 2024 15:55:36 +0200 Subject: [PATCH 02/11] added registerAuthorizationInterceptor --- dashboard/src/api/client/axios.ts | 7 ++- dashboard/src/api/hooks/useGetProjects.ts | 43 ++++++++++--------- .../src/api/interceptors/authorization.ts | 15 +++++++ .../src/components/ColorSchemeToggle.tsx | 4 +- 4 files changed, 42 insertions(+), 27 deletions(-) create mode 100644 dashboard/src/api/interceptors/authorization.ts diff --git a/dashboard/src/api/client/axios.ts b/dashboard/src/api/client/axios.ts index 16f621a..9535319 100644 --- a/dashboard/src/api/client/axios.ts +++ b/dashboard/src/api/client/axios.ts @@ -1,10 +1,9 @@ import axios from 'axios'; +import AxiosMockAdapter from 'axios-mock-adapter'; +import { ENV } from '../../constants/env'; - -import registerAuthorizationInterceptor from '../interceptors/authorization'; import registerLoggerInterceptor from '../interceptors/logger'; -import { ENV } from '../../constants/env'; -import AxiosMockAdapter from 'axios-mock-adapter'; +import registerAuthorizationInterceptor from '../interceptors/authorization'; // Mock axios instance const mockInstance = axios.create(); diff --git a/dashboard/src/api/hooks/useGetProjects.ts b/dashboard/src/api/hooks/useGetProjects.ts index 5021da1..9d5c0e2 100644 --- a/dashboard/src/api/hooks/useGetProjects.ts +++ b/dashboard/src/api/hooks/useGetProjects.ts @@ -1,25 +1,26 @@ -import { useQuery } from '@tanstack/react-query'; -import { ENV } from '../../constants/env'; -import { GetProjectsResponse } from '../../interfaces/projects'; -import './useGetApplicationCategories.mock'; +// import { useQuery } from '@tanstack/react-query'; +// import { ENV } from '../../constants/env'; +// import { GetProjectsResponse } from '../../interfaces/projects'; +// import client from '../client'; +// import './useGetApplicationCategories.mock'; -// Get application categories that have been defined on the license -const getApplicationCategories = async () => { - const response = await client.get( - `/device-monitor/v1/admin/settings/application-categories`, - { - // TODO: replave with the real one - baseURL: ENV.MOCKED, - }, - ); +// // Get application categories that have been defined on the license +// const getApplicationCategories = async () => { +// const response = await client.get( +// `/device-monitor/v1/admin/settings/application-categories`, +// { +// // TODO: replace with the real one +// baseURL: ENV.MOCKED, +// }, +// ); - return response.data; -}; +// return response.data; +// }; -export const useGetApplicationCategories = () => { - return useQuery( - ['device-monitor', 'v1', 'admin', 'settings', 'application-categories'], - () => getApplicationCategories(), - ); -}; +// export const useGetApplicationCategories = () => { +// return useQuery( +// ['device-monitor', 'v1', 'admin', 'settings', 'application-categories'], +// () => getApplicationCategories(), +// ); +// }; diff --git a/dashboard/src/api/interceptors/authorization.ts b/dashboard/src/api/interceptors/authorization.ts new file mode 100644 index 0000000..e5ba9aa --- /dev/null +++ b/dashboard/src/api/interceptors/authorization.ts @@ -0,0 +1,15 @@ +import * as JWT from '@uniwise/jwt'; +import { AxiosInstance, AxiosHeaders } from 'axios'; + +function registerAuthorizationInterceptor(inst: AxiosInstance) { + inst.interceptors.request.use((config) => { + if (!config.headers) { + config.headers = new AxiosHeaders(); + } + config.headers['X-csrftoken'] = JWT.get()?.wiseflow.userInfo.csrfId || 0; + config.headers['Authorization'] = `Bearer ${JWT.getString()}`; + return config; + }); +} + +export default registerAuthorizationInterceptor; diff --git a/dashboard/src/components/ColorSchemeToggle.tsx b/dashboard/src/components/ColorSchemeToggle.tsx index 0c1bec4..adb1ceb 100644 --- a/dashboard/src/components/ColorSchemeToggle.tsx +++ b/dashboard/src/components/ColorSchemeToggle.tsx @@ -49,8 +49,8 @@ export const ColorSchemeToggle = (props: IconButtonProps) => { }} sx={[ mode === colorMode.DARK - ? { "& > *:first-child": { display: "none" } } - : { "& > *:first-child": { display: "initial" } }, + ? { "& > *:first-of-type": { display: "none" } } + : { "& > *:first-of-type": { display: "initial" } }, mode === colorMode.LIGHT ? { "& > *:last-child": { display: "none" } } : { "& > *:last-child": { display: "initial" } }, From 6cadf37a38bd31d27aff400aa6a16d7affb21f34 Mon Sep 17 00:00:00 2001 From: Max Ivanov Date: Thu, 11 Jul 2024 09:39:02 +0200 Subject: [PATCH 03/11] added useGetProjects hook and made it work with the mocked env --- dashboard/src/api/client/axios.ts | 5 +- dashboard/src/api/hooks/useGetProjects.ts | 43 +++++++-------- .../src/api/interceptors/authorization.ts | 15 ----- dashboard/src/api/mocks/projects.mock.ts | 3 + dashboard/src/main.tsx | 5 +- dashboard/src/views/Projects/index.tsx | 55 ++++++++++++------- 6 files changed, 63 insertions(+), 63 deletions(-) delete mode 100644 dashboard/src/api/interceptors/authorization.ts diff --git a/dashboard/src/api/client/axios.ts b/dashboard/src/api/client/axios.ts index 9535319..4259b56 100644 --- a/dashboard/src/api/client/axios.ts +++ b/dashboard/src/api/client/axios.ts @@ -1,13 +1,11 @@ import axios from 'axios'; import AxiosMockAdapter from 'axios-mock-adapter'; import { ENV } from '../../constants/env'; - import registerLoggerInterceptor from '../interceptors/logger'; -import registerAuthorizationInterceptor from '../interceptors/authorization'; // Mock axios instance const mockInstance = axios.create(); -registerAuthorizationInterceptor(mockInstance); + registerLoggerInterceptor(mockInstance); export const mock = new AxiosMockAdapter(mockInstance, { @@ -16,7 +14,6 @@ export const mock = new AxiosMockAdapter(mockInstance, { // Real axios instance const realInstance = axios.create(); -registerAuthorizationInterceptor(realInstance); if (ENV.MOCKED) { console.debug('Using mocked axios client'); diff --git a/dashboard/src/api/hooks/useGetProjects.ts b/dashboard/src/api/hooks/useGetProjects.ts index 9d5c0e2..a733572 100644 --- a/dashboard/src/api/hooks/useGetProjects.ts +++ b/dashboard/src/api/hooks/useGetProjects.ts @@ -1,26 +1,23 @@ -// import { useQuery } from '@tanstack/react-query'; -// import { ENV } from '../../constants/env'; -// import { GetProjectsResponse } from '../../interfaces/projects'; -// import client from '../client'; -// import './useGetApplicationCategories.mock'; +import { useQuery } from '@tanstack/react-query'; +import { GetProjectsResponse } from '../../interfaces/projects'; +import client from '../client'; +import '../mocks/projects.mock'; +const getProjects = async () => { + const response = await client.get( + `/parrot/v1/projects`, + { + // TODO: replace with the real one + baseURL: '', + }, + ); -// // Get application categories that have been defined on the license -// const getApplicationCategories = async () => { -// const response = await client.get( -// `/device-monitor/v1/admin/settings/application-categories`, -// { -// // TODO: replace with the real one -// baseURL: ENV.MOCKED, -// }, -// ); + return response.data; +}; -// return response.data; -// }; - -// export const useGetApplicationCategories = () => { -// return useQuery( -// ['device-monitor', 'v1', 'admin', 'settings', 'application-categories'], -// () => getApplicationCategories(), -// ); -// }; +export const useGetProjects = () => { + return useQuery({ + queryKey: ['parrot', 'projects'], + queryFn: () => getProjects(), + }); +}; diff --git a/dashboard/src/api/interceptors/authorization.ts b/dashboard/src/api/interceptors/authorization.ts deleted file mode 100644 index e5ba9aa..0000000 --- a/dashboard/src/api/interceptors/authorization.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as JWT from '@uniwise/jwt'; -import { AxiosInstance, AxiosHeaders } from 'axios'; - -function registerAuthorizationInterceptor(inst: AxiosInstance) { - inst.interceptors.request.use((config) => { - if (!config.headers) { - config.headers = new AxiosHeaders(); - } - config.headers['X-csrftoken'] = JWT.get()?.wiseflow.userInfo.csrfId || 0; - config.headers['Authorization'] = `Bearer ${JWT.getString()}`; - return config; - }); -} - -export default registerAuthorizationInterceptor; diff --git a/dashboard/src/api/mocks/projects.mock.ts b/dashboard/src/api/mocks/projects.mock.ts index 77fb499..f3fa4dd 100644 --- a/dashboard/src/api/mocks/projects.mock.ts +++ b/dashboard/src/api/mocks/projects.mock.ts @@ -1,4 +1,5 @@ import { GetProjectsResponse } from "../../interfaces/projects"; +import { mock } from "../client"; export const mockedProjectsResponse: GetProjectsResponse = { projects: [ @@ -154,3 +155,5 @@ export const mockedProjectsResponse: GetProjectsResponse = { }, ], }; + +mock.onGet(/^\/parrot\/v1\/projects$/).reply(200, mockedProjectsResponse); diff --git a/dashboard/src/main.tsx b/dashboard/src/main.tsx index 763a4cd..0fadb40 100644 --- a/dashboard/src/main.tsx +++ b/dashboard/src/main.tsx @@ -1,10 +1,13 @@ import React from "react"; import ReactDOM from "react-dom/client"; +import { ReactQueryClientProvider } from "./api/client"; import { Parrot } from "./App"; import "./index.css"; ReactDOM.createRoot(document.getElementById("root")!).render( - + + + , ); diff --git a/dashboard/src/views/Projects/index.tsx b/dashboard/src/views/Projects/index.tsx index 0104889..f43da07 100644 --- a/dashboard/src/views/Projects/index.tsx +++ b/dashboard/src/views/Projects/index.tsx @@ -1,7 +1,8 @@ import SearchIcon from '@mui/icons-material/Search'; import { Box, FormControl, FormLabel, Input, Sheet, Table } from "@mui/joy"; import { useEffect, useState } from "react"; -import { mockedProjectsResponse } from "../../api/mocks/projects.mock"; + +import { useGetProjects } from '../../api/hooks/useGetProjects'; import { GetProjectsResponse } from "../../interfaces/projects"; import { PaginationSection } from "./components/PaginationSection/PaginationSection"; import { ProjectTableRow } from "./components/Row"; @@ -10,25 +11,37 @@ import { ProjectTableRow } from "./components/Row"; export const ProjectsOverview = () => { const [searchBar, setSearchBar] = useState(""); //TODO: replace mocked data with the response from the API when react query hooks are implemented - const projects = mockedProjectsResponse; - const [projectsList, setProjectsList] = useState( - mockedProjectsResponse, - ); + const { data: projects } = useGetProjects(); + const [projectsList, setProjectsList] = useState(); + + console.log(projects); + + // useEffect(() => { + // if (searchBar === "") { + // return; + // } + + // if (!projects) { + // return; + // } + + // setProjectsList((prevList) => { + // if (prevList !== projectsList) { + // return projectsList; + // } + // return prevList; + // }); + // }, [projectsList, searchBar, projects]); useEffect(() => { - if (searchBar === "") { + setProjectsList(projects); + }, [projects]); + + const projectSearchHandle = (projectName: string) => { + if (!projects) { return; } - setProjectsList((prevList) => { - if (prevList !== projectsList) { - return projectsList; - } - return prevList; - }); - }, [projectsList, searchBar]); - - const projectSearchHandle = (projectName: string) => { const filteredProjects = projects.projects.filter((project) => project.name.toLowerCase().includes(projectName.toLowerCase()), ); @@ -113,11 +126,13 @@ export const ProjectsOverview = () => { - - {projectsList.projects.map((project) => ( - - ))} - + {projectsList && ( + + {projectsList.projects.map((project) => ( + + ))} + + )} From a1817365bc6b65c6931613d5733a774c729ff755 Mon Sep 17 00:00:00 2001 From: Max Ivanov Date: Thu, 11 Jul 2024 16:17:30 +0200 Subject: [PATCH 04/11] deleted commented code, added reactQueryProvider to the root, added id prop to TableRow component --- dashboard/src/components/TableRow.tsx | 21 ++++++++++++++++++--- dashboard/src/main.tsx | 9 ++++++--- dashboard/src/views/Projects/index.tsx | 23 ++--------------------- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/dashboard/src/components/TableRow.tsx b/dashboard/src/components/TableRow.tsx index 03a8dc1..9fe4863 100644 --- a/dashboard/src/components/TableRow.tsx +++ b/dashboard/src/components/TableRow.tsx @@ -1,14 +1,17 @@ import { Delete } from "@mui/icons-material"; -import { Typography } from "@mui/joy"; +import { Button, Typography } from "@mui/joy"; import { FC } from "react"; +import { Link } from "react-router-dom"; interface ProjectTableRowProps { + id?: number; name: string; numberOfVersions?: number; createdAt: string; } export const TableRow: FC = ({ + id, name, numberOfVersions, createdAt, @@ -35,7 +38,7 @@ export const TableRow: FC = ({ {createdAtDate} - {!numberOfVersions && ( + {!numberOfVersions ? ( = ({ verticalAlign: "center", }} > - + + + ) : ( + + + + )} diff --git a/dashboard/src/main.tsx b/dashboard/src/main.tsx index 18318cc..b72533a 100644 --- a/dashboard/src/main.tsx +++ b/dashboard/src/main.tsx @@ -3,6 +3,7 @@ import ReactDOM from "react-dom/client"; import { Box, CssBaseline, CssVarsProvider } from "@mui/joy"; import { BrowserRouter } from "react-router-dom"; +import { ReactQueryClientProvider } from "./api/client"; import "./index.css"; import Routes from "./routes"; @@ -29,9 +30,11 @@ ReactDOM.createRoot(document.getElementById("root")!).render( gap: 1, }} > - - - + + + + + diff --git a/dashboard/src/views/Projects/index.tsx b/dashboard/src/views/Projects/index.tsx index 986e8a1..c68db22 100644 --- a/dashboard/src/views/Projects/index.tsx +++ b/dashboard/src/views/Projects/index.tsx @@ -16,29 +16,9 @@ import { GetProjectsResponse } from "../../interfaces/projects"; export const ProjectsOverview = () => { const [searchBar, setSearchBar] = useState(""); - //TODO: replace mocked data with the response from the API when react query hooks are implemented const { data: projects } = useGetProjects(); const [projectsList, setProjectsList] = useState(); - console.log(projects); - - // useEffect(() => { - // if (searchBar === "") { - // return; - // } - - // if (!projects) { - // return; - // } - - // setProjectsList((prevList) => { - // if (prevList !== projectsList) { - // return projectsList; - // } - // return prevList; - // }); - // }, [projectsList, searchBar, projects]); - useEffect(() => { setProjectsList(projects); }, [projects]); @@ -145,8 +125,9 @@ export const ProjectsOverview = () => { {projectsList && ( {projectsList.projects.map((project) => ( - Date: Thu, 11 Jul 2024 16:18:01 +0200 Subject: [PATCH 05/11] fixed typo --- dashboard/src/views/Projects/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashboard/src/views/Projects/index.tsx b/dashboard/src/views/Projects/index.tsx index c68db22..58a33db 100644 --- a/dashboard/src/views/Projects/index.tsx +++ b/dashboard/src/views/Projects/index.tsx @@ -125,7 +125,7 @@ export const ProjectsOverview = () => { {projectsList && ( {projectsList.projects.map((project) => ( - Date: Fri, 12 Jul 2024 19:29:44 +0200 Subject: [PATCH 06/11] added more hooks and mocks --- dashboard/src/api/hooks/useDeleteVersion.ts | 29 ++++++++ dashboard/src/api/hooks/useGetProject.ts | 19 ++++++ dashboard/src/api/hooks/useGetProjects.ts | 10 +-- dashboard/src/api/hooks/useGetVersions.ts | 19 ++++++ dashboard/src/api/hooks/usePostVersion.ts | 29 ++++++++ .../src/api/mocks/useDeleteVersion.mock.ts | 14 ++++ dashboard/src/api/mocks/useGetProject.mock.ts | 15 ++++ ...rojects.mock.ts => useGetProjects.mock.ts} | 2 +- ...ersions.mock.ts => useGetVersions.mock.ts} | 3 + .../src/api/mocks/usePostVersion.mock.ts | 20 ++++++ dashboard/src/components/TableRow.tsx | 16 +++-- .../src/views/Projects/Versions/index.tsx | 68 +++++++++++-------- dashboard/src/views/Projects/index.tsx | 4 +- 13 files changed, 202 insertions(+), 46 deletions(-) create mode 100644 dashboard/src/api/hooks/useDeleteVersion.ts create mode 100644 dashboard/src/api/hooks/useGetProject.ts create mode 100644 dashboard/src/api/hooks/useGetVersions.ts create mode 100644 dashboard/src/api/hooks/usePostVersion.ts create mode 100644 dashboard/src/api/mocks/useDeleteVersion.mock.ts create mode 100644 dashboard/src/api/mocks/useGetProject.mock.ts rename dashboard/src/api/mocks/{projects.mock.ts => useGetProjects.mock.ts} (97%) rename dashboard/src/api/mocks/{versions.mock.ts => useGetVersions.mock.ts} (95%) create mode 100644 dashboard/src/api/mocks/usePostVersion.mock.ts diff --git a/dashboard/src/api/hooks/useDeleteVersion.ts b/dashboard/src/api/hooks/useDeleteVersion.ts new file mode 100644 index 0000000..c681bd2 --- /dev/null +++ b/dashboard/src/api/hooks/useDeleteVersion.ts @@ -0,0 +1,29 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { GetVersionsResponse } from '../../interfaces/versions'; +import client from '../client'; +import '../mocks/useDeleteVersion.mock'; + +const deleteVersion = async (projectId?: string, version?: number) => { + const response = await client.delete( + `/api/v1/projects/${projectId}/versions/${version}`, + ); + + return response.data; +}; + +export const useDeleteVersion = (projectId?: string, version?: number) => { + const queryClient = useQueryClient(); + + return useMutation({ + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: ['api', 'projects', projectId, 'versions'], + exact: true, + }, + { throwOnError: true }); + }, + mutationFn: () => deleteVersion(projectId, version), + }); +}; + + diff --git a/dashboard/src/api/hooks/useGetProject.ts b/dashboard/src/api/hooks/useGetProject.ts new file mode 100644 index 0000000..5d09d16 --- /dev/null +++ b/dashboard/src/api/hooks/useGetProject.ts @@ -0,0 +1,19 @@ +import { useQuery } from '@tanstack/react-query'; +import { Project } from '../../interfaces/projects'; +import client from '../client'; +import '../mocks/useGetProject.mock'; + +const getProject = async (projectId?: string) => { + const response = await client.get( + `/api/v1/projects/${projectId}`, + ); + + return response.data; +}; + +export const useGetProject = (projectId?: string) => { + return useQuery({ + queryKey: ['api', 'projects', projectId], + queryFn: () => getProject(projectId), + }); +}; diff --git a/dashboard/src/api/hooks/useGetProjects.ts b/dashboard/src/api/hooks/useGetProjects.ts index a733572..0785232 100644 --- a/dashboard/src/api/hooks/useGetProjects.ts +++ b/dashboard/src/api/hooks/useGetProjects.ts @@ -1,15 +1,11 @@ import { useQuery } from '@tanstack/react-query'; import { GetProjectsResponse } from '../../interfaces/projects'; import client from '../client'; -import '../mocks/projects.mock'; +import '../mocks/useGetProjects.mock'; const getProjects = async () => { const response = await client.get( - `/parrot/v1/projects`, - { - // TODO: replace with the real one - baseURL: '', - }, + `/api/v1/projects`, ); return response.data; @@ -17,7 +13,7 @@ const getProjects = async () => { export const useGetProjects = () => { return useQuery({ - queryKey: ['parrot', 'projects'], + queryKey: ['api', 'projects'], queryFn: () => getProjects(), }); }; diff --git a/dashboard/src/api/hooks/useGetVersions.ts b/dashboard/src/api/hooks/useGetVersions.ts new file mode 100644 index 0000000..31b5355 --- /dev/null +++ b/dashboard/src/api/hooks/useGetVersions.ts @@ -0,0 +1,19 @@ +import { useQuery } from '@tanstack/react-query'; +import { GetVersionsResponse } from '../../interfaces/versions'; +import client from '../client'; +import '../mocks/useGetVersions.mock'; + +const getVersions = async (projectId?: string) => { + const response = await client.get( + `/api/v1/projects/${projectId}/versions`, + ); + + return response.data; +}; + +export const useGetVersions = (projectId?: string) => { + return useQuery({ + queryKey: ['api', 'projects', projectId, 'versions'], + queryFn: () => getVersions(projectId), + }); +}; diff --git a/dashboard/src/api/hooks/usePostVersion.ts b/dashboard/src/api/hooks/usePostVersion.ts new file mode 100644 index 0000000..2af794f --- /dev/null +++ b/dashboard/src/api/hooks/usePostVersion.ts @@ -0,0 +1,29 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { GetVersionsResponse } from '../../interfaces/versions'; +import client from '../client'; +import '../mocks/useGetVersions.mock'; + +const postVersion = async (projectId: number) => { + const response = await client.post( + `/api/v1/projects/${projectId}/versions`, + ); + + return response.data; +}; + +export const usePostVersion = (projectId: number) => { + const queryClient = useQueryClient(); + + return useMutation({ + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: ['api', 'projects', projectId, 'versions'], + exact: true, + }, + { throwOnError: true }); + }, + mutationFn: () => postVersion(projectId), + }); +}; + + diff --git a/dashboard/src/api/mocks/useDeleteVersion.mock.ts b/dashboard/src/api/mocks/useDeleteVersion.mock.ts new file mode 100644 index 0000000..9ed49fc --- /dev/null +++ b/dashboard/src/api/mocks/useDeleteVersion.mock.ts @@ -0,0 +1,14 @@ +import { mock } from "../client"; +import { mockedVersionsResponse } from "./useGetVersions.mock"; + +mock.onDelete(/^\/api\/v1\/projects\/\d+\/versions\/\d+$/).reply((req) => { + const versionId = parseInt(req.url!.split("/")[6]); + + const versionIndex = mockedVersionsResponse.versions.findIndex( + (version) => version.id === versionId + ); + + mockedVersionsResponse.versions.splice(versionIndex, 1); + + return [204]; +}); diff --git a/dashboard/src/api/mocks/useGetProject.mock.ts b/dashboard/src/api/mocks/useGetProject.mock.ts new file mode 100644 index 0000000..2fbdfd1 --- /dev/null +++ b/dashboard/src/api/mocks/useGetProject.mock.ts @@ -0,0 +1,15 @@ +import { mock } from "../client"; +import { mockedProjectsResponse } from "./useGetProjects.mock"; + +mock.onGet(/^\/api\/v1\/projects\/\d+$/).reply((req) => { + const projectId = parseInt(req.url!.split("/")[4]); + + const project = mockedProjectsResponse.projects.find( + (project) => project.id === projectId + ); + + return [ + 200, + project + ]; +}); diff --git a/dashboard/src/api/mocks/projects.mock.ts b/dashboard/src/api/mocks/useGetProjects.mock.ts similarity index 97% rename from dashboard/src/api/mocks/projects.mock.ts rename to dashboard/src/api/mocks/useGetProjects.mock.ts index f3fa4dd..5fa8a68 100644 --- a/dashboard/src/api/mocks/projects.mock.ts +++ b/dashboard/src/api/mocks/useGetProjects.mock.ts @@ -156,4 +156,4 @@ export const mockedProjectsResponse: GetProjectsResponse = { ], }; -mock.onGet(/^\/parrot\/v1\/projects$/).reply(200, mockedProjectsResponse); +mock.onGet(/^\/api\/v1\/projects$/).reply(200, mockedProjectsResponse); diff --git a/dashboard/src/api/mocks/versions.mock.ts b/dashboard/src/api/mocks/useGetVersions.mock.ts similarity index 95% rename from dashboard/src/api/mocks/versions.mock.ts rename to dashboard/src/api/mocks/useGetVersions.mock.ts index bab6c8b..5dd0597 100644 --- a/dashboard/src/api/mocks/versions.mock.ts +++ b/dashboard/src/api/mocks/useGetVersions.mock.ts @@ -1,4 +1,5 @@ import { GetVersionsResponse } from "../../interfaces/versions"; +import { mock } from "../client"; export const mockedVersionsResponse: GetVersionsResponse = { versions: [ @@ -129,3 +130,5 @@ export const mockedVersionsResponse: GetVersionsResponse = { }, ], }; + +mock.onGet(/^\/api\/v1\/projects\/\d+\/versions$/).reply(200, mockedVersionsResponse); diff --git a/dashboard/src/api/mocks/usePostVersion.mock.ts b/dashboard/src/api/mocks/usePostVersion.mock.ts new file mode 100644 index 0000000..5f93193 --- /dev/null +++ b/dashboard/src/api/mocks/usePostVersion.mock.ts @@ -0,0 +1,20 @@ +import { mock } from "../client"; +import { mockedVersionsResponse } from "./useGetVersions.mock"; + +mock.onPost(/^\/api\/v1\/projects\/\d+\/versions$/).reply((req) => { + const request = JSON.parse(req.data); + const projectId = parseInt(req.url!.split("/")[4]); + + const newVersion = { + id: Math.floor(Math.random() * 1000), + name: request.name, + projectId: projectId, + version: request.version, + createdAt: new Date().toISOString(), + }; + + mockedVersionsResponse.versions.push(newVersion); + + return [201, newVersion]; +}); + diff --git a/dashboard/src/components/TableRow.tsx b/dashboard/src/components/TableRow.tsx index 9fe4863..462e839 100644 --- a/dashboard/src/components/TableRow.tsx +++ b/dashboard/src/components/TableRow.tsx @@ -1,7 +1,8 @@ import { Delete } from "@mui/icons-material"; import { Button, Typography } from "@mui/joy"; import { FC } from "react"; -import { Link } from "react-router-dom"; +import { Link, useParams } from "react-router-dom"; +import { useDeleteVersion } from "../api/hooks/useDeleteVersion"; interface ProjectTableRowProps { id?: number; @@ -20,6 +21,9 @@ export const TableRow: FC = ({ return new Date(isoDate).toLocaleString(); }; + const { projectId } = useParams(); + const { mutate: handleDeleteVersion } = useDeleteVersion(projectId, id); + const createdAtDate = formatIsoDateToLocaleString(createdAt); return ( @@ -46,7 +50,7 @@ export const TableRow: FC = ({ verticalAlign: "center", }} > - + ) : ( = ({ padding: "0.5rem 5rem", verticalAlign: "center", }} - > - - - + > + + + )} diff --git a/dashboard/src/views/Projects/Versions/index.tsx b/dashboard/src/views/Projects/Versions/index.tsx index 37b56b2..019f69b 100644 --- a/dashboard/src/views/Projects/Versions/index.tsx +++ b/dashboard/src/views/Projects/Versions/index.tsx @@ -9,37 +9,32 @@ import { Table, Typography, } from "@mui/joy"; - import { useEffect, useState } from "react"; -import { mockedVersionsResponse } from "../../../api/mocks/versions.mock"; +import { useParams } from "react-router"; +import { useGetProject } from "../../../api/hooks/useGetProject"; +import { useGetVersions } from "../../../api/hooks/useGetVersions"; import { PaginationSection } from "../../../components/TablePaginationSection"; import { TableRow } from "../../../components/TableRow"; import { GetVersionsResponse } from "../../../interfaces/versions"; export const VersionsOverview = () => { const [searchBar, setSearchBar] = useState(""); - //TODO: replace mocked data with the response from the API when react query hooks are implemented - const versions = mockedVersionsResponse.versions; - const [versionsList, setVersionsList] = useState( - mockedVersionsResponse, - ); + const { projectId } = useParams(); + const { data: project } = useGetProject(projectId); + const { data: versionsData } = useGetVersions(projectId); + const [versionsList, setVersionsList] = useState(); useEffect(() => { - if (searchBar === "") { - return; - } + if (!versionsData) return; - setVersionsList((prevList) => { - if (prevList !== versionsList) { - return versionsList; - } - return prevList; - }); - }, [searchBar, versionsList]); + setVersionsList(versionsData); + }, [versionsData]); - const versionSearchHandle = (search: string) => { - const filteredVersions = versions.filter((version) => - version.name.toLowerCase().includes(search.toLowerCase()), + const versionSearchHandle = (versionName: string) => { + if (!versionsData) return; + + const filteredVersions = versionsData.versions.filter((version) => + version.name.toLowerCase().includes(versionName.toLowerCase()), ); setVersionsList({ versions: filteredVersions }); }; @@ -58,6 +53,18 @@ export const VersionsOverview = () => { }, }} > + + {project?.name} + + { - - {versionsList.versions.map((version) => ( - - ))} - + {versionsList && ( + + {versionsList.versions.map((version) => ( + + ))} + + )} diff --git a/dashboard/src/views/Projects/index.tsx b/dashboard/src/views/Projects/index.tsx index 58a33db..36724fe 100644 --- a/dashboard/src/views/Projects/index.tsx +++ b/dashboard/src/views/Projects/index.tsx @@ -24,9 +24,7 @@ export const ProjectsOverview = () => { }, [projects]); const projectSearchHandle = (projectName: string) => { - if (!projects) { - return; - } + if (!projects) return; const filteredProjects = projects.projects.filter((project) => project.name.toLowerCase().includes(projectName.toLowerCase()), From 2b5338a23ef8799e46e088a0667e7b2efe055fad Mon Sep 17 00:00:00 2001 From: Max Ivanov Date: Fri, 12 Jul 2024 19:40:05 +0200 Subject: [PATCH 07/11] added type to mocked post data in usePostRequest --- dashboard/src/api/mocks/usePostVersion.mock.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dashboard/src/api/mocks/usePostVersion.mock.ts b/dashboard/src/api/mocks/usePostVersion.mock.ts index 5f93193..7072b7e 100644 --- a/dashboard/src/api/mocks/usePostVersion.mock.ts +++ b/dashboard/src/api/mocks/usePostVersion.mock.ts @@ -1,15 +1,13 @@ +import { Version } from "../../interfaces/versions"; import { mock } from "../client"; import { mockedVersionsResponse } from "./useGetVersions.mock"; mock.onPost(/^\/api\/v1\/projects\/\d+\/versions$/).reply((req) => { const request = JSON.parse(req.data); - const projectId = parseInt(req.url!.split("/")[4]); - const newVersion = { + const newVersion: Version = { id: Math.floor(Math.random() * 1000), name: request.name, - projectId: projectId, - version: request.version, createdAt: new Date().toISOString(), }; From 2ce2fafd6096b7fdee5a00421113fd11a16cc308 Mon Sep 17 00:00:00 2001 From: Max Ivanov Date: Fri, 12 Jul 2024 20:05:21 +0200 Subject: [PATCH 08/11] styling changes --- dashboard/src/components/TableRow.tsx | 4 ++-- dashboard/src/interfaces/versions.ts | 1 + .../src/views/Projects/Versions/index.tsx | 22 ++++++------------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/dashboard/src/components/TableRow.tsx b/dashboard/src/components/TableRow.tsx index 462e839..1cbcbf7 100644 --- a/dashboard/src/components/TableRow.tsx +++ b/dashboard/src/components/TableRow.tsx @@ -50,7 +50,7 @@ export const TableRow: FC = ({ verticalAlign: "center", }} > - + ) : ( = ({ }} > - + )} diff --git a/dashboard/src/interfaces/versions.ts b/dashboard/src/interfaces/versions.ts index 6dee80f..382505f 100644 --- a/dashboard/src/interfaces/versions.ts +++ b/dashboard/src/interfaces/versions.ts @@ -7,3 +7,4 @@ export interface Version { name: string; createdAt: string; // ISO 8601 } + // background-color: var(--variant-solidBg, var(--joy-palette-primary-solidBg, var(--joy-palette-primary-500, #0B6BCB))); diff --git a/dashboard/src/views/Projects/Versions/index.tsx b/dashboard/src/views/Projects/Versions/index.tsx index 019f69b..30c0001 100644 --- a/dashboard/src/views/Projects/Versions/index.tsx +++ b/dashboard/src/views/Projects/Versions/index.tsx @@ -51,30 +51,22 @@ export const VersionsOverview = () => { "& > *": { minWidth: { xs: "120px", md: "160px" }, }, + flexDirection: 'column', }} > - {project?.name} - - - - Versions + {project?.name} versions @@ -93,7 +85,7 @@ export const VersionsOverview = () => { - + Date: Mon, 22 Jul 2024 11:19:14 +0200 Subject: [PATCH 09/11] added ProjectTableRow component --- dashboard/src/components/TableRow.tsx | 70 ------------------- .../src/views/Projects/Versions/index.tsx | 4 +- .../src/views/Projects/components/index.tsx | 52 ++++++++++++++ dashboard/src/views/Projects/index.tsx | 8 +-- 4 files changed, 58 insertions(+), 76 deletions(-) delete mode 100644 dashboard/src/components/TableRow.tsx create mode 100644 dashboard/src/views/Projects/components/index.tsx diff --git a/dashboard/src/components/TableRow.tsx b/dashboard/src/components/TableRow.tsx deleted file mode 100644 index 1cbcbf7..0000000 --- a/dashboard/src/components/TableRow.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Delete } from "@mui/icons-material"; -import { Button, Typography } from "@mui/joy"; -import { FC } from "react"; -import { Link, useParams } from "react-router-dom"; -import { useDeleteVersion } from "../api/hooks/useDeleteVersion"; - -interface ProjectTableRowProps { - id?: number; - name: string; - numberOfVersions?: number; - createdAt: string; -} - -export const TableRow: FC = ({ - id, - name, - numberOfVersions, - createdAt, -}) => { - const formatIsoDateToLocaleString = (isoDate: string) => { - return new Date(isoDate).toLocaleString(); - }; - - const { projectId } = useParams(); - const { mutate: handleDeleteVersion } = useDeleteVersion(projectId, id); - - const createdAtDate = formatIsoDateToLocaleString(createdAt); - - return ( - - - {name} - - - {numberOfVersions && ( - - {numberOfVersions} - - )} - - - {createdAtDate} - - - {!numberOfVersions ? ( - - - - ) : ( - - - - - - )} - - ); -}; diff --git a/dashboard/src/views/Projects/Versions/index.tsx b/dashboard/src/views/Projects/Versions/index.tsx index 30c0001..d54410c 100644 --- a/dashboard/src/views/Projects/Versions/index.tsx +++ b/dashboard/src/views/Projects/Versions/index.tsx @@ -14,8 +14,8 @@ import { useParams } from "react-router"; import { useGetProject } from "../../../api/hooks/useGetProject"; import { useGetVersions } from "../../../api/hooks/useGetVersions"; import { PaginationSection } from "../../../components/TablePaginationSection"; -import { TableRow } from "../../../components/TableRow"; import { GetVersionsResponse } from "../../../interfaces/versions"; +import { ProjectTableRow } from "../components"; export const VersionsOverview = () => { const [searchBar, setSearchBar] = useState(""); @@ -133,7 +133,7 @@ export const VersionsOverview = () => { {versionsList && ( {versionsList.versions.map((version) => ( - = ({ + id, + name, + numberOfVersions, + createdAt, +}) => { + const formatIsoDateToLocaleString = (isoDate: string) => { + return new Date(isoDate).toLocaleString(); + }; + + const createdAtDate = formatIsoDateToLocaleString(createdAt); + + return ( + + + {name} + + + + {numberOfVersions} + + + + {createdAtDate} + + + + + + + + + + ); +}; diff --git a/dashboard/src/views/Projects/index.tsx b/dashboard/src/views/Projects/index.tsx index 36724fe..a575932 100644 --- a/dashboard/src/views/Projects/index.tsx +++ b/dashboard/src/views/Projects/index.tsx @@ -11,8 +11,8 @@ import { import { useEffect, useState } from "react"; import { useGetProjects } from "../../api/hooks/useGetProjects"; import { PaginationSection } from "../../components/TablePaginationSection"; -import { TableRow } from "../../components/TableRow"; -import { GetProjectsResponse } from "../../interfaces/projects"; +import { GetProjectsResponse, Project } from "../../interfaces/projects"; +import { ProjectTableRow } from "./components"; export const ProjectsOverview = () => { const [searchBar, setSearchBar] = useState(""); @@ -26,7 +26,7 @@ export const ProjectsOverview = () => { const projectSearchHandle = (projectName: string) => { if (!projects) return; - const filteredProjects = projects.projects.filter((project) => + const filteredProjects = projects.projects.filter((project: Project) => project.name.toLowerCase().includes(projectName.toLowerCase()), ); setProjectsList({ projects: filteredProjects }); @@ -123,7 +123,7 @@ export const ProjectsOverview = () => { {projectsList && ( {projectsList.projects.map((project) => ( - Date: Mon, 22 Jul 2024 11:32:16 +0200 Subject: [PATCH 10/11] added VersionTableRow component --- .../Projects/Versions/components/index.tsx | 52 +++++++++++++++++-- .../src/views/Projects/Versions/index.tsx | 9 ++-- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/dashboard/src/views/Projects/Versions/components/index.tsx b/dashboard/src/views/Projects/Versions/components/index.tsx index c53a8ae..b52df5f 100644 --- a/dashboard/src/views/Projects/Versions/components/index.tsx +++ b/dashboard/src/views/Projects/Versions/components/index.tsx @@ -1,5 +1,49 @@ -const AddNewVersionModal = () => { - return
AddNewVersion
; -}; +import { Delete } from "@mui/icons-material"; +import { Button, Typography } from "@mui/joy"; +import { FC } from "react"; +import { useParams } from "react-router-dom"; +import { useDeleteVersion } from "../../../../api/hooks/useDeleteVersion"; + + +interface VersionTableRowProps { + versionId: number; + versionName: string; + createdAt: string; +} + +export const VersionTableRow: FC = ({ + versionId, + versionName, + createdAt, +}) => { + const formatIsoDateToLocaleString = (isoDate: string) => { + return new Date(isoDate).toLocaleString(); + }; + + const { projectId } = useParams(); + const { mutate: deleteVersion } = useDeleteVersion(projectId, versionId); -export default AddNewVersionModal; + const createdAtDate = formatIsoDateToLocaleString(createdAt); + + return ( + + + {versionName} + + + + {createdAtDate} + + + + + + + ); +}; diff --git a/dashboard/src/views/Projects/Versions/index.tsx b/dashboard/src/views/Projects/Versions/index.tsx index d54410c..a63bde5 100644 --- a/dashboard/src/views/Projects/Versions/index.tsx +++ b/dashboard/src/views/Projects/Versions/index.tsx @@ -15,7 +15,8 @@ import { useGetProject } from "../../../api/hooks/useGetProject"; import { useGetVersions } from "../../../api/hooks/useGetVersions"; import { PaginationSection } from "../../../components/TablePaginationSection"; import { GetVersionsResponse } from "../../../interfaces/versions"; -import { ProjectTableRow } from "../components"; +import { VersionTableRow } from "./components"; + export const VersionsOverview = () => { const [searchBar, setSearchBar] = useState(""); @@ -133,10 +134,10 @@ export const VersionsOverview = () => { {versionsList && ( {versionsList.versions.map((version) => ( - ))} From e4b9b61906c72eb2354dcd8f14ae5bf900d7b092 Mon Sep 17 00:00:00 2001 From: Max Ivanov Date: Mon, 22 Jul 2024 12:31:33 +0200 Subject: [PATCH 11/11] deleted comment --- dashboard/src/interfaces/versions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/dashboard/src/interfaces/versions.ts b/dashboard/src/interfaces/versions.ts index 382505f..6dee80f 100644 --- a/dashboard/src/interfaces/versions.ts +++ b/dashboard/src/interfaces/versions.ts @@ -7,4 +7,3 @@ export interface Version { name: string; createdAt: string; // ISO 8601 } - // background-color: var(--variant-solidBg, var(--joy-palette-primary-solidBg, var(--joy-palette-primary-500, #0B6BCB)));