From 9ba363e5fc278bfa7c2daf70f241e31f2400d694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Jim=C3=A9nez=20Aguilera?= Date: Wed, 4 Sep 2024 19:22:02 +0200 Subject: [PATCH] feat: Create `Slot` filter (#525) * refactor(web): create Option type for Dropdown options * chore(web): update changeset * refactor(web): use label in Dropdown selected option display * refactor(web): update Dropdown component main filename * refactor(web): remove nested /components folder in Dropdown * feat(web): render value when no label in Dropdown * feat(web): add prefix slot to Dropdown option * feat(web): add filter panel with rollup filter in PaginatedTable * chore(web): update changeset * refactor(web): rename main PaginedTable filename * refactor(web): rename main FilersPanel filename * refactor(web): update RollupFilter component to fetch rollups from API * feat(web): add filters to query params * fix(web): pages number calculation in PaginatedTable * fix(web): pages number calculation in PaginatedTable * feat(web): install React DatePicker Tailwind library * feat(web): add full width prop to Button * feat(web): add custom height prop to Dropdown * feat(web): add Timestamp filter to FilterPanel * chore(web): update changeset * feat(web): add timestamp filters to URL * fix(web): Skeleton custom height not working * refactor(web): simplify dropdowns options * refactor(web): reduce contitions to filter in Filters * fix(web): reset filters when all are reset after being filtered * feat(web): create BlockNumber filter * feat(web): move block number validations to filters form * chore(web): update changeset * fix(web): reset filters when all are reset after being filtered * feat(web): add clear button for filter * fix(web): clear/filter buttons responsiveness * fix(web): clear/filter buttons responsiveness for desktop * fix(web): fix block number filter responsiveness * refactor(web): decouple Filters from PaginatedTable * feat(web): add slot filter to blocks table * fix(web): fix filters reponsiveness * chore(web): update changeset * fix(web): display block number and slot range filters on the same row on small screens * chore(web): remove unused leftover files * fix(web): align number range input values to the left * refactor(web): change number range in filters to close interval --------- Co-authored-by: Luis Herasme Co-authored-by: PJColombo --- .changeset/red-years-deny.md | 5 ++ .../web/src/components/Filters/SlotFilter.tsx | 18 +++++ apps/web/src/components/Filters/index.tsx | 75 +++++++++++++++---- .../components/Inputs/NumberRangeInput.tsx | 2 +- .../src/components/PaginatedTable/index.tsx | 10 ++- apps/web/src/hooks/useQueryParams.ts | 18 ++++- apps/web/src/pages/blobs.tsx | 16 +++- apps/web/src/pages/blocks.tsx | 16 +++- apps/web/src/pages/txs.tsx | 16 +++- packages/api/src/middlewares/withFilters.ts | 6 +- pnpm-lock.yaml | 12 +-- 11 files changed, 156 insertions(+), 38 deletions(-) create mode 100644 .changeset/red-years-deny.md create mode 100644 apps/web/src/components/Filters/SlotFilter.tsx diff --git a/.changeset/red-years-deny.md b/.changeset/red-years-deny.md new file mode 100644 index 000000000..253692e72 --- /dev/null +++ b/.changeset/red-years-deny.md @@ -0,0 +1,5 @@ +--- +"@blobscan/web": minor +--- + +Added slot range filter to blocks view diff --git a/apps/web/src/components/Filters/SlotFilter.tsx b/apps/web/src/components/Filters/SlotFilter.tsx new file mode 100644 index 000000000..6980bfac5 --- /dev/null +++ b/apps/web/src/components/Filters/SlotFilter.tsx @@ -0,0 +1,18 @@ +import type { FC } from "react"; + +import type { NumberRangeInputProps } from "../Inputs/NumberRangeInput"; +import { NumberRangeInput } from "../Inputs/NumberRangeInput"; + +type SlotFilterProps = Pick; + +export const SlotFilter: FC = function (props) { + return ( + + ); +}; diff --git a/apps/web/src/components/Filters/index.tsx b/apps/web/src/components/Filters/index.tsx index f5cd69c7e..b4ab02af3 100644 --- a/apps/web/src/components/Filters/index.tsx +++ b/apps/web/src/components/Filters/index.tsx @@ -12,12 +12,14 @@ import type { Option } from "../Dropdown"; import type { NumberRange } from "../Inputs/NumberRangeInput"; import { BlockNumberFilter } from "./BlockNumberFilter"; import { ROLLUP_OPTIONS, RollupFilter } from "./RollupFilter"; +import { SlotFilter } from "./SlotFilter"; import { TimestampFilter } from "./TimestampFilter"; type FiltersState = { rollup: Option | null; timestampRange: DateRangeType | null; blockNumberRange: NumberRange | null; + slotRange: NumberRange | null; }; type ClearAction = { @@ -41,6 +43,7 @@ const INIT_STATE: FiltersState = { startDate: null, }, blockNumberRange: null, + slotRange: null, }; function reducer( @@ -68,11 +71,15 @@ export const Filters: FC = function () { const queryParams = useQueryParams(); const [filters, dispatch] = useReducer(reducer, INIT_STATE); const disableClear = - !filters.rollup && !filters.timestampRange && !filters.blockNumberRange; + !filters.rollup && + !filters.timestampRange?.endDate && + !filters.timestampRange?.startDate && + !filters.blockNumberRange && + !filters.slotRange; const handleFilter = () => { const query: UrlObject["query"] = {}; - const { rollup, timestampRange, blockNumberRange } = filters; + const { rollup, timestampRange, blockNumberRange, slotRange } = filters; if (rollup) { if (rollup.value === "null") { @@ -106,6 +113,18 @@ export const Filters: FC = function () { } } + if (slotRange) { + const { start, end } = slotRange; + + if (start) { + query.startSlot = start; + } + + if (end) { + query.endSlot = end; + } + } + router.push({ pathname: router.pathname, query, @@ -113,8 +132,16 @@ export const Filters: FC = function () { }; useEffect(() => { - const { rollup, from, startDate, endDate, startBlock, endBlock } = - queryParams; + const { + rollup, + from, + startDate, + endDate, + startBlock, + endBlock, + startSlot, + endSlot, + } = queryParams; const newFilters: Partial = {}; if (rollup || from) { @@ -141,6 +168,13 @@ export const Filters: FC = function () { }; } + if (startSlot || endSlot) { + newFilters.slotRange = { + start: startSlot, + end: endSlot, + }; + } + dispatch({ type: "UPDATE", payload: newFilters }); }, [queryParams]); @@ -167,16 +201,29 @@ export const Filters: FC = function () { } /> -
- - dispatch({ - type: "UPDATE", - payload: { blockNumberRange: newBlockNumberRange }, - }) - } - /> +
+
+ + dispatch({ + type: "UPDATE", + payload: { blockNumberRange: newBlockNumberRange }, + }) + } + /> +
+
+ + dispatch({ + type: "UPDATE", + payload: { slotRange: newSlotRange }, + }) + } + /> +
diff --git a/apps/web/src/components/Inputs/NumberRangeInput.tsx b/apps/web/src/components/Inputs/NumberRangeInput.tsx index b4d7d19d5..5466571bd 100644 --- a/apps/web/src/components/Inputs/NumberRangeInput.tsx +++ b/apps/web/src/components/Inputs/NumberRangeInput.tsx @@ -34,7 +34,7 @@ export const NumberRangeInput: React.FC = ({ type={type} variant="filled" placeholder="Start" - className={`rounded-l-lg rounded-r-none border-r-controlBorder-light text-right text-sm dark:border-r-border-dark ${className}`} + className={`rounded-l-lg rounded-r-none border-r-controlBorder-light text-sm dark:border-r-border-dark ${className}`} onChange={(newStartValue) => onChange({ start: newStartValue, diff --git a/apps/web/src/components/PaginatedTable/index.tsx b/apps/web/src/components/PaginatedTable/index.tsx index f102a2c36..16f320905 100644 --- a/apps/web/src/components/PaginatedTable/index.tsx +++ b/apps/web/src/components/PaginatedTable/index.tsx @@ -1,5 +1,5 @@ import { useCallback } from "react"; -import type { FC } from "react"; +import type { FC, ReactNode } from "react"; import { useRouter } from "next/router"; import Skeleton from "react-loading-skeleton"; @@ -10,6 +10,7 @@ import type { PaginationProps } from "~/components/Pagination"; import { Pagination } from "~/components/Pagination"; import type { TableProps } from "~/components/Table"; import { Table } from "~/components/Table"; +import type { Rollup } from "~/types"; const DEFAULT_TABLE_EMPTY_STATE = "No items"; const PAGE_SIZES_OPTIONS: DropdownProps["options"] = [ @@ -20,6 +21,12 @@ const PAGE_SIZES_OPTIONS: DropdownProps["options"] = [ ]; const DEFAULT_ROW_SKELETON_HEIGHT = 22; +export interface PaginatedTableQueryFilters { + rollup: Rollup; + startDate: Date; + endDate: Date; +} + type PaginationData = { page: number; pageSize: number; @@ -31,6 +38,7 @@ export type PaginatedTableProps = { isExpandable?: boolean; paginationData: PaginationData; rowSkeletonHeight?: string | number; + tableTopSlot?: ReactNode; } & Pick; const getRowsSkeleton = ( diff --git a/apps/web/src/hooks/useQueryParams.ts b/apps/web/src/hooks/useQueryParams.ts index fed861b45..09bffdca5 100644 --- a/apps/web/src/hooks/useQueryParams.ts +++ b/apps/web/src/hooks/useQueryParams.ts @@ -14,6 +14,8 @@ type QueryParams = { endDate?: Date; startBlock?: number; endBlock?: number; + startSlot?: number; + endSlot?: number; }; const DEFAULT_INITIAL_PAGE_SIZE = 50; @@ -31,8 +33,18 @@ export function useQueryParams() { return; } - const { from, p, ps, rollup, startDate, endDate, startBlock, endBlock } = - router.query; + const { + from, + p, + ps, + rollup, + startDate, + endDate, + startBlock, + endBlock, + startSlot, + endSlot, + } = router.query; setQueryParams({ from: (from as string)?.toLowerCase(), @@ -49,6 +61,8 @@ export function useQueryParams() { endDate: endDate ? new Date(endDate as string) : undefined, startBlock: parseInt(startBlock as string) || undefined, endBlock: parseInt(endBlock as string) || undefined, + startSlot: parseInt(startSlot as string) || undefined, + endSlot: parseInt(endSlot as string) || undefined, }); }, [router]); diff --git a/apps/web/src/pages/blobs.tsx b/apps/web/src/pages/blobs.tsx index 274f411f6..18086c65d 100644 --- a/apps/web/src/pages/blobs.tsx +++ b/apps/web/src/pages/blobs.tsx @@ -51,8 +51,18 @@ const BLOBS_TABLE_HEADERS = [ ]; const Blobs: NextPage = function () { - const { from, p, ps, rollup, startDate, endDate, startBlock, endBlock } = - useQueryParams(); + const { + from, + p, + ps, + rollup, + startDate, + endDate, + startBlock, + endBlock, + startSlot, + endSlot, + } = useQueryParams(); const { data, error, isLoading } = api.blob.getAll.useQuery({ p, ps, @@ -62,6 +72,8 @@ const Blobs: NextPage = function () { endDate, startBlock, endBlock, + startSlot, + endSlot, }); const { blobs, totalBlobs } = data || {}; diff --git a/apps/web/src/pages/blocks.tsx b/apps/web/src/pages/blocks.tsx index df13891db..5bd6bec9c 100644 --- a/apps/web/src/pages/blocks.tsx +++ b/apps/web/src/pages/blocks.tsx @@ -57,8 +57,18 @@ export const BLOCKS_TABLE_HEADERS = [ ]; const Blocks: NextPage = function () { - const { from, p, ps, rollup, startDate, endDate, startBlock, endBlock } = - useQueryParams(); + const { + from, + p, + ps, + rollup, + startDate, + endDate, + startBlock, + endBlock, + startSlot, + endSlot, + } = useQueryParams(); const { data: rawBlocksData, isLoading, @@ -72,6 +82,8 @@ const Blocks: NextPage = function () { endDate, startBlock, endBlock, + startSlot, + endSlot, }); const blocksData = useMemo(() => { if (!rawBlocksData) { diff --git a/apps/web/src/pages/txs.tsx b/apps/web/src/pages/txs.tsx index e3967bbdc..21c4c60d1 100644 --- a/apps/web/src/pages/txs.tsx +++ b/apps/web/src/pages/txs.tsx @@ -83,8 +83,18 @@ export const TRANSACTIONS_TABLE_HEADERS = [ ]; const Txs: NextPage = function () { - const { from, p, ps, rollup, startDate, endDate, startBlock, endBlock } = - useQueryParams(); + const { + from, + p, + ps, + rollup, + startDate, + endDate, + startBlock, + endBlock, + startSlot, + endSlot, + } = useQueryParams(); const { data: rawTxsData, @@ -102,6 +112,8 @@ const Txs: NextPage = function () { endDate, startBlock, endBlock, + startSlot, + endSlot, expand: "block,blob", }); const txsData = useMemo(() => { diff --git a/packages/api/src/middlewares/withFilters.ts b/packages/api/src/middlewares/withFilters.ts index 9fef9feda..d16760016 100644 --- a/packages/api/src/middlewares/withFilters.ts +++ b/packages/api/src/middlewares/withFilters.ts @@ -12,7 +12,7 @@ import { type NumberRange = { gte?: number; - lt?: number; + lte?: number; }; type DateRange = { @@ -106,7 +106,7 @@ export const withFilters = t.middleware(({ next, input = {} }) => { if (blockRangeExists) { filters.blockNumber = { - lt: endBlock, + lte: endBlock, gte: startBlock, }; } @@ -120,7 +120,7 @@ export const withFilters = t.middleware(({ next, input = {} }) => { if (slotRangeExists) { filters.blockSlot = { - lt: endSlot, + lte: endSlot, gte: startSlot, }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c7d7a781..6e8977137 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -112,7 +112,7 @@ importers: version: 0.5.13(tailwindcss@3.4.6(ts-node@10.9.2(@types/node@18.19.41)(typescript@5.5.3))) autoprefixer: specifier: ^10.4.14 - version: 10.4.19(postcss@8.4.38) + version: 10.4.19(postcss@8.4.39) clsx: specifier: ^2.1.0 version: 2.1.1 @@ -10350,16 +10350,6 @@ snapshots: asynckit@0.4.0: {} - autoprefixer@10.4.19(postcss@8.4.38): - dependencies: - browserslist: 4.23.0 - caniuse-lite: 1.0.30001614 - fraction.js: 4.3.7 - normalize-range: 0.1.2 - picocolors: 1.0.0 - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - autoprefixer@10.4.19(postcss@8.4.39): dependencies: browserslist: 4.23.0