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 (
+
+
+ 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
+};