From 43cf277044729d5fa49cf475ca1d31e0bfcbf231 Mon Sep 17 00:00:00 2001 From: Neven Dyulgerov Date: Wed, 29 Nov 2023 03:32:11 +0200 Subject: [PATCH] Update time column in inputs table to show date in age or UTC format (#74) Co-authored-by: nevendiulgerov --- .../src/app/applications/[address]/page.tsx | 24 ++------ apps/web/src/app/page.tsx | 31 ++-------- apps/web/src/components/inputRow.tsx | 24 +++++--- apps/web/src/components/inputsTable.tsx | 52 ++++++++++++++++ apps/web/test/components/inputRow.test.tsx | 59 +++++++++++++++++++ apps/web/test/components/inputsTable.test.tsx | 42 +++++++++++++ 6 files changed, 177 insertions(+), 55 deletions(-) create mode 100644 apps/web/src/components/inputsTable.tsx create mode 100644 apps/web/test/components/inputRow.test.tsx create mode 100644 apps/web/test/components/inputsTable.test.tsx diff --git a/apps/web/src/app/applications/[address]/page.tsx b/apps/web/src/app/applications/[address]/page.tsx index 271ed4e4e..c3a4dfc78 100644 --- a/apps/web/src/app/applications/[address]/page.tsx +++ b/apps/web/src/app/applications/[address]/page.tsx @@ -6,7 +6,6 @@ import { Pagination, Select, Stack, - Table, Text, Title, } from "@mantine/core"; @@ -16,12 +15,12 @@ import { pathOr } from "ramda"; import { FC, useEffect, useState } from "react"; import { TbInbox } from "react-icons/tb"; import Address from "../../../components/address"; -import InputRow from "../../../components/inputRow"; import { InputOrderByInput, useInputsQuery } from "../../../graphql"; import { limitBounds, usePaginationParams, } from "../../../hooks/usePaginationParams"; +import InputsTable from "../../../components/inputsTable"; export type ApplicationPageProps = { params: { address: string }; @@ -42,6 +41,7 @@ const ApplicationPage: FC = ({ params }) => { const [activePage, setActivePage] = useState( page > totalPages ? totalPages : page, ); + const inputs = data?.inputsConnection.edges.map((edge) => edge.node) ?? []; const { scrollIntoView, targetRef } = useScrollIntoView({ duration: 700, offset: 150, @@ -82,24 +82,8 @@ const ApplicationPage: FC = ({ params }) => { updateParams(pageN, limit); }} /> - - - - From - - To - Method - Index - Age - Data - - - - {data?.inputsConnection.edges.map(({ node: input }) => ( - - ))} - -
+ + diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 4a22ebe69..18c738d72 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -7,22 +7,16 @@ import { Pagination, Select, Stack, - Table, Text, Title, } from "@mantine/core"; import { FC, useEffect, useState } from "react"; import { TbInbox } from "react-icons/tb"; - import { useScrollIntoView } from "@mantine/hooks"; import { pathOr } from "ramda"; -import InputRow from "../components/inputRow"; -import { - InputOrderByInput, - useInputsQuery, - useStatsQuery, -} from "../graphql/index"; +import { InputOrderByInput, useInputsQuery, useStatsQuery } from "../graphql"; import { limitBounds, usePaginationParams } from "../hooks/usePaginationParams"; +import InputsTable from "../components/inputsTable"; const Explorer: FC = (props) => { const [{ limit, page }, updateParams] = usePaginationParams(); @@ -40,6 +34,7 @@ const Explorer: FC = (props) => { const [activePage, setActivePage] = useState( page > totalPages ? totalPages : page, ); + const inputs = data?.inputsConnection.edges.map((edge) => edge.node) ?? []; const { scrollIntoView, targetRef } = useScrollIntoView({ duration: 700, @@ -82,24 +77,8 @@ const Explorer: FC = (props) => { updateParams(pageN, limit); }} /> - - - - From - - To - Method - Index - Age - Data - - - - {data?.inputsConnection.edges.map(({ node: input }) => ( - - ))} - -
+ + diff --git a/apps/web/src/components/inputRow.tsx b/apps/web/src/components/inputRow.tsx index c5949f668..a0a7f3758 100644 --- a/apps/web/src/components/inputRow.tsx +++ b/apps/web/src/components/inputRow.tsx @@ -10,8 +10,9 @@ import { InputItemFragment } from "../graphql"; import Address from "./address"; import InputDetailsView from "./inputDetailsView"; -export type InputCardProps = { +export type InputRowProps = { input: InputItemFragment; + timeType: string; }; export type MethodResolver = ( @@ -32,14 +33,8 @@ const methodResolver: MethodResolver = (input) => { return undefined; }; -const InputRow: FC = ({ input }) => { +const InputRow: FC = ({ input, timeType }) => { const [opened, { toggle }] = useDisclosure(false); - const age = prettyMilliseconds(Date.now() - input.timestamp * 1000, { - unitCount: 2, - secondsDecimalDigits: 0, - verbose: true, - }); - const from = input.msgSender as AddressType; const to = input.application.id as AddressType; @@ -98,7 +93,18 @@ const InputRow: FC = ({ input }) => { {input.index} - {age} ago + + {timeType === "age" + ? `${prettyMilliseconds( + Date.now() - input.timestamp * 1000, + { + unitCount: 2, + secondsDecimalDigits: 0, + verbose: true, + }, + )} ago` + : new Date(input.timestamp * 1000).toISOString()} + diff --git a/apps/web/src/components/inputsTable.tsx b/apps/web/src/components/inputsTable.tsx new file mode 100644 index 000000000..de58dde5b --- /dev/null +++ b/apps/web/src/components/inputsTable.tsx @@ -0,0 +1,52 @@ +"use client"; +import { Button, Table } from "@mantine/core"; +import { FC, useCallback, useState } from "react"; +import InputRow from "../components/inputRow"; +import type { InputItemFragment } from "../graphql"; + +export interface InputsTableProps { + inputs: InputItemFragment[]; +} + +const InputsTable: FC = ({ inputs }) => { + const [timeType, setTimeType] = useState("age"); + + const onChangeTimeColumnType = useCallback(() => { + setTimeType((timeType) => (timeType === "age" ? "timestamp" : "age")); + }, []); + + return ( + + + + From + + To + Method + Index + + + + Data + + + + {inputs.map((input) => ( + + ))} + +
+ ); +}; + +export default InputsTable; diff --git a/apps/web/test/components/inputRow.test.tsx b/apps/web/test/components/inputRow.test.tsx new file mode 100644 index 000000000..86d5c5d88 --- /dev/null +++ b/apps/web/test/components/inputRow.test.tsx @@ -0,0 +1,59 @@ +import { describe, it } from "vitest"; +import type { FC } from "react"; +import { render, screen } from "@testing-library/react"; +import InputRow, { InputRowProps } from "../../src/components/inputRow"; +import { withMantineTheme } from "../utils/WithMantineTheme"; +import { Table } from "@mantine/core"; +import prettyMilliseconds from "pretty-ms"; + +const TableComponent: FC = (props) => ( + + + + +
+); + +const Component = withMantineTheme(TableComponent); + +const defaultProps: InputRowProps = { + input: { + id: "0xdb84080e7d2b4654a7e384de851a6cf7281643de-1", + application: { + id: "0xdb84080e7d2b4654a7e384de851a6cf7281643de", + }, + index: 1, + payload: "0x68656c6c6f2032", + msgSender: "0x8a12cf75000cd2e73ab16469826838d5f137f444", + timestamp: 1700593992, + transactionHash: + "0x4ad73b8f46dc16bc27d75b3f8f584e8785a8cb6fdf97a6c2a5a5dcfbda3e75c0", + erc20Deposit: null, + }, + timeType: "age", +}; + +describe("InputRow component", () => { + it("should display correct age", () => { + render(); + + const age = `${prettyMilliseconds( + Date.now() - defaultProps.input.timestamp * 1000, + { + unitCount: 2, + secondsDecimalDigits: 0, + verbose: true, + }, + )} ago`; + expect(screen.getByText(age)).toBeInTheDocument(); + }); + + it("should display correct timestamp in UTC format", () => { + render(); + + const timestamp = new Date( + defaultProps.input.timestamp * 1000, + ).toISOString(); + expect(screen.getByText(timestamp)).toBeInTheDocument(); + }); +}); diff --git a/apps/web/test/components/inputsTable.test.tsx b/apps/web/test/components/inputsTable.test.tsx new file mode 100644 index 000000000..2110642a1 --- /dev/null +++ b/apps/web/test/components/inputsTable.test.tsx @@ -0,0 +1,42 @@ +import { describe, it } from "vitest"; +import { fireEvent, render, screen } from "@testing-library/react"; +import InputsTable, { + InputsTableProps, +} from "../../src/components/inputsTable"; +import { withMantineTheme } from "../utils/WithMantineTheme"; + +const Component = withMantineTheme(InputsTable); + +const defaultProps: InputsTableProps = { + inputs: [ + { + id: "0xdb84080e7d2b4654a7e384de851a6cf7281643de-1", + application: { + id: "0xdb84080e7d2b4654a7e384de851a6cf7281643de", + }, + index: 1, + payload: "0x68656c6c6f2032", + msgSender: "0x8a12cf75000cd2e73ab16469826838d5f137f444", + timestamp: 1700593992, + transactionHash: + "0x4ad73b8f46dc16bc27d75b3f8f584e8785a8cb6fdf97a6c2a5a5dcfbda3e75c0", + erc20Deposit: null, + }, + ], +}; + +describe("InputsTable component", () => { + it("should display time column with age label", () => { + render(); + expect(screen.getByText("Age")).toBeInTheDocument(); + }); + + it("should display time column with timestamp label", () => { + render(); + + const timeTypeButton = screen.getByText("Age"); + fireEvent.click(timeTypeButton); + + expect(screen.getByText("Timestamp (UTC)")).toBeInTheDocument(); + }); +});