From 808f687a51df37afe664d1747cb67419a4cdcd80 Mon Sep 17 00:00:00 2001 From: ap-justin <89639563+ap-justin@users.noreply.github.com> Date: Sat, 9 Dec 2023 15:48:02 +0800 Subject: [PATCH] banking applicatiosn page --- .../BankingApplications.tsx | 76 ++++++++++++ src/pages/BankingApplications/Filter/Form.tsx | 49 ++++++++ .../Filter/StatusDropdown.tsx | 16 +++ .../BankingApplications/Filter/constants.ts | 8 ++ .../BankingApplications/Filter/index.tsx | 78 ++++++++++++ .../BankingApplications/Filter/schema.ts | 12 ++ src/pages/BankingApplications/Filter/types.ts | 7 ++ src/pages/BankingApplications/LoadMoreBtn.tsx | 28 +++++ src/pages/BankingApplications/Table.tsx | 116 ++++++++++++++++++ src/pages/BankingApplications/index.ts | 1 + src/pages/BankingApplications/types.ts | 10 ++ .../BankingApplications/usePagination.ts | 67 ++++++++++ src/services/aws/banking-applications.ts | 14 ++- src/types/aws/ap/applications.ts | 9 +- 14 files changed, 486 insertions(+), 5 deletions(-) create mode 100644 src/pages/BankingApplications/BankingApplications.tsx create mode 100644 src/pages/BankingApplications/Filter/Form.tsx create mode 100644 src/pages/BankingApplications/Filter/StatusDropdown.tsx create mode 100644 src/pages/BankingApplications/Filter/constants.ts create mode 100644 src/pages/BankingApplications/Filter/index.tsx create mode 100644 src/pages/BankingApplications/Filter/schema.ts create mode 100644 src/pages/BankingApplications/Filter/types.ts create mode 100644 src/pages/BankingApplications/LoadMoreBtn.tsx create mode 100644 src/pages/BankingApplications/Table.tsx create mode 100644 src/pages/BankingApplications/index.ts create mode 100644 src/pages/BankingApplications/types.ts create mode 100644 src/pages/BankingApplications/usePagination.ts diff --git a/src/pages/BankingApplications/BankingApplications.tsx b/src/pages/BankingApplications/BankingApplications.tsx new file mode 100644 index 0000000000..6a75670158 --- /dev/null +++ b/src/pages/BankingApplications/BankingApplications.tsx @@ -0,0 +1,76 @@ +import withAuth from "contexts/Auth"; +import Icon from "components/Icon"; +import QueryLoader from "components/QueryLoader"; +import Filter from "./Filter"; +import Table from "./Table"; +import usePagination from "./usePagination"; + +function BankingApplications() { + const { + data, + hasMore, + isError, + isLoading, + isLoadingNextPage, + query, + loadNextPage, + onQueryChange, + setParams, + isFetching, + } = usePagination(); + + const isLoadingOrError = isLoading || isLoadingNextPage || isError; + + return ( +
+

+ Banking Applications +

+
+ + onQueryChange(e.target.value)} + /> +
+ + + {(applications) => ( +
+ + + )} + + + ); +} +export default withAuth(BankingApplications, ["ap-admin"]); diff --git a/src/pages/BankingApplications/Filter/Form.tsx b/src/pages/BankingApplications/Filter/Form.tsx new file mode 100644 index 0000000000..9a6d798d13 --- /dev/null +++ b/src/pages/BankingApplications/Filter/Form.tsx @@ -0,0 +1,49 @@ +import { Popover } from "@headlessui/react"; +import { FC, FormEventHandler } from "react"; +import { FormValues } from "./types"; +import Icon from "components/Icon"; +import { Field } from "components/form"; +import StatusDropdown from "./StatusDropdown"; + +type Props = { + submit: FormEventHandler; + onReset: FormEventHandler; + classes?: string; +}; + +const Form: FC = ({ onReset, submit, classes = "" }) => { + return ( + +
+ Filters + + + +
+ name="endowmentID" label="Endowment ID" /> + + +
+

Filter by

+ + +
+
+ ); +}; +export default Form; diff --git a/src/pages/BankingApplications/Filter/StatusDropdown.tsx b/src/pages/BankingApplications/Filter/StatusDropdown.tsx new file mode 100644 index 0000000000..6dd4aee979 --- /dev/null +++ b/src/pages/BankingApplications/Filter/StatusDropdown.tsx @@ -0,0 +1,16 @@ +import { FormValues as FV } from "./types"; +import { Selector } from "components/Selector"; +import { statuses } from "./constants"; + +export default function StatusDropdown({ classes = "" }) { + return ( +
+ + + name="status" + classes={{ button: "dark:bg-blue-d6" }} + options={statuses} + /> +
+ ); +} diff --git a/src/pages/BankingApplications/Filter/constants.ts b/src/pages/BankingApplications/Filter/constants.ts new file mode 100644 index 0000000000..55042e21ea --- /dev/null +++ b/src/pages/BankingApplications/Filter/constants.ts @@ -0,0 +1,8 @@ +import { BankingApplicationStatus } from "types/aws"; +import { OptionType } from "types/components"; + +export const statuses: OptionType[] = [ + { label: "Rejected", value: "rejected" }, + { label: "Under Review", value: "under-review" }, + { label: "Approved", value: "approved" }, +]; diff --git a/src/pages/BankingApplications/Filter/index.tsx b/src/pages/BankingApplications/Filter/index.tsx new file mode 100644 index 0000000000..b492d0bd29 --- /dev/null +++ b/src/pages/BankingApplications/Filter/index.tsx @@ -0,0 +1,78 @@ +import { Popover } from "@headlessui/react"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { FormEventHandler, useRef } from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import { FormValues as FV } from "./types"; +import { BankingApplicationsQueryParams } from "types/aws"; +import Icon, { DrawerIcon } from "components/Icon"; +import { cleanObject } from "helpers/cleanObject"; +import Form from "./Form"; +import { schema } from "./schema"; + +type Props = { + classes?: string; + setParams: React.Dispatch< + React.SetStateAction + >; + isDisabled: boolean; +}; + +export default function Filter({ setParams, classes = "", isDisabled }: Props) { + const buttonRef = useRef(null); + + const methods = useForm({ + mode: "onChange", + reValidateMode: "onChange", + resolver: yupResolver(schema), + defaultValues: { + endowmentID: "", + status: { label: "Under Review", value: "under-review" }, + }, + }); + + const { handleSubmit, reset } = methods; + + async function submit(data: FV) { + setParams( + cleanObject({ + status: data.status.value, + endowmentID: +data.endowmentID, + requestor: "bg-admin", + }) + ); + buttonRef.current?.click(); + } + + const onReset: FormEventHandler = () => { + reset(); + setParams({ requestor: "bg-admin" }); + buttonRef.current?.click(); + }; + return ( + + + {({ open }) => ( + <> + +
+ Filter +
+ + + )} +
+ + +
+
+
+ ); +} diff --git a/src/pages/BankingApplications/Filter/schema.ts b/src/pages/BankingApplications/Filter/schema.ts new file mode 100644 index 0000000000..5872a6dacc --- /dev/null +++ b/src/pages/BankingApplications/Filter/schema.ts @@ -0,0 +1,12 @@ +import { ObjectSchema, number, object } from "yup"; +import { FormValues } from "./types"; +import { SchemaShape } from "schemas/types"; +import { optionType } from "schemas/shape"; + +export const schema = object>({ + endowmentID: number() + .typeError("must be a number") + .integer("must be integer") + .min(1), + status: optionType(), +}) as ObjectSchema; diff --git a/src/pages/BankingApplications/Filter/types.ts b/src/pages/BankingApplications/Filter/types.ts new file mode 100644 index 0000000000..724a7a2fdf --- /dev/null +++ b/src/pages/BankingApplications/Filter/types.ts @@ -0,0 +1,7 @@ +import { BankingApplicationStatus } from "types/aws"; +import { OptionType } from "types/components"; + +export type FormValues = { + endowmentID: string; + status: OptionType; +}; diff --git a/src/pages/BankingApplications/LoadMoreBtn.tsx b/src/pages/BankingApplications/LoadMoreBtn.tsx new file mode 100644 index 0000000000..2b08ea05c6 --- /dev/null +++ b/src/pages/BankingApplications/LoadMoreBtn.tsx @@ -0,0 +1,28 @@ +import LoaderRing from "components/LoaderRing"; + +export default function LoadMoreBtn({ + onLoadMore, + disabled, + isLoading, +}: { + onLoadMore(): void; + disabled: boolean; + isLoading: boolean; +}) { + return ( + + ); +} diff --git a/src/pages/BankingApplications/Table.tsx b/src/pages/BankingApplications/Table.tsx new file mode 100644 index 0000000000..aeb83cf42e --- /dev/null +++ b/src/pages/BankingApplications/Table.tsx @@ -0,0 +1,116 @@ +import { Link } from "react-router-dom"; +import { TableProps } from "./types"; +import { BankingApplicationStatus } from "types/aws"; +import Icon from "components/Icon"; +import TableSection, { Cells } from "components/TableSection"; +import { appRoutes } from "constants/routes"; +import LoadMoreBtn from "./LoadMoreBtn"; + +export default function Table({ + applications, + classes = "", + disabled, + isLoading, + hasMore, + onLoadMore, +}: TableProps) { + return ( +
+ + + <>ID + <>Endowment + <>Bank name + <>Account number + <>Currency + <>Bank Statement + <>Date created + <>Status + Details + + + + {applications + .map((row) => ( + + <>{row.wiseRecipientID} + <>{row.endowmentId} + <>{new Date(row.dateCreated).toLocaleDateString()} + <>{row.status} + + + + + + )) + .concat( + hasMore ? ( + + ) : ( + [] + ) + )} + +
+ + + +
+ ); +} + +const bg: { [key in BankingApplicationStatus]: string } = { + approved: "bg-green", + "under-review": "bg-gray-d1", + rejected: "bg-red", +}; + +const text: { [key in BankingApplicationStatus]: string } = { + "under-review": "Under review", + rejected: "Rejected", + approved: "Approved", +}; + +function Status({ status }: { status: BankingApplicationStatus }) { + return ( +

+ {text[status]} +

+ ); +} diff --git a/src/pages/BankingApplications/index.ts b/src/pages/BankingApplications/index.ts new file mode 100644 index 0000000000..c67d449391 --- /dev/null +++ b/src/pages/BankingApplications/index.ts @@ -0,0 +1 @@ +export { default } from "./BankingApplications"; diff --git a/src/pages/BankingApplications/types.ts b/src/pages/BankingApplications/types.ts new file mode 100644 index 0000000000..17bc61c7e0 --- /dev/null +++ b/src/pages/BankingApplications/types.ts @@ -0,0 +1,10 @@ +import { BankingApplication } from "types/aws"; + +export type TableProps = { + applications: BankingApplication[]; + classes?: string; + onLoadMore(): void; + hasMore: boolean; + disabled: boolean; + isLoading: boolean; +}; diff --git a/src/pages/BankingApplications/usePagination.ts b/src/pages/BankingApplications/usePagination.ts new file mode 100644 index 0000000000..dcfa78e0b3 --- /dev/null +++ b/src/pages/BankingApplications/usePagination.ts @@ -0,0 +1,67 @@ +import { useState } from "react"; +import { BankingApplicationsQueryParams } from "types/aws"; +import { + updateBankingApplicationsQueryData, + useBankingApplicationsQuery, + useLazyBankingApplicationsQuery, +} from "services/aws/banking-applications"; +import { useSetter } from "store/accessors"; + +export default function usePagination() { + const dispatch = useSetter(); + + const [query, setQuery] = useState(""); + + const [params, setParams] = useState({ + requestor: "bg-admin", + }); + + const queryState = useBankingApplicationsQuery(params); + + const { isLoading, isFetching, isError, data, originalArgs } = queryState; + + const [loadMore, { isLoading: isLoadingNextPage, isError: isErrorNextPage }] = + useLazyBankingApplicationsQuery(); + + async function loadNextPage() { + //button is hidden when there's no more + if ( + data?.nextPageKey && + originalArgs /** cards won't even show if no initial query is made */ + ) { + const { data: newPageRes } = await loadMore({ + ...originalArgs, + nextPageKey: data.nextPageKey, + }); + + if (newPageRes) { + //pessimistic update to original cache data + dispatch( + updateBankingApplicationsQueryData( + "bankingApplications", + originalArgs, + (prevResult) => { + prevResult.items.push(...newPageRes.items); + prevResult.nextPageKey = newPageRes.nextPageKey; + } + ) + ); + } + } + } + + const hasMore = !!data?.nextPageKey; + + return { + data, + hasMore, + isError: isError || isErrorNextPage, + isLoading, + isFetching, + isLoadingNextPage, + query, + loadNextPage, + onQueryChange: setQuery, + setParams, + }; +} diff --git a/src/services/aws/banking-applications.ts b/src/services/aws/banking-applications.ts index 929c31cf28..55949c0f35 100644 --- a/src/services/aws/banking-applications.ts +++ b/src/services/aws/banking-applications.ts @@ -1,5 +1,5 @@ import { - BankingApplication, + BankingApplicationsPage, BankingApplicationsQueryParams, NewBankingApplication, } from "types/aws"; @@ -17,7 +17,7 @@ const bankingApplications = aws.injectEndpoints({ }, }), bankingApplications: builder.query< - BankingApplication[], + BankingApplicationsPage, BankingApplicationsQueryParams >({ query: (params) => { @@ -30,5 +30,11 @@ const bankingApplications = aws.injectEndpoints({ }), }); -export const { useNewBankingApplicationMutation, useBankingApplicationsQuery } = - bankingApplications; +export const { + useNewBankingApplicationMutation, + useBankingApplicationsQuery, + endpoints: { + bankingApplications: { useLazyQuery: useLazyBankingApplicationsQuery }, + }, + util: { updateQueryData: updateBankingApplicationsQueryData }, +} = bankingApplications; diff --git a/src/types/aws/ap/applications.ts b/src/types/aws/ap/applications.ts index 1a47a9229e..80d984e392 100644 --- a/src/types/aws/ap/applications.ts +++ b/src/types/aws/ap/applications.ts @@ -14,9 +14,10 @@ export type BankingApplicationsQueryParams = { status?: BankingApplicationStatus; endowmentID?: number; requestor: "endowment" | "bg-admin"; + nextPageKey?: string; //base64 encoded keys }; -type BankingApplicationStatus = "under-review" | "approved" | "rejected"; +export type BankingApplicationStatus = "under-review" | "approved" | "rejected"; type BaseBankingApplication = { wiseRecipientID: string; @@ -33,4 +34,10 @@ export type BankingApplication = { defaultPriorityNum: number; priorityNum: number; status: BankingApplicationStatus; + dateCreated: string; //ISODateString } & BaseBankingApplication; + +export type BankingApplicationsPage = { + items: BankingApplication[]; + nextPageKey?: string; //base64 encoded string +};