From 44a732a8c1052ea9d4c8e8c004fff61a9d2dedf7 Mon Sep 17 00:00:00 2001 From: ridhlab Date: Mon, 1 Jan 2024 15:38:36 +0700 Subject: [PATCH 1/3] feat(result): add index page --- src/common/breadcrumb.ts | 11 +++++ src/interfaces/entities/InputValues/index.ts | 10 +++++ src/interfaces/entities/Results/index.ts | 11 +++++ src/interfaces/responses/Result/index.ts | 5 +++ src/pages/Result/index.tsx | 47 +++++++++++++++++++- src/routes/routes.ts | 6 +++ src/services/apis/Result/index.ts | 14 ++++++ src/services/query/Result/index.ts | 14 ++++++ 8 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 src/interfaces/entities/InputValues/index.ts create mode 100644 src/interfaces/entities/Results/index.ts create mode 100644 src/interfaces/responses/Result/index.ts create mode 100644 src/services/apis/Result/index.ts create mode 100644 src/services/query/Result/index.ts diff --git a/src/common/breadcrumb.ts b/src/common/breadcrumb.ts index cbfb3a2..b10fc32 100644 --- a/src/common/breadcrumb.ts +++ b/src/common/breadcrumb.ts @@ -55,5 +55,16 @@ export const Breadcrumbs = { }, Result: { Index: () => [{ label: "Result Predict", href: Routes.ResultIndex }], + Create: () => [ + { label: "Result Predict", href: Routes.ResultIndex }, + { label: "Create", href: Routes.ResultCreate }, + ], + Detail: (id) => [ + { label: "Result Predict", href: Routes.ResultIndex }, + { + label: "Detail", + href: parsingRoute(Routes.ResultDetail, { id }), + }, + ], }, }; diff --git a/src/interfaces/entities/InputValues/index.ts b/src/interfaces/entities/InputValues/index.ts new file mode 100644 index 0000000..894903d --- /dev/null +++ b/src/interfaces/entities/InputValues/index.ts @@ -0,0 +1,10 @@ +import { IVariableInput } from "../VariableInput"; +import { IBaseEntity } from "../base"; + +export interface IInputValue extends IBaseEntity { + id: number; + value: number; + resultId: number; + variableInputId: number; + variableInput?: IVariableInput; +} diff --git a/src/interfaces/entities/Results/index.ts b/src/interfaces/entities/Results/index.ts new file mode 100644 index 0000000..8fcb5c8 --- /dev/null +++ b/src/interfaces/entities/Results/index.ts @@ -0,0 +1,11 @@ +import { IInputValue } from "../InputValues"; +import { IUser } from "../Users"; +import { IBaseEntity } from "../base"; + +export interface IResult extends IBaseEntity { + id: number; + name: string; + userId: string; + user?: IUser; + inputValues: IInputValue[]; +} diff --git a/src/interfaces/responses/Result/index.ts b/src/interfaces/responses/Result/index.ts new file mode 100644 index 0000000..9dbb2a4 --- /dev/null +++ b/src/interfaces/responses/Result/index.ts @@ -0,0 +1,5 @@ +import { IResult } from "@/interfaces/entities/Results"; +import { IBaseResponse } from "../base"; + +export interface IResultListResponse extends IBaseResponse {} +export interface IResultDetailResponse extends IBaseResponse {} diff --git a/src/pages/Result/index.tsx b/src/pages/Result/index.tsx index ea692fe..c130904 100644 --- a/src/pages/Result/index.tsx +++ b/src/pages/Result/index.tsx @@ -1,12 +1,55 @@ import { Breadcrumbs } from "@/common/breadcrumb"; +import LoaderCenter from "@/components/shared/Loader/LoaderCenter"; +import RowActionButtons from "@/components/shared/Table/RowActionButtons"; +import { parsingRoute } from "@/helpers/route"; +import { numberColumns } from "@/helpers/table"; +import { IResult } from "@/interfaces/entities/Results"; import MainLayout from "@/layouts/MainLayout"; -import { Card } from "antd"; +import { Routes } from "@/routes/routes"; +import { useResultByUserLoginQuery } from "@/services/query/Result"; +import { Card, Table } from "antd"; +import { ColumnsType } from "antd/es/table"; import React from "react"; const ResultIndex: React.FC = () => { + const query = useResultByUserLoginQuery(); + const columns: ColumnsType = [ + numberColumns(), + { + title: "Name", + key: "name", + dataIndex: "name", + }, + { + title: "Actions", + key: "actions", + dataIndex: "id", + render: (id) => ( + + ), + }, + ]; return ( - + + {query.isLoading || query.isFetching ? ( + + ) : ( + + )} + ); }; diff --git a/src/routes/routes.ts b/src/routes/routes.ts index ce89fd7..40984e3 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -18,6 +18,8 @@ export enum Routes { MatrixComparesEdit = "/matrix-compares/edit-by-input/:inputId", ResultIndex = "/result", + ResultCreate = "/result/create", + ResultDetail = "/result/:id", } export enum EndpointApi { @@ -38,4 +40,8 @@ export enum EndpointApi { MatrixCompareIndexByVariableInputId = "/matrix-compare?variable_input_id=:inputId", MatrixCompareUpdateByInputId = "/matrix-compare/edit-by-input-id/:inputId", + + ResultByUserLogin = "/result-by-user-login", + ResultDetail = "/result/:id", + ResultPredict = "/predict", } diff --git a/src/services/apis/Result/index.ts b/src/services/apis/Result/index.ts new file mode 100644 index 0000000..316ac39 --- /dev/null +++ b/src/services/apis/Result/index.ts @@ -0,0 +1,14 @@ +import { AxiosError } from "axios"; +import { axiosAuthInstance } from ".."; +import { EndpointApi } from "@/routes/routes"; + +export const resultGetByUserLogin = async () => { + try { + const response = await axiosAuthInstance.get( + EndpointApi.ResultByUserLogin + ); + return response.data; + } catch (error) { + throw (error as AxiosError).response.data; + } +}; diff --git a/src/services/query/Result/index.ts b/src/services/query/Result/index.ts new file mode 100644 index 0000000..a1bdbf7 --- /dev/null +++ b/src/services/query/Result/index.ts @@ -0,0 +1,14 @@ +import { IResultListResponse } from "@/interfaces/responses/Result"; +import { IBaseResponse } from "@/interfaces/responses/base"; +import { resultGetByUserLogin } from "@/services/apis/Result"; +import { UseQueryOptions, useQuery } from "@tanstack/react-query"; + +export const useResultByUserLoginQuery = ( + options?: UseQueryOptions> +) => { + return useQuery({ + queryKey: ["get-result-by-user-login"], + queryFn: () => resultGetByUserLogin(), + ...options, + }); +}; From a216b85ee7cad9a4d3a7529a64798bf1a208b46b Mon Sep 17 00:00:00 2001 From: ridhlab Date: Tue, 2 Jan 2024 15:23:24 +0700 Subject: [PATCH 2/3] feat: create predict --- src/common/breadcrumb.ts | 2 +- .../modules/Result/ModalResultPredict.tsx | 67 ++++++++ src/interfaces/requests/Result/index.ts | 4 + src/interfaces/responses/Result/index.ts | 11 ++ src/pages/Result/Create.tsx | 152 ++++++++++++++++++ src/pages/Result/index.tsx | 9 +- src/routes/index.tsx | 9 ++ src/services/apis/Result/index.ts | 13 ++ src/services/mutation/Result/index.ts | 19 +++ 9 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 src/components/modules/Result/ModalResultPredict.tsx create mode 100644 src/interfaces/requests/Result/index.ts create mode 100644 src/pages/Result/Create.tsx create mode 100644 src/services/mutation/Result/index.ts diff --git a/src/common/breadcrumb.ts b/src/common/breadcrumb.ts index b10fc32..cddfad6 100644 --- a/src/common/breadcrumb.ts +++ b/src/common/breadcrumb.ts @@ -57,7 +57,7 @@ export const Breadcrumbs = { Index: () => [{ label: "Result Predict", href: Routes.ResultIndex }], Create: () => [ { label: "Result Predict", href: Routes.ResultIndex }, - { label: "Create", href: Routes.ResultCreate }, + { label: "Predict", href: Routes.ResultCreate }, ], Detail: (id) => [ { label: "Result Predict", href: Routes.ResultIndex }, diff --git a/src/components/modules/Result/ModalResultPredict.tsx b/src/components/modules/Result/ModalResultPredict.tsx new file mode 100644 index 0000000..66e4197 --- /dev/null +++ b/src/components/modules/Result/ModalResultPredict.tsx @@ -0,0 +1,67 @@ +import Button from "@/components/shared/Button/Button"; +import { getDecimalPlace } from "@/helpers/number"; +import { parsingRoute } from "@/helpers/route"; +import { IPredict } from "@/interfaces/responses/Result"; +import { Routes } from "@/routes/routes"; +import { EyeOutlined } from "@ant-design/icons"; +import { Modal, Space, Table } from "antd"; +import { ColumnsType } from "antd/es/table"; +import React from "react"; + +interface IProps { + open: boolean; + closeModal: () => void; + dataPredict: IPredict; +} + +const ModalResultPredict: React.FC = ({ + open, + closeModal, + dataPredict, +}) => { + const footer = ( + + + + + ); + + const columns: ColumnsType = [ + { + title: "Variable Output", + dataIndex: "variableOutputName", + }, + { + title: "Value", + dataIndex: "value", + render: (value) => getDecimalPlace(value, 3), + }, + ]; + + return ( + +
+ + ); +}; + +export default ModalResultPredict; diff --git a/src/interfaces/requests/Result/index.ts b/src/interfaces/requests/Result/index.ts new file mode 100644 index 0000000..8e99580 --- /dev/null +++ b/src/interfaces/requests/Result/index.ts @@ -0,0 +1,4 @@ +export interface IPredictRequest { + name: string; + dataInput: { variableInputId: number; value: number }[]; +} diff --git a/src/interfaces/responses/Result/index.ts b/src/interfaces/responses/Result/index.ts index 9dbb2a4..964755f 100644 --- a/src/interfaces/responses/Result/index.ts +++ b/src/interfaces/responses/Result/index.ts @@ -3,3 +3,14 @@ import { IBaseResponse } from "../base"; export interface IResultListResponse extends IBaseResponse {} export interface IResultDetailResponse extends IBaseResponse {} + +export interface IPredict { + resultId: number; + name: string; + predict: { + variableOutputId: number; + variableOutputName: string; + value: number; + }[]; +} +export interface IResultPredictResponse extends IBaseResponse {} diff --git a/src/pages/Result/Create.tsx b/src/pages/Result/Create.tsx new file mode 100644 index 0000000..e60e295 --- /dev/null +++ b/src/pages/Result/Create.tsx @@ -0,0 +1,152 @@ +import { Breadcrumbs } from "@/common/breadcrumb"; +import ModalResultPredict from "@/components/modules/Result/ModalResultPredict"; +import Button from "@/components/shared/Button/Button"; +import ButtonAction from "@/components/shared/Form/ButtonActions"; +import LoaderCenter from "@/components/shared/Loader/LoaderCenter"; +import { getRequiredMessage } from "@/helpers/form"; +import { modalConfirm } from "@/helpers/modal-confirm"; +import { useFormUtility } from "@/hooks/useFormUtility"; +import { IPredictRequest } from "@/interfaces/requests/Result"; +import { IPredict } from "@/interfaces/responses/Result"; +import MainLayout from "@/layouts/MainLayout"; +import { Routes } from "@/routes/routes"; +import { useResultPredictMutation } from "@/services/mutation/Result"; +import { useVariableInputQueryIndex } from "@/services/query/VariableInput"; +import { Card, Flex, Form, Input, InputNumber } from "antd"; +import React from "react"; +import * as yup from "yup"; + +const schema = yup.object().shape({ + name: yup.string().required(getRequiredMessage("Name")), + dataInput: yup + .array() + .of( + yup.object().shape({ + variableInputId: yup + .number() + .required(getRequiredMessage("Variable Input")) + .defined(), + value: yup + .number() + .required(getRequiredMessage("Value")) + .defined(), + }) + ) + .required(), +}); + +const ResultCreate: React.FC = () => { + const { form, yupSync } = useFormUtility({ schema }); + const queryVariableInput = useVariableInputQueryIndex(); + + const [openModalResultPredict, setOpenModalResultPredict] = + React.useState(false); + const [resultPredict, setResultPredict] = React.useState(null); + + const mutationPredict = useResultPredictMutation({ + onSuccess: ({ data }) => { + setOpenModalResultPredict(true); + setResultPredict(data); + }, + }); + + const closeModal = () => { + setOpenModalResultPredict(false); + setResultPredict(null); + }; + + const onFinish = async () => { + await form.validateFields(); + modalConfirm({ + onOk: () => { + const dataSubmitted = form.getFieldsValue(); + mutationPredict.mutate(dataSubmitted as IPredictRequest); + }, + }); + }; + + const getLabelOutput = (idContext) => { + return queryVariableInput?.data?.data?.find( + ({ id }) => id === idContext + )?.name; + }; + + const formListDataInput = ( + + {(fields) => + fields.map((field) => ( + + + + + + + )) + } + + ); + + return ( + + + {queryVariableInput.isLoading || + queryVariableInput.isFetching ? ( + + ) : ( +
({ variableInputId: id, value: 0 }) + ), + }} + > + + + + + {formListDataInput} + + + Cancel + , + , + ]} + /> + + )} +
+ +
+ ); +}; + +export default ResultCreate; diff --git a/src/pages/Result/index.tsx b/src/pages/Result/index.tsx index c130904..525b11b 100644 --- a/src/pages/Result/index.tsx +++ b/src/pages/Result/index.tsx @@ -1,4 +1,5 @@ import { Breadcrumbs } from "@/common/breadcrumb"; +import Button from "@/components/shared/Button/Button"; import LoaderCenter from "@/components/shared/Loader/LoaderCenter"; import RowActionButtons from "@/components/shared/Table/RowActionButtons"; import { parsingRoute } from "@/helpers/route"; @@ -38,7 +39,13 @@ const ResultIndex: React.FC = () => { ]; return ( - + + Create + + } + > {query.isLoading || query.isFetching ? ( ) : ( diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 11e5427..f1413f5 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -32,6 +32,7 @@ const MatrixCompareEditByInputPage = React.lazy( ); const ResultIndexPage = React.lazy(() => import("@/pages/Result")); +const ResultCreatePage = React.lazy(() => import("@/pages/Result/Create")); export const withSuspense = (component: ReactNode) => { return }>{component}; @@ -134,4 +135,12 @@ export const router = createBrowserRouter([ ), }, + { + path: Routes.ResultCreate, + element: withSuspense( + + + + ), + }, ]); diff --git a/src/services/apis/Result/index.ts b/src/services/apis/Result/index.ts index 316ac39..0076d29 100644 --- a/src/services/apis/Result/index.ts +++ b/src/services/apis/Result/index.ts @@ -1,6 +1,7 @@ import { AxiosError } from "axios"; import { axiosAuthInstance } from ".."; import { EndpointApi } from "@/routes/routes"; +import { IPredictRequest } from "@/interfaces/requests/Result"; export const resultGetByUserLogin = async () => { try { @@ -12,3 +13,15 @@ export const resultGetByUserLogin = async () => { throw (error as AxiosError).response.data; } }; + +export const resultPredict = async (payload: IPredictRequest) => { + try { + const response = await axiosAuthInstance.post( + EndpointApi.ResultPredict, + payload + ); + return response.data; + } catch (error) { + throw (error as AxiosError).response.data; + } +}; diff --git a/src/services/mutation/Result/index.ts b/src/services/mutation/Result/index.ts new file mode 100644 index 0000000..f9620a0 --- /dev/null +++ b/src/services/mutation/Result/index.ts @@ -0,0 +1,19 @@ +import { IPredictRequest } from "@/interfaces/requests/Result"; +import { IResultPredictResponse } from "@/interfaces/responses/Result"; +import { IBaseResponse } from "@/interfaces/responses/base"; +import { resultPredict } from "@/services/apis/Result"; +import { UseMutationOptions, useMutation } from "@tanstack/react-query"; + +export const useResultPredictMutation = ( + options?: UseMutationOptions< + IResultPredictResponse, + IBaseResponse, + IPredictRequest + > +) => { + return useMutation({ + mutationKey: ["result-predict"], + mutationFn: (payload) => resultPredict(payload), + ...options, + }); +}; From e78a156f46a67834b43b1c0540d4d371aeb5fc3d Mon Sep 17 00:00:00 2001 From: ridhlab Date: Tue, 2 Jan 2024 16:48:42 +0700 Subject: [PATCH 3/3] feat: detail predict --- src/interfaces/entities/Results/index.ts | 2 +- src/interfaces/responses/Result/index.ts | 10 +++ src/pages/Result/Detail.tsx | 77 ++++++++++++++++++++++++ src/routes/index.tsx | 9 +++ src/routes/routes.ts | 2 + src/services/apis/Result/index.ts | 23 +++++++ src/services/query/Result/index.ts | 39 +++++++++++- 7 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 src/pages/Result/Detail.tsx diff --git a/src/interfaces/entities/Results/index.ts b/src/interfaces/entities/Results/index.ts index 8fcb5c8..ec16f8c 100644 --- a/src/interfaces/entities/Results/index.ts +++ b/src/interfaces/entities/Results/index.ts @@ -7,5 +7,5 @@ export interface IResult extends IBaseEntity { name: string; userId: string; user?: IUser; - inputValues: IInputValue[]; + inputValues?: IInputValue[]; } diff --git a/src/interfaces/responses/Result/index.ts b/src/interfaces/responses/Result/index.ts index 964755f..8b935e0 100644 --- a/src/interfaces/responses/Result/index.ts +++ b/src/interfaces/responses/Result/index.ts @@ -14,3 +14,13 @@ export interface IPredict { }[]; } export interface IResultPredictResponse extends IBaseResponse {} + +export interface IResultPredictByResultIdResponse + extends IBaseResponse<{ + result: IResult; + predict: { + variableOutputId: number; + variableOutputName: string; + value: number; + }[]; + }> {} diff --git a/src/pages/Result/Detail.tsx b/src/pages/Result/Detail.tsx new file mode 100644 index 0000000..efbdf0d --- /dev/null +++ b/src/pages/Result/Detail.tsx @@ -0,0 +1,77 @@ +import { Breadcrumbs } from "@/common/breadcrumb"; +import LoaderCenter from "@/components/shared/Loader/LoaderCenter"; +import { getDecimalPlace } from "@/helpers/number"; +import { IInputValue } from "@/interfaces/entities/InputValues"; +import MainLayout from "@/layouts/MainLayout"; +import { useResultPredictByresultId } from "@/services/query/Result"; +import { Card, Col, Row, Table } from "antd"; +import { ColumnsType } from "antd/es/table"; +import React from "react"; +import { useParams } from "react-router-dom"; + +const DetailResultPredict: React.FC = () => { + const { id } = useParams(); + const query = useResultPredictByresultId(id); + const columnsResultPredict: ColumnsType = [ + { + title: "Variable Output", + dataIndex: "variableOutputName", + }, + { + title: "Value", + dataIndex: "value", + render: (value) => getDecimalPlace(value, 3), + }, + ]; + + const columnsVariableInput: ColumnsType = [ + { + title: "Variable Input", + render: (_, record) => record?.variableInput?.name, + }, + { + title: "Value", + render: (_, record) => record?.value, + }, + ]; + + const mainContent = ( + +
+
+ + +
+ + + ); + + return ( + + + {query?.isLoading || query?.isFetching ? ( + + ) : ( + mainContent + )} + + + ); +}; + +export default DetailResultPredict; diff --git a/src/routes/index.tsx b/src/routes/index.tsx index f1413f5..4549f7f 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -33,6 +33,7 @@ const MatrixCompareEditByInputPage = React.lazy( const ResultIndexPage = React.lazy(() => import("@/pages/Result")); const ResultCreatePage = React.lazy(() => import("@/pages/Result/Create")); +const ResultDetailPage = React.lazy(() => import("@/pages/Result/Detail")); export const withSuspense = (component: ReactNode) => { return }>{component}; @@ -143,4 +144,12 @@ export const router = createBrowserRouter([ ), }, + { + path: Routes.ResultDetail, + element: withSuspense( + + + + ), + }, ]); diff --git a/src/routes/routes.ts b/src/routes/routes.ts index 40984e3..3b0ad1e 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -44,4 +44,6 @@ export enum EndpointApi { ResultByUserLogin = "/result-by-user-login", ResultDetail = "/result/:id", ResultPredict = "/predict", + + ResultPredictGetByResultID = "/result-predict/:resultId", } diff --git a/src/services/apis/Result/index.ts b/src/services/apis/Result/index.ts index 0076d29..7c99f66 100644 --- a/src/services/apis/Result/index.ts +++ b/src/services/apis/Result/index.ts @@ -2,6 +2,7 @@ import { AxiosError } from "axios"; import { axiosAuthInstance } from ".."; import { EndpointApi } from "@/routes/routes"; import { IPredictRequest } from "@/interfaces/requests/Result"; +import { parsingRoute } from "@/helpers/route"; export const resultGetByUserLogin = async () => { try { @@ -14,6 +15,28 @@ export const resultGetByUserLogin = async () => { } }; +export const resultGetById = async (id) => { + try { + const response = await axiosAuthInstance.get( + parsingRoute(EndpointApi.ResultDetail, { id }) + ); + return response.data; + } catch (error) { + throw (error as AxiosError).response.data; + } +}; + +export const resultPredictByResultId = async (resultId) => { + try { + const response = await axiosAuthInstance.get( + parsingRoute(EndpointApi.ResultPredictGetByResultID, { resultId }) + ); + return response.data; + } catch (error) { + throw (error as AxiosError).response.data; + } +}; + export const resultPredict = async (payload: IPredictRequest) => { try { const response = await axiosAuthInstance.post( diff --git a/src/services/query/Result/index.ts b/src/services/query/Result/index.ts index a1bdbf7..50e3a05 100644 --- a/src/services/query/Result/index.ts +++ b/src/services/query/Result/index.ts @@ -1,6 +1,14 @@ -import { IResultListResponse } from "@/interfaces/responses/Result"; +import { + IResultDetailResponse, + IResultListResponse, + IResultPredictByResultIdResponse, +} from "@/interfaces/responses/Result"; import { IBaseResponse } from "@/interfaces/responses/base"; -import { resultGetByUserLogin } from "@/services/apis/Result"; +import { + resultGetById, + resultGetByUserLogin, + resultPredictByResultId, +} from "@/services/apis/Result"; import { UseQueryOptions, useQuery } from "@tanstack/react-query"; export const useResultByUserLoginQuery = ( @@ -12,3 +20,30 @@ export const useResultByUserLoginQuery = ( ...options, }); }; + +export const useResultByIdQuery = ( + id, + options?: UseQueryOptions> +) => { + return useQuery({ + queryKey: ["result-by-id", id], + queryFn: () => resultGetById(id), + enabled: !!id, + ...options, + }); +}; + +export const useResultPredictByresultId = ( + id, + options?: UseQueryOptions< + IResultPredictByResultIdResponse, + IBaseResponse + > +) => { + return useQuery({ + queryKey: ["result-predict-by-result-id", id], + queryFn: () => resultPredictByResultId(id), + enabled: !!id, + ...options, + }); +};