From ffbdd2d89ccfce55ab75441321bf576306f8f133 Mon Sep 17 00:00:00 2001 From: Daria Mikhailova Date: Mon, 20 Jan 2025 18:14:56 +1300 Subject: [PATCH 01/14] added Expires soon warning on everything that is not Reservation and expires within 7 days --- packages/page-coretime/src/Row.tsx | 36 +++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/page-coretime/src/Row.tsx b/packages/page-coretime/src/Row.tsx index f88a14b9ae69..0771807ae4fa 100644 --- a/packages/page-coretime/src/Row.tsx +++ b/packages/page-coretime/src/Row.tsx @@ -6,7 +6,7 @@ import type { ChainWorkTaskInformation, LegacyLease } from '@polkadot/react-hook import React from 'react'; -import { ParaLink, styled, Tag } from '@polkadot/react-components'; +import { MarkWarning, ParaLink, styled, Tag } from '@polkadot/react-components'; import { ChainRenewalStatus, CoreTimeTypes } from '@polkadot/react-hooks/constants'; import { BN, formatBalance, formatNumber } from '@polkadot/util'; @@ -33,15 +33,35 @@ const StyledCell = styled.td<{ $p: boolean }>` && { background-color: ${({ $p }) => ($p ? '#F9FAFB' : undefined)}; } + height: 55px; `; -function Row ({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease, regionBegin, regionEnd }: Props): React.ReactElement { +const StyledMarkWarning = styled(MarkWarning)` + width: fit-content; + margin: 0; + display: inline-block; + vertical-align: middle; + &.mark { + margin: 0 0 0 1rem; + display: inline; + } +`; + +const EXPIRES_IN_DAYS = 7 + +function Row({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease, regionBegin, regionEnd }: Props): React.ReactElement { const chainRegionEnd = (chainRecord.renewalStatus === ChainRenewalStatus.Renewed ? regionEnd : regionBegin); const targetTimeslice = lease?.until || chainRegionEnd; const showEstimates = !!targetTimeslice && Object.values(CoreTimeTypes)[chainRecord.type] !== CoreTimeTypes.Reservation; const { coretimeInfo, get } = useCoretimeContext(); + const estimatedTime = showEstimates && get && coretimeInfo && + estimateTime(targetTimeslice, get.blocks.relay(lastCommittedTimeslice), coretimeInfo?.constants?.relay); + + const isWithinWeek = estimatedTime && new Date(estimatedTime).getTime() - Date.now() < EXPIRES_IN_DAYS * 24 * 60 * 60 * 1000; + const isReservation = chainRecord.type === CoreTimeTypes.Reservation; + return ( {id} @@ -63,7 +83,17 @@ function Row ({ chainRecord, highlight = false, id, lastCommittedTimeslice, leas {showEstimates && get && coretimeInfo && estimateTime(targetTimeslice, get.blocks.relay(lastCommittedTimeslice), coretimeInfo?.constants?.relay)} + style={{ whiteSpace: 'nowrap' }} + > + + {estimatedTime} + {!!isWithinWeek && !isReservation && ( + + )} + + Date: Tue, 28 Jan 2025 18:09:25 +1300 Subject: [PATCH 02/14] added subscan links to the table, link for the project and for the block --- packages/apps-config/src/links/subscan.ts | 25 +++++++++- .../page-coretime/src/ParachainsTable.tsx | 3 +- packages/page-coretime/src/Row.tsx | 18 ++++++-- packages/react-components/src/ParaLink.tsx | 46 ++++++++++++++----- 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/packages/apps-config/src/links/subscan.ts b/packages/apps-config/src/links/subscan.ts index a5e3939dfb93..47d7891488ab 100644 --- a/packages/apps-config/src/links/subscan.ts +++ b/packages/apps-config/src/links/subscan.ts @@ -10,28 +10,41 @@ export const Subscan: ExternalDef = { chains: { Acala: 'acala', 'Acala Mandala TC5': 'acala-testnet', + Ajuna: 'ajuna', 'Ajuna Polkadot': 'ajuna', 'Aleph Zero': 'alephzero', 'Aleph Zero Testnet': 'alephzero-testnet', Altair: 'altair', + AssetHub: 'assethub-polkadot', + Autonomys: 'autonomys', + People: 'people-polkadot', Astar: 'astar', 'Bajun Kusama': 'bajun', Basilisk: 'basilisk', Bifrost: 'bifrost-kusama', 'Bifrost Polkadot': 'bifrost', + BridgeHub: 'bridgehub-polkadot', 'Calamari Parachain': 'calamari', Centrifuge: 'centrifuge', ChainX: 'chainx', + Clover: 'clover', + Collectives: 'collectives-polkadot', 'Composable Finance': 'composable', 'Continuum Network': 'continuum', + Continuum: 'continuum', + Coretime: 'coretime-polkadot', Crab2: 'crab', Creditcoin: 'creditcoin', 'Creditcoin3 Testnet': 'creditcoin3-testnet', Crust: 'crust', + 'Crust Network': 'crust-parachain', 'Crust Shadow': 'shadow', Darwinia2: 'darwinia', + Darwinia: 'darwinia', Dock: 'dock', 'Dolphin Parachain Testnet': 'dolphin', + 'Energy Web X': 'energywebx', + 'Humanode': 'humanode', 'Humanode Mainnet': 'humanode', Hydration: 'hydration', 'Integritee Network (Kusama)': 'integritee', @@ -44,21 +57,29 @@ export const Subscan: ExternalDef = { Kusama: 'kusama', 'Kusama Asset Hub': 'assethub-kusama', 'Mangata Kusama Mainnet': 'mangatax', + Manta: 'manta', 'Moonbase Alpha': 'moonbase', Moonbeam: 'moonbeam', Moonriver: 'moonriver', + Mythos: 'mythos', NeuroWeb: 'neuroweb', 'NeuroWeb Testnet': 'neuroweb-testnet', 'Nodle Parachain': 'nodle', + 'Nodle': 'nodle', 'OPAL by UNIQUE': 'opal', 'Paseo Testnet': 'paseo', + Peaq: 'peaq', + Pendulum: 'pendulum', Phala: 'phala', + 'Phala Network': 'phala', Picasso: 'picasso', 'Pioneer Network': 'pioneer', - Polkadex: 'polkadex', + Polimec: 'polimec', + Polkadex: 'polkadex-parachain', Polkadot: 'polkadot', 'Polkadot Asset Hub': 'assethub-polkadot', Polymesh: 'polymesh', + 'Polymesh Mainnet': 'polymesh', 'Polymesh Testnet': 'polymesh-testnet', 'QUARTZ by UNIQUE': 'quartz', Robonomics: 'robonomics', @@ -72,7 +93,9 @@ export const Subscan: ExternalDef = { Stafi: 'stafi', 'Turing Network': 'turing', UNIQUE: 'unique', + Unique: 'unique', 'Vara Network': 'vara', + 'Vara': 'vara', Westend: 'westend', Zeitgeist: 'zeitgeist', kintsugi: 'kintsugi' diff --git a/packages/page-coretime/src/ParachainsTable.tsx b/packages/page-coretime/src/ParachainsTable.tsx index 27a56081537e..2fcefe56db1f 100644 --- a/packages/page-coretime/src/ParachainsTable.tsx +++ b/packages/page-coretime/src/ParachainsTable.tsx @@ -13,7 +13,7 @@ interface Props { coretimeInfo: CoretimeInformation } -function ParachainsTable ({ coretimeInfo }: Props): React.ReactElement { +function ParachainsTable({ coretimeInfo }: Props): React.ReactElement { const { t } = useTranslation(); const headerRef = useRef<([React.ReactNode?, string?, number?] | false)[]>([ [t('parachains'), 'start'], @@ -24,6 +24,7 @@ function ParachainsTable ({ coretimeInfo }: Props): React.ReactElement { [t('end'), 'start media--1000'], [t('renewal'), 'start media--1200'], [t('renewal price'), 'start media--1200'], + [t('links'), 'start media--800'], [t('other cores'), 'end'] ]); diff --git a/packages/page-coretime/src/Row.tsx b/packages/page-coretime/src/Row.tsx index 0771807ae4fa..0b9376007d68 100644 --- a/packages/page-coretime/src/Row.tsx +++ b/packages/page-coretime/src/Row.tsx @@ -4,14 +4,16 @@ import type { FlagColor } from '@polkadot/react-components/types'; import type { ChainWorkTaskInformation, LegacyLease } from '@polkadot/react-hooks/types'; -import React from 'react'; +import React, { useMemo } from 'react'; import { MarkWarning, ParaLink, styled, Tag } from '@polkadot/react-components'; import { ChainRenewalStatus, CoreTimeTypes } from '@polkadot/react-hooks/constants'; import { BN, formatBalance, formatNumber } from '@polkadot/util'; + import { estimateTime } from './utils/index.js'; import { useCoretimeContext } from './CoretimeContext.js'; +import { ParaLinkType } from '@polkadot/react-components/ParaLink'; interface Props { id: number @@ -53,8 +55,8 @@ function Row({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease const chainRegionEnd = (chainRecord.renewalStatus === ChainRenewalStatus.Renewed ? regionEnd : regionBegin); const targetTimeslice = lease?.until || chainRegionEnd; const showEstimates = !!targetTimeslice && Object.values(CoreTimeTypes)[chainRecord.type] !== CoreTimeTypes.Reservation; - const { coretimeInfo, get } = useCoretimeContext(); + const lastBlock = useMemo(() => get?.blocks.relay(targetTimeslice), [get, targetTimeslice]) const estimatedTime = showEstimates && get && coretimeInfo && estimateTime(targetTimeslice, get.blocks.relay(lastCommittedTimeslice), coretimeInfo?.constants?.relay); @@ -79,7 +81,7 @@ function Row({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease {showEstimates && get && formatNumber(get.blocks.relay(targetTimeslice)).toString()} + >{showEstimates && lastBlock && {formatNumber(lastBlock)}} {chainRecord?.renewal ? formatBalance(chainRecord.renewal?.price.toString()) : ''} + {
+ +
+ +
+
}
+ {highlight && }
diff --git a/packages/react-components/src/ParaLink.tsx b/packages/react-components/src/ParaLink.tsx index b78f1fd3f45e..a9c1c76a7497 100644 --- a/packages/react-components/src/ParaLink.tsx +++ b/packages/react-components/src/ParaLink.tsx @@ -9,42 +9,64 @@ import { useParaEndpoints } from '@polkadot/react-hooks'; import ChainImg from './ChainImg.js'; import { styled } from './styled.js'; +import Icon from './Icon.js'; +import { Subscan } from '@polkadot/apps-config/links/subscan'; + +export enum ParaLinkType { + PJS = 'pjs', + HOME = 'home', + SUBSCAN = 'subscan' +} interface Props { className?: string; id: BN; + showLogo?: boolean; + type?: ParaLinkType; } -function ParaLink ({ className, id }: Props): React.ReactElement | null { +function ParaLink({ className, id, type = ParaLinkType.PJS, showLogo = true }: Props): React.ReactElement | null { const endpoints = useParaEndpoints(id); const links = useMemo( () => endpoints.filter(({ isDisabled, isUnreachable }) => !isDisabled && !isUnreachable), [endpoints] ); - if (!endpoints.length) { return null; } - const { text, ui, value } = links.length + const { text, ui, value, homepage } = links.length ? links[links.length - 1] : endpoints[0]; + const subscanUrl = text && Subscan.chains[text?.toString()] && Subscan.create(Subscan.chains[text?.toString()], '', '').toString() + return ( - - {links.length - ? ( - {text} - ) - : text + } + {links.length ? + ( + <> + {type === ParaLinkType.SUBSCAN && !!subscanUrl && + Subscan + } + {type === ParaLinkType.HOME && homepage && + + } + {type === ParaLinkType.PJS && {text}} + + ) : type === ParaLinkType.PJS ? text : null } ); From 9db44e5f626357810767b15c88f182b264e1843b Mon Sep 17 00:00:00 2001 From: Daria Mikhailova Date: Tue, 28 Jan 2025 19:48:44 +1300 Subject: [PATCH 03/14] filter by parachain id --- packages/apps-config/src/links/subscan.ts | 14 ++--- .../page-coretime/src/Overview/Filters.tsx | 54 ++++++++++++++++ .../page-coretime/src/ParachainsTable.tsx | 62 ++++++++++++------- packages/page-coretime/src/Row.tsx | 34 ++++++---- packages/react-components/src/ParaLink.tsx | 36 +++++++---- 5 files changed, 147 insertions(+), 53 deletions(-) create mode 100644 packages/page-coretime/src/Overview/Filters.tsx diff --git a/packages/apps-config/src/links/subscan.ts b/packages/apps-config/src/links/subscan.ts index 47d7891488ab..7d911bfe115f 100644 --- a/packages/apps-config/src/links/subscan.ts +++ b/packages/apps-config/src/links/subscan.ts @@ -16,9 +16,8 @@ export const Subscan: ExternalDef = { 'Aleph Zero Testnet': 'alephzero-testnet', Altair: 'altair', AssetHub: 'assethub-polkadot', - Autonomys: 'autonomys', - People: 'people-polkadot', Astar: 'astar', + Autonomys: 'autonomys', 'Bajun Kusama': 'bajun', Basilisk: 'basilisk', Bifrost: 'bifrost-kusama', @@ -30,8 +29,8 @@ export const Subscan: ExternalDef = { Clover: 'clover', Collectives: 'collectives-polkadot', 'Composable Finance': 'composable', - 'Continuum Network': 'continuum', Continuum: 'continuum', + 'Continuum Network': 'continuum', Coretime: 'coretime-polkadot', Crab2: 'crab', Creditcoin: 'creditcoin', @@ -39,12 +38,12 @@ export const Subscan: ExternalDef = { Crust: 'crust', 'Crust Network': 'crust-parachain', 'Crust Shadow': 'shadow', - Darwinia2: 'darwinia', Darwinia: 'darwinia', + Darwinia2: 'darwinia', Dock: 'dock', 'Dolphin Parachain Testnet': 'dolphin', 'Energy Web X': 'energywebx', - 'Humanode': 'humanode', + Humanode: 'humanode', 'Humanode Mainnet': 'humanode', Hydration: 'hydration', 'Integritee Network (Kusama)': 'integritee', @@ -64,12 +63,13 @@ export const Subscan: ExternalDef = { Mythos: 'mythos', NeuroWeb: 'neuroweb', 'NeuroWeb Testnet': 'neuroweb-testnet', + Nodle: 'nodle', 'Nodle Parachain': 'nodle', - 'Nodle': 'nodle', 'OPAL by UNIQUE': 'opal', 'Paseo Testnet': 'paseo', Peaq: 'peaq', Pendulum: 'pendulum', + People: 'people-polkadot', Phala: 'phala', 'Phala Network': 'phala', Picasso: 'picasso', @@ -94,8 +94,8 @@ export const Subscan: ExternalDef = { 'Turing Network': 'turing', UNIQUE: 'unique', Unique: 'unique', + Vara: 'vara', 'Vara Network': 'vara', - 'Vara': 'vara', Westend: 'westend', Zeitgeist: 'zeitgeist', kintsugi: 'kintsugi' diff --git a/packages/page-coretime/src/Overview/Filters.tsx b/packages/page-coretime/src/Overview/Filters.tsx new file mode 100644 index 000000000000..f5700c56768e --- /dev/null +++ b/packages/page-coretime/src/Overview/Filters.tsx @@ -0,0 +1,54 @@ +// Copyright 2017-2025 @polkadot/app-coretime authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React, { useCallback, useState } from 'react'; + +import { Input } from '@polkadot/react-components'; + +import { useTranslation } from '../translate.js'; + +interface Props { + data: any[]; + onFilter: (data: any[]) => void +} + +function Filters ({ data, onFilter }: Props): React.ReactElement { + const { t } = useTranslation(); + const [searchValue, setSearchValue] = useState(''); + const onInputChange = useCallback((v: string) => { + setSearchValue(v); + + if (!v.trim()) { + onFilter(data); + + return; + } + + const filteredData = data.filter((item: any) => + item.toString().toLowerCase().includes(v.toLowerCase()) + ); + + if (!filteredData.length) { + onFilter([]); + + return; + } + + onFilter(filteredData); + }, [data, onFilter]); + + return ( +
+
+ +
+
); +} + +export default Filters; diff --git a/packages/page-coretime/src/ParachainsTable.tsx b/packages/page-coretime/src/ParachainsTable.tsx index 2fcefe56db1f..d0ef5c3e923a 100644 --- a/packages/page-coretime/src/ParachainsTable.tsx +++ b/packages/page-coretime/src/ParachainsTable.tsx @@ -1,11 +1,12 @@ // Copyright 2017-2025 @polkadot/app-coretime authors & contributors // SPDX-License-Identifier: Apache-2.0 -import React, { useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { Table } from '@polkadot/react-components'; import { type CoretimeInformation } from '@polkadot/react-hooks/types'; +import Filters from './Overview/Filters.js'; import ParachainTableRow from './ParachainTableRow.js'; import { useTranslation } from './translate.js'; @@ -13,7 +14,7 @@ interface Props { coretimeInfo: CoretimeInformation } -function ParachainsTable({ coretimeInfo }: Props): React.ReactElement { +function ParachainsTable ({ coretimeInfo }: Props): React.ReactElement { const { t } = useTranslation(); const headerRef = useRef<([React.ReactNode?, string?, number?] | false)[]>([ [t('parachains'), 'start'], @@ -28,28 +29,43 @@ function ParachainsTable({ coretimeInfo }: Props): React.ReactElement { [t('other cores'), 'end'] ]); + const [taskIds, setTaskIds] = useState([]); + + useEffect(() => { + if (coretimeInfo?.taskIds) { + setTaskIds(coretimeInfo?.taskIds); + } + }, [coretimeInfo?.taskIds]); + return ( - - - {coretimeInfo?.taskIds?.map((taskId: number) => { - const chain = coretimeInfo.chainInfo[taskId]; - - return ( - - ); - })} - -
+ <> + { + setTaskIds(filteredData); + }} + /> + + {taskIds?.map((taskId: number) => { + const chain = coretimeInfo.chainInfo[taskId]; + + return ( + + ); + })} + +
+ ); } diff --git a/packages/page-coretime/src/Row.tsx b/packages/page-coretime/src/Row.tsx index 0b9376007d68..ce0b28b11408 100644 --- a/packages/page-coretime/src/Row.tsx +++ b/packages/page-coretime/src/Row.tsx @@ -7,13 +7,12 @@ import type { ChainWorkTaskInformation, LegacyLease } from '@polkadot/react-hook import React, { useMemo } from 'react'; import { MarkWarning, ParaLink, styled, Tag } from '@polkadot/react-components'; +import { ParaLinkType } from '@polkadot/react-components/ParaLink'; import { ChainRenewalStatus, CoreTimeTypes } from '@polkadot/react-hooks/constants'; import { BN, formatBalance, formatNumber } from '@polkadot/util'; - import { estimateTime } from './utils/index.js'; import { useCoretimeContext } from './CoretimeContext.js'; -import { ParaLinkType } from '@polkadot/react-components/ParaLink'; interface Props { id: number @@ -49,14 +48,14 @@ const StyledMarkWarning = styled(MarkWarning)` } `; -const EXPIRES_IN_DAYS = 7 +const EXPIRES_IN_DAYS = 7; -function Row({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease, regionBegin, regionEnd }: Props): React.ReactElement { +function Row ({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease, regionBegin, regionEnd }: Props): React.ReactElement { const chainRegionEnd = (chainRecord.renewalStatus === ChainRenewalStatus.Renewed ? regionEnd : regionBegin); const targetTimeslice = lease?.until || chainRegionEnd; const showEstimates = !!targetTimeslice && Object.values(CoreTimeTypes)[chainRecord.type] !== CoreTimeTypes.Reservation; const { coretimeInfo, get } = useCoretimeContext(); - const lastBlock = useMemo(() => get?.blocks.relay(targetTimeslice), [get, targetTimeslice]) + const lastBlock = useMemo(() => get?.blocks.relay(targetTimeslice), [get, targetTimeslice]); const estimatedTime = showEstimates && get && coretimeInfo && estimateTime(targetTimeslice, get.blocks.relay(lastCommittedTimeslice), coretimeInfo?.constants?.relay); @@ -81,7 +80,11 @@ function Row({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease {showEstimates && lastBlock && {formatNumber(lastBlock)}} + >{showEstimates && lastBlock && {formatNumber(lastBlock)}} {
- -
- -
-
}
- + +
+ +
+ } {highlight && } diff --git a/packages/react-components/src/ParaLink.tsx b/packages/react-components/src/ParaLink.tsx index a9c1c76a7497..e35ff1b6f161 100644 --- a/packages/react-components/src/ParaLink.tsx +++ b/packages/react-components/src/ParaLink.tsx @@ -5,12 +5,12 @@ import type { BN } from '@polkadot/util'; import React, { useMemo } from 'react'; +import { Subscan } from '@polkadot/apps-config/links/subscan'; import { useParaEndpoints } from '@polkadot/react-hooks'; import ChainImg from './ChainImg.js'; -import { styled } from './styled.js'; import Icon from './Icon.js'; -import { Subscan } from '@polkadot/apps-config/links/subscan'; +import { styled } from './styled.js'; export enum ParaLinkType { PJS = 'pjs', @@ -25,21 +25,22 @@ interface Props { type?: ParaLinkType; } -function ParaLink({ className, id, type = ParaLinkType.PJS, showLogo = true }: Props): React.ReactElement | null { +function ParaLink ({ className, id, showLogo = true, type = ParaLinkType.PJS }: Props): React.ReactElement | null { const endpoints = useParaEndpoints(id); const links = useMemo( () => endpoints.filter(({ isDisabled, isUnreachable }) => !isDisabled && !isUnreachable), [endpoints] ); + if (!endpoints.length) { return null; } - const { text, ui, value, homepage } = links.length + const { homepage, text, ui, value } = links.length ? links[links.length - 1] : endpoints[0]; - const subscanUrl = text && Subscan.chains[text?.toString()] && Subscan.create(Subscan.chains[text?.toString()], '', '').toString() + const subscanUrl = text && Subscan.chains[text?.toString()] && Subscan.create(Subscan.chains[text?.toString()], '', '').toString(); return ( @@ -49,13 +50,25 @@ function ParaLink({ className, id, type = ParaLinkType.PJS, showLogo = true }: P withoutHl /> } - {links.length ? - ( + {links.length + ? ( <> - {type === ParaLinkType.SUBSCAN && !!subscanUrl && - Subscan + {type === ParaLinkType.SUBSCAN && !!subscanUrl && + Subscan } - {type === ParaLinkType.HOME && homepage && + {type === ParaLinkType.HOME && homepage && {text}} - ) : type === ParaLinkType.PJS ? text : null + ) + : type === ParaLinkType.PJS ? text : null } ); From a1b5fbe01c4202a73cbbe0bd25ab7bd1017d9da6 Mon Sep 17 00:00:00 2001 From: Daria Mikhailova Date: Tue, 28 Jan 2025 20:10:59 +1300 Subject: [PATCH 04/14] filter by parachain name --- .../page-coretime/src/Overview/Filters.tsx | 117 ++++++++++++------ .../page-coretime/src/ParachainsTable.tsx | 11 +- 2 files changed, 84 insertions(+), 44 deletions(-) diff --git a/packages/page-coretime/src/Overview/Filters.tsx b/packages/page-coretime/src/Overview/Filters.tsx index f5700c56768e..c1b8d23125b9 100644 --- a/packages/page-coretime/src/Overview/Filters.tsx +++ b/packages/page-coretime/src/Overview/Filters.tsx @@ -1,54 +1,91 @@ // Copyright 2017-2025 @polkadot/app-coretime authors & contributors // SPDX-License-Identifier: Apache-2.0 -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useState, useMemo } from 'react'; import { Input } from '@polkadot/react-components'; import { useTranslation } from '../translate.js'; +import { useRelayEndpoints } from '@polkadot/react-hooks/useParaEndpoints'; interface Props { - data: any[]; - onFilter: (data: any[]) => void + data: number[]; + onFilter: (data: number[]) => void; } -function Filters ({ data, onFilter }: Props): React.ReactElement { - const { t } = useTranslation(); - const [searchValue, setSearchValue] = useState(''); - const onInputChange = useCallback((v: string) => { - setSearchValue(v); - - if (!v.trim()) { - onFilter(data); - - return; - } - - const filteredData = data.filter((item: any) => - item.toString().toLowerCase().includes(v.toLowerCase()) - ); - - if (!filteredData.length) { - onFilter([]); - - return; - } - - onFilter(filteredData); - }, [data, onFilter]); - - return ( -
-
- -
-
); +function Filters({ data, onFilter }: Props): React.ReactElement { + const { t } = useTranslation(); + const [searchValue, setSearchValue] = useState(''); + const endpoints = useRelayEndpoints(); + const [endPointsMap, setEndPointsMap] = useState>({}); + + useEffect(() => { + const endPointsMap = endpoints.reduce((acc: Record, endpoint) => { + if (endpoint?.text && endpoint.paraId) { + acc[endpoint.text.toString()] = endpoint.paraId; + } + return acc; + }, {}); + setEndPointsMap(endPointsMap); + }, [endpoints]); + + const filteredData = useMemo(() => { + if (!searchValue.trim()) { + return data; + } + + const searchLower = searchValue.toLowerCase(); + let results = data.filter((item) => + item.toString().toLowerCase().includes(searchLower) + ); + + if (!results.length) { + Object.entries(endPointsMap).forEach(([key, value]) => { + if (key.toLowerCase().includes(searchLower)) { + results.push(value); + } + }); + } + + return results; + }, [data, searchValue, endPointsMap]); + + useEffect(() => { + onFilter(filteredData); + }, [filteredData, onFilter]); + + const onInputChange = useCallback((v: string) => { + setSearchValue(v); + + if (!v.trim()) { + onFilter(data); + return; + } + + const searchLower = v.toLowerCase(); + const filteredData = [...new Set([ + ...data.filter((item) => item.toString().toLowerCase().includes(searchLower)), + ...Object.entries(endPointsMap) + .filter(([key]) => key.toLowerCase().includes(searchLower)) + .map(([, value]) => value) + ])]; + + onFilter(filteredData); + }, [data, endPointsMap, onFilter]); + + return ( +
+
+ +
+
); } export default Filters; diff --git a/packages/page-coretime/src/ParachainsTable.tsx b/packages/page-coretime/src/ParachainsTable.tsx index d0ef5c3e923a..697493faf816 100644 --- a/packages/page-coretime/src/ParachainsTable.tsx +++ b/packages/page-coretime/src/ParachainsTable.tsx @@ -14,7 +14,7 @@ interface Props { coretimeInfo: CoretimeInformation } -function ParachainsTable ({ coretimeInfo }: Props): React.ReactElement { +function ParachainsTable({ coretimeInfo }: Props): React.ReactElement { const { t } = useTranslation(); const headerRef = useRef<([React.ReactNode?, string?, number?] | false)[]>([ [t('parachains'), 'start'], @@ -41,9 +41,9 @@ function ParachainsTable ({ coretimeInfo }: Props): React.ReactElement { <> { - setTaskIds(filteredData); - }} + onFilter={(filteredData) => + setTaskIds(filteredData) + } /> { {taskIds?.map((taskId: number) => { const chain = coretimeInfo.chainInfo[taskId]; + if (!chain) { + return null; + } return ( Date: Tue, 28 Jan 2025 21:44:39 +1300 Subject: [PATCH 05/14] filter by type --- .../page-coretime/src/Overview/Filters.tsx | 87 ++++++++++++------- .../page-coretime/src/ParachainsTable.tsx | 1 + packages/page-coretime/src/Row.tsx | 38 ++++---- packages/page-coretime/src/utils/index.ts | 7 ++ 4 files changed, 81 insertions(+), 52 deletions(-) diff --git a/packages/page-coretime/src/Overview/Filters.tsx b/packages/page-coretime/src/Overview/Filters.tsx index c1b8d23125b9..b2fe706a7814 100644 --- a/packages/page-coretime/src/Overview/Filters.tsx +++ b/packages/page-coretime/src/Overview/Filters.tsx @@ -3,21 +3,47 @@ import React, { useCallback, useEffect, useState, useMemo } from 'react'; -import { Input } from '@polkadot/react-components'; +import { Dropdown, Input, Tag } from '@polkadot/react-components'; import { useTranslation } from '../translate.js'; import { useRelayEndpoints } from '@polkadot/react-hooks/useParaEndpoints'; +import { CoreTimeTypes } from '@polkadot/react-hooks/constants'; +import { coretimeTypeColours } from '../utils/index.js'; +import { FlagColor } from '@polkadot/react-components/types'; +import { ChainInformation } from '@polkadot/react-hooks/types'; interface Props { data: number[]; + chainInfo: Record; onFilter: (data: number[]) => void; } -function Filters({ data, onFilter }: Props): React.ReactElement { +function Filters({ data, chainInfo, onFilter }: Props): React.ReactElement { const { t } = useTranslation(); const [searchValue, setSearchValue] = useState(''); const endpoints = useRelayEndpoints(); const [endPointsMap, setEndPointsMap] = useState>({}); + const [selectedType, setSelectedType] = useState(''); + const coretimeTypes = Object.keys(CoreTimeTypes) + .filter(key => isNaN(Number(key))); + + const typeOptions = useMemo(() => { + return [ + { + value: '', + text: 'All' + }, + ...coretimeTypes.map((type) => ({ + value: CoreTimeTypes[type].toString(), + text: ( + + ) + })) + ]; + }, [coretimeTypes]); useEffect(() => { const endPointsMap = endpoints.reduce((acc: Record, endpoint) => { @@ -29,31 +55,6 @@ function Filters({ data, onFilter }: Props): React.ReactElement { setEndPointsMap(endPointsMap); }, [endpoints]); - const filteredData = useMemo(() => { - if (!searchValue.trim()) { - return data; - } - - const searchLower = searchValue.toLowerCase(); - let results = data.filter((item) => - item.toString().toLowerCase().includes(searchLower) - ); - - if (!results.length) { - Object.entries(endPointsMap).forEach(([key, value]) => { - if (key.toLowerCase().includes(searchLower)) { - results.push(value); - } - }); - } - - return results; - }, [data, searchValue, endPointsMap]); - - useEffect(() => { - onFilter(filteredData); - }, [filteredData, onFilter]); - const onInputChange = useCallback((v: string) => { setSearchValue(v); @@ -73,18 +74,44 @@ function Filters({ data, onFilter }: Props): React.ReactElement { onFilter(filteredData); }, [data, endPointsMap, onFilter]); + const onDropDownChange = useCallback((v: string) => { + setSelectedType(v); + if (v === 'all') { + onFilter(data); + return; + } + const filteredData = data + .filter((paraId) => { + const taskInfo = chainInfo[paraId].workTaskInfo; + if (taskInfo.length > 0) { + return taskInfo[0].type.toString() === v + } + return false; + }) + onFilter(filteredData); + }, [chainInfo, data, onFilter]); + return ( -
-
+
+
+ +
); } diff --git a/packages/page-coretime/src/ParachainsTable.tsx b/packages/page-coretime/src/ParachainsTable.tsx index 697493faf816..7230da602128 100644 --- a/packages/page-coretime/src/ParachainsTable.tsx +++ b/packages/page-coretime/src/ParachainsTable.tsx @@ -41,6 +41,7 @@ function ParachainsTable({ coretimeInfo }: Props): React.ReactElement { <> setTaskIds(filteredData) } diff --git a/packages/page-coretime/src/Row.tsx b/packages/page-coretime/src/Row.tsx index ce0b28b11408..14be8ad8230a 100644 --- a/packages/page-coretime/src/Row.tsx +++ b/packages/page-coretime/src/Row.tsx @@ -11,7 +11,7 @@ import { ParaLinkType } from '@polkadot/react-components/ParaLink'; import { ChainRenewalStatus, CoreTimeTypes } from '@polkadot/react-hooks/constants'; import { BN, formatBalance, formatNumber } from '@polkadot/util'; -import { estimateTime } from './utils/index.js'; +import { coretimeTypeColours, estimateTime } from './utils/index.js'; import { useCoretimeContext } from './CoretimeContext.js'; interface Props { @@ -24,12 +24,6 @@ interface Props { highlight?: boolean } -const colours: Record = { - [CoreTimeTypes.Reservation]: 'orange', - [CoreTimeTypes.Lease]: 'blue', - [CoreTimeTypes['Bulk Coretime']]: 'pink' -}; - const StyledCell = styled.td<{ $p: boolean }>` && { background-color: ${({ $p }) => ($p ? '#F9FAFB' : undefined)}; @@ -50,7 +44,7 @@ const StyledMarkWarning = styled(MarkWarning)` const EXPIRES_IN_DAYS = 7; -function Row ({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease, regionBegin, regionEnd }: Props): React.ReactElement { +function Row({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease, regionBegin, regionEnd }: Props): React.ReactElement { const chainRegionEnd = (chainRecord.renewalStatus === ChainRenewalStatus.Renewed ? regionEnd : regionBegin); const targetTimeslice = lease?.until || chainRegionEnd; const showEstimates = !!targetTimeslice && Object.values(CoreTimeTypes)[chainRecord.type] !== CoreTimeTypes.Reservation; @@ -73,7 +67,7 @@ function Row ({ chainRecord, highlight = false, id, lastCommittedTimeslice, leas {chainRecord?.workload?.core} @@ -81,10 +75,10 @@ function Row ({ chainRecord, highlight = false, id, lastCommittedTimeslice, leas $p={highlight} className='media--800' >{showEstimates && lastBlock && {formatNumber(lastBlock)}} + href={`https://polkadot.subscan.io/block/${lastBlock}`} + rel='noreferrer' + target='_blank' + >{formatNumber(lastBlock)}} {
+ +
-
- -
-
} +
+
} {highlight && } diff --git a/packages/page-coretime/src/utils/index.ts b/packages/page-coretime/src/utils/index.ts index c49a4accdcb3..4215263ecefd 100644 --- a/packages/page-coretime/src/utils/index.ts +++ b/packages/page-coretime/src/utils/index.ts @@ -5,6 +5,7 @@ import type { ChainBlockConstants, ChainConstants, CoretimeInformation } from '@ import type { ChainName, GetResponse, RegionInfo } from '../types.js'; import { BN } from '@polkadot/util'; +import { CoreTimeTypes } from '@polkadot/react-hooks/constants'; type FirstCycleStartType = Record< 'block' | 'timeslice', @@ -34,6 +35,12 @@ export const FirstCycleStart: FirstCycleStartType = { } }; +export const coretimeTypeColours: Record = { + [CoreTimeTypes.Reservation]: 'orange', + [CoreTimeTypes.Lease]: 'blue', + [CoreTimeTypes['Bulk Coretime']]: 'pink' +}; + export function formatDate (date: Date) { const day = date.getDate(); const month = date.toLocaleString('default', { month: 'short' }); From e6ec3b9a715a7fd2542979ede1b759bd1e138acb Mon Sep 17 00:00:00 2001 From: Daria Mikhailova Date: Thu, 30 Jan 2025 14:07:46 +1300 Subject: [PATCH 06/14] sorting filters by lastBlock --- .../page-coretime/src/Overview/Filters.tsx | 124 ++++++++++++++++-- packages/page-coretime/src/Row.tsx | 21 ++- packages/react-hooks/src/types.ts | 1 + .../react-hooks/src/useCoretimeInformation.ts | 11 +- 4 files changed, 137 insertions(+), 20 deletions(-) diff --git a/packages/page-coretime/src/Overview/Filters.tsx b/packages/page-coretime/src/Overview/Filters.tsx index b2fe706a7814..a5325a2598df 100644 --- a/packages/page-coretime/src/Overview/Filters.tsx +++ b/packages/page-coretime/src/Overview/Filters.tsx @@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useState, useMemo } from 'react'; -import { Dropdown, Input, Tag } from '@polkadot/react-components'; +import { Button, Dropdown, Input, Tag } from '@polkadot/react-components'; import { useTranslation } from '../translate.js'; import { useRelayEndpoints } from '@polkadot/react-hooks/useParaEndpoints'; @@ -24,20 +24,65 @@ function Filters({ data, chainInfo, onFilter }: Props): React.ReactElement>({}); const [selectedType, setSelectedType] = useState(''); + const [selectedCore, setSelectedCore] = useState(''); const coretimeTypes = Object.keys(CoreTimeTypes) .filter(key => isNaN(Number(key))); + const onCoreChange = useCallback((v: string) => { + setSelectedCore(v); + if (v === 'All') { + onFilter(data); + return; + } + const filteredData = data.filter((paraId) => { + const taskInfo = chainInfo[paraId]?.workTaskInfo; + return taskInfo?.length > 0 && taskInfo[0]?.workload?.core?.toString() === v; + }); + onFilter(filteredData); + }, [chainInfo, data, onFilter]); + + const coreOptions = useMemo(() => { + const cores = new Set(); + + // Collect all unique cores from workTaskInfo + Object.values(chainInfo).forEach((info) => { + console.log('info', info); + if (info?.workTaskInfo?.length > 0 && info.workTaskInfo[0]?.workload?.core) { + console.log('core: ', info.workTaskInfo[0].workload.core.toString()); + cores.add(info.workTaskInfo[0].workload.core.toString()); + } + }); + return [ + { + text: t('All cores'), + value: 'All' + }, + ...Array.from(cores).sort((a, b) => parseInt(a) - parseInt(b)).map((core) => ({ + text: t('Core {{core}}', { replace: { core } }), + value: core + })) + ]; + }, [chainInfo, t]); + + const [blocksSort, setBlocksSort] = useState<'DESC' | 'ASC' | ''>(''); + + const getNextSortState = useCallback((current: 'DESC' | 'ASC' | ''): 'DESC' | 'ASC' | '' => { + if (current === 'DESC') return 'ASC'; + if (current === 'ASC') return ''; + return 'DESC'; + }, []); + const typeOptions = useMemo(() => { return [ { - value: '', + value: 'All', text: 'All' }, ...coretimeTypes.map((type) => ({ - value: CoreTimeTypes[type].toString(), + value: CoreTimeTypes[type as keyof typeof CoreTimeTypes].toString(), text: ( ) @@ -55,6 +100,34 @@ function Filters({ data, chainInfo, onFilter }: Props): React.ReactElement { + if (!data || !chainInfo) { + return; + } + if (blocksSort === '') { + onFilter(data); + return; + } + + const sortedData = [...data].sort((a, b) => { + const aInfo = chainInfo[a]?.workTaskInfo[0]; + const bInfo = chainInfo[b]?.workTaskInfo[0]; + + if (!aInfo) return blocksSort === 'DESC' ? 1 : -1; + if (!bInfo) return blocksSort === 'DESC' ? -1 : 1; + + return blocksSort === 'DESC' + ? bInfo.lastBlock - aInfo.lastBlock + : aInfo.lastBlock - bInfo.lastBlock; + }); + + if (blocksSort && JSON.stringify(sortedData) !== JSON.stringify(data)) { + onFilter(sortedData); + } + }, [blocksSort, data, chainInfo]); + + const onInputChange = useCallback((v: string) => { setSearchValue(v); @@ -76,7 +149,7 @@ function Filters({ data, chainInfo, onFilter }: Props): React.ReactElement { setSelectedType(v); - if (v === 'all') { + if (v === 'All') { onFilter(data); return; } @@ -91,28 +164,59 @@ function Filters({ data, chainInfo, onFilter }: Props): React.ReactElement { + // setSearchValue(''); + // setSelectedType(''); + // setSelectedCore(''); + // setBlocksSort(''); + // console.log('data', data); + // onFilter(data); + // }, [data, onFilter]); + return ( -
+
- -
); + +
+
+ {/* {(searchValue || selectedType || selectedCore || blocksSort) && ( +
); } export default Filters; diff --git a/packages/page-coretime/src/Row.tsx b/packages/page-coretime/src/Row.tsx index 14be8ad8230a..4e3dc3655e46 100644 --- a/packages/page-coretime/src/Row.tsx +++ b/packages/page-coretime/src/Row.tsx @@ -4,7 +4,7 @@ import type { FlagColor } from '@polkadot/react-components/types'; import type { ChainWorkTaskInformation, LegacyLease } from '@polkadot/react-hooks/types'; -import React, { useMemo } from 'react'; +import React from 'react'; import { MarkWarning, ParaLink, styled, Tag } from '@polkadot/react-components'; import { ParaLinkType } from '@polkadot/react-components/ParaLink'; @@ -24,9 +24,15 @@ interface Props { highlight?: boolean } -const StyledCell = styled.td<{ $p: boolean }>` +interface StyledCellProps { + $p: boolean; + $width?: string; +} + +const StyledCell = styled.td` && { background-color: ${({ $p }) => ($p ? '#F9FAFB' : undefined)}; + width: ${({ $width }) => $width}; } height: 55px; `; @@ -49,7 +55,7 @@ function Row({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease const targetTimeslice = lease?.until || chainRegionEnd; const showEstimates = !!targetTimeslice && Object.values(CoreTimeTypes)[chainRecord.type] !== CoreTimeTypes.Reservation; const { coretimeInfo, get } = useCoretimeContext(); - const lastBlock = useMemo(() => get?.blocks.relay(targetTimeslice), [get, targetTimeslice]); + // const lastBlock = useMemo(() => get?.blocks.relay(targetTimeslice), [get, targetTimeslice]); const estimatedTime = showEstimates && get && coretimeInfo && estimateTime(targetTimeslice, get.blocks.relay(lastCommittedTimeslice), coretimeInfo?.constants?.relay); @@ -59,8 +65,9 @@ function Row({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease return ( - {id} + {id} {} @@ -74,11 +81,11 @@ function Row({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease {showEstimates && lastBlock && {showEstimates && chainRecord?.lastBlock && {formatNumber(lastBlock)}} + >{formatNumber(chainRecord?.lastBlock)}}
{ - if (!workloadData?.length || !reservations?.length) { + if (!workloadData?.length || !reservations?.length || !coretimeConstants) { return; } @@ -137,14 +137,19 @@ function useCoretimeInformationImpl (api: ApiPromise, ready: boolean): CoretimeI const chainRenewedCore = type === CoreTimeTypes['Bulk Coretime'] && workplan?.find((a) => a.core === workload?.core); const renewal = potentialRenewalsCurrentRegion?.find((renewal) => renewal.task.toString() === taskId); const renewalStatus = chainRenewedCore ? ChainRenewalStatus.Renewed : renewal ? ChainRenewalStatus.Eligible : ChainRenewalStatus.None; - + const chainRegionEnd = (renewalStatus === ChainRenewalStatus.Renewed ? salesInfo?.regionEnd : salesInfo?.regionBegin); + const targetTimeslice = lease?.until || chainRegionEnd; + + const lastBlock = targetTimeslice ? targetTimeslice * coretimeConstants?.relay.blocksPerTimeslice : 0; + return { chainRenewedCore, renewal, renewalStatus, type, workload, - workplan + workplan, + lastBlock }; }); From da8c39b4cf9a10a6cb1d69715af1fc37e38a5418 Mon Sep 17 00:00:00 2001 From: Daria Mikhailova Date: Thu, 30 Jan 2025 20:51:22 +1300 Subject: [PATCH 07/14] filters working together --- .../page-coretime/src/Overview/Filters.tsx | 240 +++++------------- .../src/Overview/filters/index.ts | 4 + .../src/Overview/filters/useBlockSort.tsx | 51 ++++ .../src/Overview/filters/useSearchFilter.tsx | 64 +++++ .../src/Overview/filters/useTypeFilter.tsx | 62 +++++ packages/page-coretime/src/types.ts | 12 + 6 files changed, 262 insertions(+), 171 deletions(-) create mode 100644 packages/page-coretime/src/Overview/filters/index.ts create mode 100644 packages/page-coretime/src/Overview/filters/useBlockSort.tsx create mode 100644 packages/page-coretime/src/Overview/filters/useSearchFilter.tsx create mode 100644 packages/page-coretime/src/Overview/filters/useTypeFilter.tsx diff --git a/packages/page-coretime/src/Overview/Filters.tsx b/packages/page-coretime/src/Overview/Filters.tsx index a5325a2598df..2cb3bce93af6 100644 --- a/packages/page-coretime/src/Overview/Filters.tsx +++ b/packages/page-coretime/src/Overview/Filters.tsx @@ -1,16 +1,16 @@ // Copyright 2017-2025 @polkadot/app-coretime authors & contributors // SPDX-License-Identifier: Apache-2.0 -import React, { useCallback, useEffect, useState, useMemo } from 'react'; - -import { Button, Dropdown, Input, Tag } from '@polkadot/react-components'; - +import React, { useState, useCallback } from 'react'; +import { Button, Dropdown, Input } from '@polkadot/react-components'; import { useTranslation } from '../translate.js'; -import { useRelayEndpoints } from '@polkadot/react-hooks/useParaEndpoints'; -import { CoreTimeTypes } from '@polkadot/react-hooks/constants'; -import { coretimeTypeColours } from '../utils/index.js'; -import { FlagColor } from '@polkadot/react-components/types'; import { ChainInformation } from '@polkadot/react-hooks/types'; +import { + useSearchFilter, + useTypeFilter, + useBlocksSort +} from './filters/index.js'; +import { sortByBlocks } from './filters/useBlockSort.js'; interface Props { data: number[]; @@ -18,160 +18,64 @@ interface Props { onFilter: (data: number[]) => void; } -function Filters({ data, chainInfo, onFilter }: Props): React.ReactElement { +function Filters({ data: initialData, chainInfo, onFilter }: Props): React.ReactElement { const { t } = useTranslation(); - const [searchValue, setSearchValue] = useState(''); - const endpoints = useRelayEndpoints(); - const [endPointsMap, setEndPointsMap] = useState>({}); - const [selectedType, setSelectedType] = useState(''); - const [selectedCore, setSelectedCore] = useState(''); - const coretimeTypes = Object.keys(CoreTimeTypes) - .filter(key => isNaN(Number(key))); - - const onCoreChange = useCallback((v: string) => { - setSelectedCore(v); - if (v === 'All') { - onFilter(data); - return; - } - const filteredData = data.filter((paraId) => { - const taskInfo = chainInfo[paraId]?.workTaskInfo; - return taskInfo?.length > 0 && taskInfo[0]?.workload?.core?.toString() === v; - }); - onFilter(filteredData); - }, [chainInfo, data, onFilter]); - - const coreOptions = useMemo(() => { - const cores = new Set(); - - // Collect all unique cores from workTaskInfo - Object.values(chainInfo).forEach((info) => { - console.log('info', info); - if (info?.workTaskInfo?.length > 0 && info.workTaskInfo[0]?.workload?.core) { - console.log('core: ', info.workTaskInfo[0].workload.core.toString()); - cores.add(info.workTaskInfo[0].workload.core.toString()); - } - }); - return [ - { - text: t('All cores'), - value: 'All' - }, - ...Array.from(cores).sort((a, b) => parseInt(a) - parseInt(b)).map((core) => ({ - text: t('Core {{core}}', { replace: { core } }), - value: core - })) - ]; - }, [chainInfo, t]); - - const [blocksSort, setBlocksSort] = useState<'DESC' | 'ASC' | ''>(''); - - const getNextSortState = useCallback((current: 'DESC' | 'ASC' | ''): 'DESC' | 'ASC' | '' => { - if (current === 'DESC') return 'ASC'; - if (current === 'ASC') return ''; - return 'DESC'; - }, []); - - const typeOptions = useMemo(() => { - return [ - { - value: 'All', - text: 'All' - }, - ...coretimeTypes.map((type) => ({ - value: CoreTimeTypes[type as keyof typeof CoreTimeTypes].toString(), - text: ( - - ) - })) - ]; - }, [coretimeTypes]); - - useEffect(() => { - const endPointsMap = endpoints.reduce((acc: Record, endpoint) => { - if (endpoint?.text && endpoint.paraId) { - acc[endpoint.text.toString()] = endpoint.paraId; - } - return acc; - }, {}); - setEndPointsMap(endPointsMap); - }, [endpoints]); - - - useEffect(() => { - if (!data || !chainInfo) { - return; - } - if (blocksSort === '') { - onFilter(data); - return; + const [activeFilters, setActiveFilters] = useState({ + search: [], + type: [] + }); + + const { blocksSort, setBlocksSort, getNextSortState, resetSort } = useBlocksSort({ + data: initialData, + chainInfo, + onFilter: (data) => handleFilter(data, 'blocks') + }); + + const handleFilter = useCallback((filteredData: number[], filterType: 'search' | 'type' | 'blocks') => { + let resultData = filteredData; + + if (activeFilters.search.length > 0 && filterType !== 'search') { + resultData = resultData.filter(id => activeFilters.search.includes(id)); } - const sortedData = [...data].sort((a, b) => { - const aInfo = chainInfo[a]?.workTaskInfo[0]; - const bInfo = chainInfo[b]?.workTaskInfo[0]; - - if (!aInfo) return blocksSort === 'DESC' ? 1 : -1; - if (!bInfo) return blocksSort === 'DESC' ? -1 : 1; + if (activeFilters.type.length > 0 && filterType !== 'type') { + resultData = resultData.filter(id => activeFilters.type.includes(id)); + } - return blocksSort === 'DESC' - ? bInfo.lastBlock - aInfo.lastBlock - : aInfo.lastBlock - bInfo.lastBlock; - }); + if (blocksSort && filterType !== 'blocks') { + resultData = sortByBlocks(resultData, chainInfo, blocksSort); + } - if (blocksSort && JSON.stringify(sortedData) !== JSON.stringify(data)) { - onFilter(sortedData); + if (filterType !== 'blocks') { + setActiveFilters((prev) => ({ + ...prev, + [filterType]: filteredData.length === initialData.length ? [] : filteredData + })); } - }, [blocksSort, data, chainInfo]); + onFilter(resultData); + }, [initialData, onFilter, activeFilters, blocksSort, chainInfo]); - const onInputChange = useCallback((v: string) => { - setSearchValue(v); + const { searchValue, onInputChange, resetSearch } = useSearchFilter({ + data: initialData, + onFilter: (data) => handleFilter(data, 'search') + }); - if (!v.trim()) { - onFilter(data); - return; - } + const { selectedType, onDropDownChange, typeOptions, resetType } = useTypeFilter({ + data: initialData, + chainInfo, + onFilter: (data) => handleFilter(data, 'type') + }); - const searchLower = v.toLowerCase(); - const filteredData = [...new Set([ - ...data.filter((item) => item.toString().toLowerCase().includes(searchLower)), - ...Object.entries(endPointsMap) - .filter(([key]) => key.toLowerCase().includes(searchLower)) - .map(([, value]) => value) - ])]; - - onFilter(filteredData); - }, [data, endPointsMap, onFilter]); - - const onDropDownChange = useCallback((v: string) => { - setSelectedType(v); - if (v === 'All') { - onFilter(data); - return; - } - const filteredData = data - .filter((paraId) => { - const taskInfo = chainInfo[paraId].workTaskInfo; - if (taskInfo.length > 0) { - return taskInfo[0].type.toString() === v - } - return false; - }) - onFilter(filteredData); - }, [chainInfo, data, onFilter]); - - // const resetFilters = useCallback(() => { - // setSearchValue(''); - // setSelectedType(''); - // setSelectedCore(''); - // setBlocksSort(''); - // console.log('data', data); - // onFilter(data); - // }, [data, onFilter]); + const resetAllFilters = useCallback(() => { + resetSearch(); + resetType(); + resetSort(); + setActiveFilters({ search: [], type: [] }); + onFilter(initialData); + }, [initialData, onFilter, resetSearch, resetType, resetSort]); + + const hasActiveFilters = searchValue || selectedType || blocksSort; return (
@@ -193,30 +97,24 @@ function Filters({ data, chainInfo, onFilter }: Props): React.ReactElement - -
+
- {/* {(searchValue || selectedType || selectedCore || blocksSort) && ( -
); + {hasActiveFilters && ( +
+
+ )} +
+ ); } -export default Filters; +export default Filters; \ No newline at end of file diff --git a/packages/page-coretime/src/Overview/filters/index.ts b/packages/page-coretime/src/Overview/filters/index.ts new file mode 100644 index 000000000000..2d2401fb6171 --- /dev/null +++ b/packages/page-coretime/src/Overview/filters/index.ts @@ -0,0 +1,4 @@ +export * from './useSearchFilter.js'; +export * from './useTypeFilter.js'; +export * from './useBlockSort.js'; +export * from '../../types.js'; diff --git a/packages/page-coretime/src/Overview/filters/useBlockSort.tsx b/packages/page-coretime/src/Overview/filters/useBlockSort.tsx new file mode 100644 index 000000000000..5a3e64e73983 --- /dev/null +++ b/packages/page-coretime/src/Overview/filters/useBlockSort.tsx @@ -0,0 +1,51 @@ +// Copyright 2017-2025 @polkadot/app-coretime authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React, { useState, useCallback } from 'react'; +import { ChainInfoFilterProps, SortDirection } from '../../types.js'; +import { ChainInformation } from '@polkadot/react-hooks/types'; + +export function sortByBlocks(data: number[], chainInfo: Record, direction: SortDirection): number[] { + if (!data || !chainInfo || !direction) { + return data || []; + } + + return [...data].sort((a, b) => { + const aInfo = chainInfo[a]?.workTaskInfo[0]; + const bInfo = chainInfo[b]?.workTaskInfo[0]; + + if (!aInfo) return direction === 'DESC' ? 1 : -1; + if (!bInfo) return direction === 'DESC' ? -1 : 1; + + return direction === 'DESC' + ? bInfo.lastBlock - aInfo.lastBlock + : aInfo.lastBlock - bInfo.lastBlock; + }); +} + +export function useBlocksSort({ data, chainInfo, onFilter }: ChainInfoFilterProps) { + const [blocksSort, setBlocksSort] = useState(''); + + const getNextSortState = useCallback((current: SortDirection): SortDirection => { + if (current === 'DESC') return 'ASC'; + if (current === 'ASC') return ''; + return 'DESC'; + }, []); + + const handleSort = useCallback((direction: SortDirection) => { + setBlocksSort(direction); + onFilter(sortByBlocks(data, chainInfo, direction)); + }, [data, chainInfo, onFilter]); + + const resetSort = useCallback(() => { + setBlocksSort(''); + onFilter(data || []); + }, [data, onFilter]); + + return { + blocksSort, + setBlocksSort: handleSort, + getNextSortState, + resetSort + }; +} \ No newline at end of file diff --git a/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx b/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx new file mode 100644 index 000000000000..1da3f623a210 --- /dev/null +++ b/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx @@ -0,0 +1,64 @@ +// Copyright 2017-2025 @polkadot/app-coretime authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { useRelayEndpoints } from '@polkadot/react-hooks/useParaEndpoints'; +import React, { useState, useCallback, useMemo } from 'react'; + +interface UseSearchFilterProps { + data: number[]; + onFilter: (data: number[]) => void; +} + +export function useSearchFilter({ data, onFilter }: UseSearchFilterProps) { + const [searchValue, setSearchValue] = useState(''); + const endpoints = useRelayEndpoints(); + const endPointsMap = useMemo(() => endpoints.reduce((acc: Record, endpoint) => { + if (endpoint?.text && endpoint.paraId) { + acc[endpoint.text.toString()] = endpoint.paraId; + } + return acc; + }, {}), [endpoints]); + + + const resetSearch = useCallback(() => { + setSearchValue(''); + onFilter(data); + }, [data, onFilter]); + + const onInputChange = useCallback((v: string) => { + setSearchValue(v); + const trimmed = v.trim(); + const searchLower = trimmed.toLowerCase(); + + const matchingIds = new Set(); + + if (searchLower) { + data.forEach((item) => { + if (item.toString().toLowerCase().includes(searchLower)) { + matchingIds.add(item); + } + }); + + Object.entries(endPointsMap).forEach(([key, value]) => { + if (key.toLowerCase().includes(searchLower) && data.includes(value)) { + matchingIds.add(value); + } + }); + + } else { + data.forEach((item) => { + matchingIds.add(item); + }); + } + + const filteredData = Array.from(matchingIds); + + onFilter(filteredData); + }, [data, endPointsMap, onFilter]); + + return { + searchValue, + onInputChange, + resetSearch + }; +} \ No newline at end of file diff --git a/packages/page-coretime/src/Overview/filters/useTypeFilter.tsx b/packages/page-coretime/src/Overview/filters/useTypeFilter.tsx new file mode 100644 index 000000000000..421a48b76e39 --- /dev/null +++ b/packages/page-coretime/src/Overview/filters/useTypeFilter.tsx @@ -0,0 +1,62 @@ +// Copyright 2017-2025 @polkadot/app-coretime authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React, { useState, useCallback } from 'react'; +import { Tag } from '@polkadot/react-components'; +import { CoreTimeTypes } from '@polkadot/react-hooks/constants'; +import { ChainInfoFilterProps } from '../../types.js'; +import { coretimeTypeColours } from '../../utils/index.js'; +import { FlagColor } from '@polkadot/react-components/types'; + +const coretimeTypes = Object.keys(CoreTimeTypes).filter(key => isNaN(Number(key))); + +const typeOptions = [ + { + value: 'All', + text: 'All' + }, + ...coretimeTypes.map((type) => ({ + value: CoreTimeTypes[type as keyof typeof CoreTimeTypes].toString(), + text: ( + + ) + })) +]; + +export function useTypeFilter({ data, chainInfo, onFilter }: ChainInfoFilterProps) { + const [selectedType, setSelectedType] = useState(''); + + const resetType = useCallback(() => { + setSelectedType(''); + }, []); + + const onDropDownChange = useCallback((v: string) => { + setSelectedType(v); + if (!v || v === 'All') { + onFilter(data); + return; + } + + const filteredData = data.filter((paraId) => { + if (!chainInfo[paraId]) { + return false; + } + const taskInfo = chainInfo[paraId].workTaskInfo; + if (taskInfo.length > 0) { + return taskInfo[0].type.toString() === v; + } + return false; + }); + onFilter(filteredData); + }, [chainInfo, data, onFilter]); + + return { + selectedType, + onDropDownChange, + typeOptions, + resetType + }; +} \ No newline at end of file diff --git a/packages/page-coretime/src/types.ts b/packages/page-coretime/src/types.ts index 00aacf6065ff..8553ed6a0151 100644 --- a/packages/page-coretime/src/types.ts +++ b/packages/page-coretime/src/types.ts @@ -1,6 +1,7 @@ // Copyright 2017-2025 @polkadot/app-coretime authors & contributors // SPDX-License-Identifier: Apache-2.0 +import { ChainInformation } from '@polkadot/react-hooks/types'; import type { PhaseName } from './constants.js'; export interface PhaseInfo { @@ -94,3 +95,14 @@ export interface GetResponse { relay: (blocks: number) => number; }; } + +export interface BaseFilterProps { + data: number[]; + onFilter: (data: number[]) => void; +} + +export interface ChainInfoFilterProps extends BaseFilterProps { + chainInfo: Record; +} + +export type SortDirection = 'DESC' | 'ASC' | ''; \ No newline at end of file From 2ad8070ccc004a17ffa986839ebb717eb422808c Mon Sep 17 00:00:00 2001 From: Daria Mikhailova Date: Fri, 31 Jan 2025 23:53:30 +1300 Subject: [PATCH 08/14] filtering update --- .../page-coretime/src/Overview/Filters.tsx | 49 +++++++++++-------- .../src/Overview/filters/useBlockSort.tsx | 13 +++-- .../src/Overview/filters/useSearchFilter.tsx | 18 +++++-- .../src/Overview/filters/useTypeFilter.tsx | 22 +++++++-- 4 files changed, 70 insertions(+), 32 deletions(-) diff --git a/packages/page-coretime/src/Overview/Filters.tsx b/packages/page-coretime/src/Overview/Filters.tsx index 2cb3bce93af6..c5e58b3f75de 100644 --- a/packages/page-coretime/src/Overview/Filters.tsx +++ b/packages/page-coretime/src/Overview/Filters.tsx @@ -10,7 +10,12 @@ import { useTypeFilter, useBlocksSort } from './filters/index.js'; -import { sortByBlocks } from './filters/useBlockSort.js'; +type FilterType = 'search' | 'type' | 'blocks'; + +interface ActiveFilters { + search: number[]; + type: number[]; +} interface Props { data: number[]; @@ -20,30 +25,44 @@ interface Props { function Filters({ data: initialData, chainInfo, onFilter }: Props): React.ReactElement { const { t } = useTranslation(); - const [activeFilters, setActiveFilters] = useState({ + const [activeFilters, setActiveFilters] = useState({ search: [], type: [] }); - const { blocksSort, setBlocksSort, getNextSortState, resetSort } = useBlocksSort({ + const { blocksSort, setBlocksSort, getNextSortState, resetSort, applyBlocksSort } = useBlocksSort({ data: initialData, chainInfo, onFilter: (data) => handleFilter(data, 'blocks') }); - const handleFilter = useCallback((filteredData: number[], filterType: 'search' | 'type' | 'blocks') => { + const { searchValue, onInputChange, resetSearch, applySearchFilter } = useSearchFilter({ + data: initialData, + onFilter: (data) => handleFilter(data, 'search') + }); + + const { selectedType, onDropDownChange, typeOptions, resetType, applyTypeFilter } = useTypeFilter({ + data: initialData, + chainInfo, + onFilter: (data) => handleFilter(data, 'type') + }); + + const handleFilter = useCallback(( + filteredData: number[], + filterType: FilterType + ): void => { let resultData = filteredData; - if (activeFilters.search.length > 0 && filterType !== 'search') { - resultData = resultData.filter(id => activeFilters.search.includes(id)); + if (filterType !== 'search') { + resultData = applySearchFilter(resultData, activeFilters.search); } - if (activeFilters.type.length > 0 && filterType !== 'type') { - resultData = resultData.filter(id => activeFilters.type.includes(id)); + if (filterType !== 'type') { + resultData = applyTypeFilter(resultData, activeFilters.type); } - if (blocksSort && filterType !== 'blocks') { - resultData = sortByBlocks(resultData, chainInfo, blocksSort); + if (filterType !== 'blocks' && blocksSort) { + resultData = applyBlocksSort(resultData, blocksSort); } if (filterType !== 'blocks') { @@ -56,16 +75,6 @@ function Filters({ data: initialData, chainInfo, onFilter }: Props): React.React onFilter(resultData); }, [initialData, onFilter, activeFilters, blocksSort, chainInfo]); - const { searchValue, onInputChange, resetSearch } = useSearchFilter({ - data: initialData, - onFilter: (data) => handleFilter(data, 'search') - }); - - const { selectedType, onDropDownChange, typeOptions, resetType } = useTypeFilter({ - data: initialData, - chainInfo, - onFilter: (data) => handleFilter(data, 'type') - }); const resetAllFilters = useCallback(() => { resetSearch(); diff --git a/packages/page-coretime/src/Overview/filters/useBlockSort.tsx b/packages/page-coretime/src/Overview/filters/useBlockSort.tsx index 5a3e64e73983..c7bd2fddab89 100644 --- a/packages/page-coretime/src/Overview/filters/useBlockSort.tsx +++ b/packages/page-coretime/src/Overview/filters/useBlockSort.tsx @@ -26,6 +26,12 @@ export function sortByBlocks(data: number[], chainInfo: Record(''); + const applyBlocksSort = useCallback((data: number[], sort: SortDirection): number[] => { + return sort + ? sortByBlocks(data, chainInfo, sort) + : data; + }, [chainInfo]); + const getNextSortState = useCallback((current: SortDirection): SortDirection => { if (current === 'DESC') return 'ASC'; if (current === 'ASC') return ''; @@ -34,8 +40,8 @@ export function useBlocksSort({ data, chainInfo, onFilter }: ChainInfoFilterProp const handleSort = useCallback((direction: SortDirection) => { setBlocksSort(direction); - onFilter(sortByBlocks(data, chainInfo, direction)); - }, [data, chainInfo, onFilter]); + onFilter(applyBlocksSort(data, direction)); + }, [data, chainInfo, onFilter, applyBlocksSort]); const resetSort = useCallback(() => { setBlocksSort(''); @@ -46,6 +52,7 @@ export function useBlocksSort({ data, chainInfo, onFilter }: ChainInfoFilterProp blocksSort, setBlocksSort: handleSort, getNextSortState, - resetSort + resetSort, + applyBlocksSort }; } \ No newline at end of file diff --git a/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx b/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx index 1da3f623a210..3d1879436b4b 100644 --- a/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx +++ b/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx @@ -11,6 +11,7 @@ interface UseSearchFilterProps { export function useSearchFilter({ data, onFilter }: UseSearchFilterProps) { const [searchValue, setSearchValue] = useState(''); + const [activeSearch, setActiveSearch] = useState([]); const endpoints = useRelayEndpoints(); const endPointsMap = useMemo(() => endpoints.reduce((acc: Record, endpoint) => { if (endpoint?.text && endpoint.paraId) { @@ -19,9 +20,15 @@ export function useSearchFilter({ data, onFilter }: UseSearchFilterProps) { return acc; }, {}), [endpoints]); + const applySearchFilter = useCallback((data: number[], activeSearch: number[]): number[] => { + return activeSearch.length > 0 + ? data.filter(id => activeSearch.includes(id)) + : data; + }, []); const resetSearch = useCallback(() => { setSearchValue(''); + setActiveSearch([]); onFilter(data); }, [data, onFilter]); @@ -29,7 +36,6 @@ export function useSearchFilter({ data, onFilter }: UseSearchFilterProps) { setSearchValue(v); const trimmed = v.trim(); const searchLower = trimmed.toLowerCase(); - const matchingIds = new Set(); if (searchLower) { @@ -52,13 +58,15 @@ export function useSearchFilter({ data, onFilter }: UseSearchFilterProps) { } const filteredData = Array.from(matchingIds); - - onFilter(filteredData); - }, [data, endPointsMap, onFilter]); + setActiveSearch(filteredData); + onFilter(applySearchFilter(data, filteredData)); + }, [data, endPointsMap, onFilter, applySearchFilter]); return { searchValue, onInputChange, - resetSearch + resetSearch, + activeSearch, + applySearchFilter }; } \ No newline at end of file diff --git a/packages/page-coretime/src/Overview/filters/useTypeFilter.tsx b/packages/page-coretime/src/Overview/filters/useTypeFilter.tsx index 421a48b76e39..186f9e337d94 100644 --- a/packages/page-coretime/src/Overview/filters/useTypeFilter.tsx +++ b/packages/page-coretime/src/Overview/filters/useTypeFilter.tsx @@ -28,14 +28,24 @@ const typeOptions = [ export function useTypeFilter({ data, chainInfo, onFilter }: ChainInfoFilterProps) { const [selectedType, setSelectedType] = useState(''); + const [activeType, setActiveType] = useState([]); + + const applyTypeFilter = useCallback((data: number[], activeType: number[]): number[] => { + return activeType.length > 0 + ? data.filter(id => activeType.includes(id)) + : data; + }, []); const resetType = useCallback(() => { setSelectedType(''); - }, []); + setActiveType([]); + onFilter(data); + }, [data, onFilter]); const onDropDownChange = useCallback((v: string) => { setSelectedType(v); if (!v || v === 'All') { + setActiveType([]); onFilter(data); return; } @@ -50,13 +60,17 @@ export function useTypeFilter({ data, chainInfo, onFilter }: ChainInfoFilterProp } return false; }); - onFilter(filteredData); - }, [chainInfo, data, onFilter]); + + setActiveType(filteredData); + onFilter(applyTypeFilter(data, filteredData)); + }, [chainInfo, data, onFilter, applyTypeFilter]); return { selectedType, onDropDownChange, typeOptions, - resetType + resetType, + activeType, + applyTypeFilter }; } \ No newline at end of file From 1db69e7b14bc941f084c8991a1afdb37a1e13382 Mon Sep 17 00:00:00 2001 From: Daria Mikhailova Date: Sat, 1 Feb 2025 00:15:53 +1300 Subject: [PATCH 09/14] lint --- .../page-coretime/src/Overview/Filters.tsx | 225 +++++++++--------- .../src/Overview/filters/index.ts | 7 +- .../src/Overview/filters/useBlockSort.tsx | 115 +++++---- .../src/Overview/filters/useSearchFilter.tsx | 116 ++++----- .../src/Overview/filters/useTypeFilter.tsx | 138 ++++++----- .../page-coretime/src/ParachainsTable.tsx | 15 +- packages/page-coretime/src/Row.tsx | 41 ++-- packages/page-coretime/src/types.ts | 4 +- packages/page-coretime/src/utils/index.ts | 2 +- packages/react-components/src/ParaLink.tsx | 78 +++--- .../react-hooks/src/useCoretimeInformation.ts | 10 +- 11 files changed, 403 insertions(+), 348 deletions(-) diff --git a/packages/page-coretime/src/Overview/Filters.tsx b/packages/page-coretime/src/Overview/Filters.tsx index c5e58b3f75de..eac2b7a31e05 100644 --- a/packages/page-coretime/src/Overview/Filters.tsx +++ b/packages/page-coretime/src/Overview/Filters.tsx @@ -1,129 +1,128 @@ // Copyright 2017-2025 @polkadot/app-coretime authors & contributors // SPDX-License-Identifier: Apache-2.0 -import React, { useState, useCallback } from 'react'; +import type { ChainInformation } from '@polkadot/react-hooks/types'; + +import React, { useCallback, useState } from 'react'; + import { Button, Dropdown, Input } from '@polkadot/react-components'; + import { useTranslation } from '../translate.js'; -import { ChainInformation } from '@polkadot/react-hooks/types'; -import { - useSearchFilter, - useTypeFilter, - useBlocksSort -} from './filters/index.js'; +import { useBlocksSort, useSearchFilter, useTypeFilter } from './filters/index.js'; + type FilterType = 'search' | 'type' | 'blocks'; interface ActiveFilters { - search: number[]; - type: number[]; + search: number[]; + type: number[]; } interface Props { - data: number[]; - chainInfo: Record; - onFilter: (data: number[]) => void; + chainInfo: Record; + data: number[]; + onFilter: (data: number[]) => void; } -function Filters({ data: initialData, chainInfo, onFilter }: Props): React.ReactElement { - const { t } = useTranslation(); - const [activeFilters, setActiveFilters] = useState({ - search: [], - type: [] - }); - - const { blocksSort, setBlocksSort, getNextSortState, resetSort, applyBlocksSort } = useBlocksSort({ - data: initialData, - chainInfo, - onFilter: (data) => handleFilter(data, 'blocks') - }); - - const { searchValue, onInputChange, resetSearch, applySearchFilter } = useSearchFilter({ - data: initialData, - onFilter: (data) => handleFilter(data, 'search') - }); - - const { selectedType, onDropDownChange, typeOptions, resetType, applyTypeFilter } = useTypeFilter({ - data: initialData, - chainInfo, - onFilter: (data) => handleFilter(data, 'type') - }); - - const handleFilter = useCallback(( - filteredData: number[], - filterType: FilterType - ): void => { - let resultData = filteredData; - - if (filterType !== 'search') { - resultData = applySearchFilter(resultData, activeFilters.search); - } - - if (filterType !== 'type') { - resultData = applyTypeFilter(resultData, activeFilters.type); - } - - if (filterType !== 'blocks' && blocksSort) { - resultData = applyBlocksSort(resultData, blocksSort); - } - - if (filterType !== 'blocks') { - setActiveFilters((prev) => ({ - ...prev, - [filterType]: filteredData.length === initialData.length ? [] : filteredData - })); - } - - onFilter(resultData); - }, [initialData, onFilter, activeFilters, blocksSort, chainInfo]); - - - const resetAllFilters = useCallback(() => { - resetSearch(); - resetType(); - resetSort(); - setActiveFilters({ search: [], type: [] }); - onFilter(initialData); - }, [initialData, onFilter, resetSearch, resetType, resetSort]); - - const hasActiveFilters = searchValue || selectedType || blocksSort; - - return ( -
-
- -
- -
-
- {hasActiveFilters && ( -
-
- )} +function Filters ({ chainInfo, data: initialData, onFilter }: Props): React.ReactElement { + const { t } = useTranslation(); + const [activeFilters, setActiveFilters] = useState({ + search: [], + type: [] + }); + + const { applyBlocksSort, blocksSort, handleBlocksSortClick, resetSort } = useBlocksSort({ + chainInfo, + data: initialData, + onFilter: (data) => handleFilter(data, 'blocks') + }); + + const { applySearchFilter, onInputChange, resetSearch, searchValue } = useSearchFilter({ + data: initialData, + onFilter: (data) => handleFilter(data, 'search') + }); + + const { applyTypeFilter, onDropDownChange, resetType, selectedType, typeOptions } = useTypeFilter({ + chainInfo, + data: initialData, + onFilter: (data) => handleFilter(data, 'type') + }); + + const handleFilter = useCallback(( + filteredData: number[], + filterType: FilterType + ): void => { + let resultData = filteredData; + + if (filterType !== 'search') { + resultData = applySearchFilter(resultData, activeFilters.search); + } + + if (filterType !== 'type') { + resultData = applyTypeFilter(resultData, activeFilters.type); + } + + if (filterType !== 'blocks' && blocksSort) { + resultData = applyBlocksSort(resultData, blocksSort); + } + + if (filterType !== 'blocks') { + setActiveFilters((prev) => ({ + ...prev, + [filterType]: filteredData.length === initialData.length ? [] : filteredData + })); + } + + onFilter(resultData); + }, [initialData, onFilter, activeFilters, blocksSort, applyBlocksSort, applyTypeFilter, applySearchFilter]); + + const resetAllFilters = useCallback(() => { + resetSearch(); + resetType(); + resetSort(); + setActiveFilters({ search: [], type: [] }); + onFilter(initialData); + }, [initialData, onFilter, resetSearch, resetType, resetSort]); + + const hasActiveFilters = searchValue || selectedType || blocksSort; + + return ( +
+
+ +
+ +
+
+ {hasActiveFilters && ( +
+
- ); + )} +
+ ); } -export default Filters; \ No newline at end of file +export default Filters; diff --git a/packages/page-coretime/src/Overview/filters/index.ts b/packages/page-coretime/src/Overview/filters/index.ts index 2d2401fb6171..369ff70d6ec6 100644 --- a/packages/page-coretime/src/Overview/filters/index.ts +++ b/packages/page-coretime/src/Overview/filters/index.ts @@ -1,4 +1,7 @@ +// Copyright 2017-2025 @polkadot/app-coretime authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +export * from '../../types.js'; +export * from './useBlockSort.js'; export * from './useSearchFilter.js'; export * from './useTypeFilter.js'; -export * from './useBlockSort.js'; -export * from '../../types.js'; diff --git a/packages/page-coretime/src/Overview/filters/useBlockSort.tsx b/packages/page-coretime/src/Overview/filters/useBlockSort.tsx index c7bd2fddab89..b0fc6ada1035 100644 --- a/packages/page-coretime/src/Overview/filters/useBlockSort.tsx +++ b/packages/page-coretime/src/Overview/filters/useBlockSort.tsx @@ -1,58 +1,75 @@ // Copyright 2017-2025 @polkadot/app-coretime authors & contributors // SPDX-License-Identifier: Apache-2.0 -import React, { useState, useCallback } from 'react'; -import { ChainInfoFilterProps, SortDirection } from '../../types.js'; -import { ChainInformation } from '@polkadot/react-hooks/types'; +import type { ChainInformation } from '@polkadot/react-hooks/types'; +import type { ChainInfoFilterProps, SortDirection } from '../../types.js'; -export function sortByBlocks(data: number[], chainInfo: Record, direction: SortDirection): number[] { - if (!data || !chainInfo || !direction) { - return data || []; - } +import { useCallback, useState } from 'react'; + +export function sortByBlocks (data: number[], chainInfo: Record, direction: SortDirection): number[] { + if (!data || !chainInfo || !direction) { + return data || []; + } - return [...data].sort((a, b) => { - const aInfo = chainInfo[a]?.workTaskInfo[0]; - const bInfo = chainInfo[b]?.workTaskInfo[0]; + return [...data].sort((a, b) => { + const aInfo = chainInfo[a]?.workTaskInfo[0]; + const bInfo = chainInfo[b]?.workTaskInfo[0]; + + if (!aInfo) { + return direction === 'DESC' ? 1 : -1; + } - if (!aInfo) return direction === 'DESC' ? 1 : -1; - if (!bInfo) return direction === 'DESC' ? -1 : 1; + if (!bInfo) { + return direction === 'DESC' ? -1 : 1; + } - return direction === 'DESC' - ? bInfo.lastBlock - aInfo.lastBlock - : aInfo.lastBlock - bInfo.lastBlock; - }); + return direction === 'DESC' + ? bInfo.lastBlock - aInfo.lastBlock + : aInfo.lastBlock - bInfo.lastBlock; + }); } -export function useBlocksSort({ data, chainInfo, onFilter }: ChainInfoFilterProps) { - const [blocksSort, setBlocksSort] = useState(''); - - const applyBlocksSort = useCallback((data: number[], sort: SortDirection): number[] => { - return sort - ? sortByBlocks(data, chainInfo, sort) - : data; - }, [chainInfo]); - - const getNextSortState = useCallback((current: SortDirection): SortDirection => { - if (current === 'DESC') return 'ASC'; - if (current === 'ASC') return ''; - return 'DESC'; - }, []); - - const handleSort = useCallback((direction: SortDirection) => { - setBlocksSort(direction); - onFilter(applyBlocksSort(data, direction)); - }, [data, chainInfo, onFilter, applyBlocksSort]); - - const resetSort = useCallback(() => { - setBlocksSort(''); - onFilter(data || []); - }, [data, onFilter]); - - return { - blocksSort, - setBlocksSort: handleSort, - getNextSortState, - resetSort, - applyBlocksSort - }; -} \ No newline at end of file +export function useBlocksSort ({ chainInfo, data, onFilter }: ChainInfoFilterProps) { + const [blocksSort, setBlocksSort] = useState(''); + + const applyBlocksSort = useCallback((data: number[], sort: SortDirection): number[] => { + return sort + ? sortByBlocks(data, chainInfo, sort) + : data; + }, [chainInfo]); + + const getNextSortState = useCallback((current: SortDirection): SortDirection => { + if (current === 'DESC') { + return 'ASC'; + } + + if (current === 'ASC') { + return ''; + } + + return 'DESC'; + }, []); + + const handleSort = useCallback((direction: SortDirection) => { + setBlocksSort(direction); + onFilter(applyBlocksSort(data, direction)); + }, [data, onFilter, applyBlocksSort]); + + const resetSort = useCallback(() => { + setBlocksSort(''); + onFilter(data || []); + }, [data, onFilter]); + + const handleBlocksSortClick = useCallback(() => { + setBlocksSort(getNextSortState(blocksSort)); + }, [blocksSort, getNextSortState]); + + return { + applyBlocksSort, + blocksSort, + getNextSortState, + handleBlocksSortClick, + resetSort, + setBlocksSort: handleSort + }; +} diff --git a/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx b/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx index 3d1879436b4b..561221d3beda 100644 --- a/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx +++ b/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx @@ -1,72 +1,78 @@ // Copyright 2017-2025 @polkadot/app-coretime authors & contributors // SPDX-License-Identifier: Apache-2.0 +import React, { useCallback, useMemo, useState } from 'react'; + import { useRelayEndpoints } from '@polkadot/react-hooks/useParaEndpoints'; -import React, { useState, useCallback, useMemo } from 'react'; interface UseSearchFilterProps { - data: number[]; - onFilter: (data: number[]) => void; + data: number[]; + onFilter: (data: number[]) => void; } -export function useSearchFilter({ data, onFilter }: UseSearchFilterProps) { - const [searchValue, setSearchValue] = useState(''); - const [activeSearch, setActiveSearch] = useState([]); - const endpoints = useRelayEndpoints(); - const endPointsMap = useMemo(() => endpoints.reduce((acc: Record, endpoint) => { - if (endpoint?.text && endpoint.paraId) { - acc[endpoint.text.toString()] = endpoint.paraId; - } - return acc; - }, {}), [endpoints]); +export function useSearchFilter ({ data, onFilter }: UseSearchFilterProps) { + const [searchValue, setSearchValue] = useState(''); + const [activeSearch, setActiveSearch] = useState([]); + const endpoints = useRelayEndpoints(); + const endPointsMap = useMemo(() => endpoints.reduce((acc: Record, endpoint) => { + if (endpoint?.text && endpoint.paraId) { + const textValue = React.isValidElement(endpoint.text) + ? '' + : String(endpoint.text); + + acc[textValue] = endpoint.paraId; + } - const applySearchFilter = useCallback((data: number[], activeSearch: number[]): number[] => { - return activeSearch.length > 0 - ? data.filter(id => activeSearch.includes(id)) - : data; - }, []); + return acc; + }, {}), [endpoints]); - const resetSearch = useCallback(() => { - setSearchValue(''); - setActiveSearch([]); - onFilter(data); - }, [data, onFilter]); + const applySearchFilter = useCallback((data: number[], activeSearch: number[]): number[] => { + return activeSearch.length > 0 + ? data.filter((id) => activeSearch.includes(id)) + : data; + }, []); - const onInputChange = useCallback((v: string) => { - setSearchValue(v); - const trimmed = v.trim(); - const searchLower = trimmed.toLowerCase(); - const matchingIds = new Set(); + const resetSearch = useCallback(() => { + setSearchValue(''); + setActiveSearch([]); + onFilter(data); + }, [data, onFilter]); - if (searchLower) { - data.forEach((item) => { - if (item.toString().toLowerCase().includes(searchLower)) { - matchingIds.add(item); - } - }); + const onInputChange = useCallback((v: string) => { + setSearchValue(v); + const trimmed = v.trim(); + const searchLower = trimmed.toLowerCase(); + const matchingIds = new Set(); - Object.entries(endPointsMap).forEach(([key, value]) => { - if (key.toLowerCase().includes(searchLower) && data.includes(value)) { - matchingIds.add(value); - } - }); + if (searchLower) { + data.forEach((item) => { + if (item.toString().toLowerCase().includes(searchLower)) { + matchingIds.add(item); + } + }); - } else { - data.forEach((item) => { - matchingIds.add(item); - }); + Object.entries(endPointsMap).forEach(([key, value]) => { + if (key.toLowerCase().includes(searchLower) && data.includes(value)) { + matchingIds.add(value); } + }); + } else { + data.forEach((item) => { + matchingIds.add(item); + }); + } + + const filteredData = Array.from(matchingIds); - const filteredData = Array.from(matchingIds); - setActiveSearch(filteredData); - onFilter(applySearchFilter(data, filteredData)); - }, [data, endPointsMap, onFilter, applySearchFilter]); + setActiveSearch(filteredData); + onFilter(applySearchFilter(data, filteredData)); + }, [data, endPointsMap, onFilter, applySearchFilter]); - return { - searchValue, - onInputChange, - resetSearch, - activeSearch, - applySearchFilter - }; -} \ No newline at end of file + return { + activeSearch, + applySearchFilter, + onInputChange, + resetSearch, + searchValue + }; +} diff --git a/packages/page-coretime/src/Overview/filters/useTypeFilter.tsx b/packages/page-coretime/src/Overview/filters/useTypeFilter.tsx index 186f9e337d94..a8f5655265cd 100644 --- a/packages/page-coretime/src/Overview/filters/useTypeFilter.tsx +++ b/packages/page-coretime/src/Overview/filters/useTypeFilter.tsx @@ -1,76 +1,84 @@ // Copyright 2017-2025 @polkadot/app-coretime authors & contributors // SPDX-License-Identifier: Apache-2.0 -import React, { useState, useCallback } from 'react'; +import type { FlagColor } from '@polkadot/react-components/types'; +import type { ChainInfoFilterProps } from '../../types.js'; + +import React, { useCallback, useState } from 'react'; + import { Tag } from '@polkadot/react-components'; import { CoreTimeTypes } from '@polkadot/react-hooks/constants'; -import { ChainInfoFilterProps } from '../../types.js'; + import { coretimeTypeColours } from '../../utils/index.js'; -import { FlagColor } from '@polkadot/react-components/types'; -const coretimeTypes = Object.keys(CoreTimeTypes).filter(key => isNaN(Number(key))); +const coretimeTypes = Object.keys(CoreTimeTypes).filter((key) => isNaN(Number(key))); const typeOptions = [ - { - value: 'All', - text: 'All' - }, - ...coretimeTypes.map((type) => ({ - value: CoreTimeTypes[type as keyof typeof CoreTimeTypes].toString(), - text: ( - - ) - })) + { + text: 'All', + value: 'All' + }, + ...coretimeTypes.map((type) => ({ + text: ( + + ), + value: CoreTimeTypes[type as keyof typeof CoreTimeTypes].toString() + })) ]; -export function useTypeFilter({ data, chainInfo, onFilter }: ChainInfoFilterProps) { - const [selectedType, setSelectedType] = useState(''); - const [activeType, setActiveType] = useState([]); - - const applyTypeFilter = useCallback((data: number[], activeType: number[]): number[] => { - return activeType.length > 0 - ? data.filter(id => activeType.includes(id)) - : data; - }, []); - - const resetType = useCallback(() => { - setSelectedType(''); - setActiveType([]); - onFilter(data); - }, [data, onFilter]); - - const onDropDownChange = useCallback((v: string) => { - setSelectedType(v); - if (!v || v === 'All') { - setActiveType([]); - onFilter(data); - return; - } - - const filteredData = data.filter((paraId) => { - if (!chainInfo[paraId]) { - return false; - } - const taskInfo = chainInfo[paraId].workTaskInfo; - if (taskInfo.length > 0) { - return taskInfo[0].type.toString() === v; - } - return false; - }); - - setActiveType(filteredData); - onFilter(applyTypeFilter(data, filteredData)); - }, [chainInfo, data, onFilter, applyTypeFilter]); - - return { - selectedType, - onDropDownChange, - typeOptions, - resetType, - activeType, - applyTypeFilter - }; -} \ No newline at end of file +export function useTypeFilter ({ chainInfo, data, onFilter }: ChainInfoFilterProps) { + const [selectedType, setSelectedType] = useState(''); + const [activeType, setActiveType] = useState([]); + + const applyTypeFilter = useCallback((data: number[], activeType: number[]): number[] => { + return activeType.length > 0 + ? data.filter((id) => activeType.includes(id)) + : data; + }, []); + + const resetType = useCallback(() => { + setSelectedType(''); + setActiveType([]); + onFilter(data); + }, [data, onFilter]); + + const onDropDownChange = useCallback((v: string) => { + setSelectedType(v); + + if (!v || v === 'All') { + setActiveType([]); + onFilter(data); + + return; + } + + const filteredData = data.filter((paraId) => { + if (!chainInfo[paraId]) { + return false; + } + + const taskInfo = chainInfo[paraId].workTaskInfo; + + if (taskInfo.length > 0) { + return taskInfo[0].type.toString() === v; + } + + return false; + }); + + setActiveType(filteredData); + onFilter(applyTypeFilter(data, filteredData)); + }, [chainInfo, data, onFilter, applyTypeFilter]); + + return { + activeType, + applyTypeFilter, + onDropDownChange, + resetType, + selectedType, + typeOptions + }; +} diff --git a/packages/page-coretime/src/ParachainsTable.tsx b/packages/page-coretime/src/ParachainsTable.tsx index 7230da602128..1524eb906195 100644 --- a/packages/page-coretime/src/ParachainsTable.tsx +++ b/packages/page-coretime/src/ParachainsTable.tsx @@ -1,7 +1,7 @@ // Copyright 2017-2025 @polkadot/app-coretime authors & contributors // SPDX-License-Identifier: Apache-2.0 -import React, { useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Table } from '@polkadot/react-components'; import { type CoretimeInformation } from '@polkadot/react-hooks/types'; @@ -14,7 +14,7 @@ interface Props { coretimeInfo: CoretimeInformation } -function ParachainsTable({ coretimeInfo }: Props): React.ReactElement { +function ParachainsTable ({ coretimeInfo }: Props): React.ReactElement { const { t } = useTranslation(); const headerRef = useRef<([React.ReactNode?, string?, number?] | false)[]>([ [t('parachains'), 'start'], @@ -31,6 +31,10 @@ function ParachainsTable({ coretimeInfo }: Props): React.ReactElement { const [taskIds, setTaskIds] = useState([]); + const onFilter = useCallback((filteredData: number[]) => { + setTaskIds(filteredData); + }, []); + useEffect(() => { if (coretimeInfo?.taskIds) { setTaskIds(coretimeInfo?.taskIds); @@ -40,11 +44,9 @@ function ParachainsTable({ coretimeInfo }: Props): React.ReactElement { return ( <> - setTaskIds(filteredData) - } + data={coretimeInfo?.taskIds} + onFilter={onFilter} />
{ if (!chain) { return null; } + return ( { +function Row ({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease, regionBegin, regionEnd }: Props): React.ReactElement { const chainRegionEnd = (chainRecord.renewalStatus === ChainRenewalStatus.Renewed ? regionEnd : regionBegin); const targetTimeslice = lease?.until || chainRegionEnd; const showEstimates = !!targetTimeslice && Object.values(CoreTimeTypes)[chainRecord.type] !== CoreTimeTypes.Reservation; @@ -65,10 +65,13 @@ function Row({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease return ( - {id} {id} + {} {chainRecord?.workload?.core} @@ -81,11 +84,14 @@ function Row({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease {showEstimates && chainRecord?.lastBlock && {formatNumber(chainRecord?.lastBlock)}} + >{showEstimates && chainRecord?.lastBlock && + {formatNumber(chainRecord?.lastBlock)} + } + {
- -
+ >{
-
-
} +
+ +
+
} +
{highlight && }
diff --git a/packages/page-coretime/src/types.ts b/packages/page-coretime/src/types.ts index 8553ed6a0151..ea0a37b5eada 100644 --- a/packages/page-coretime/src/types.ts +++ b/packages/page-coretime/src/types.ts @@ -1,7 +1,7 @@ // Copyright 2017-2025 @polkadot/app-coretime authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { ChainInformation } from '@polkadot/react-hooks/types'; +import type { ChainInformation } from '@polkadot/react-hooks/types'; import type { PhaseName } from './constants.js'; export interface PhaseInfo { @@ -105,4 +105,4 @@ export interface ChainInfoFilterProps extends BaseFilterProps { chainInfo: Record; } -export type SortDirection = 'DESC' | 'ASC' | ''; \ No newline at end of file +export type SortDirection = 'DESC' | 'ASC' | ''; diff --git a/packages/page-coretime/src/utils/index.ts b/packages/page-coretime/src/utils/index.ts index 4215263ecefd..76ee14d537e8 100644 --- a/packages/page-coretime/src/utils/index.ts +++ b/packages/page-coretime/src/utils/index.ts @@ -4,8 +4,8 @@ import type { ChainBlockConstants, ChainConstants, CoretimeInformation } from '@polkadot/react-hooks/types'; import type { ChainName, GetResponse, RegionInfo } from '../types.js'; -import { BN } from '@polkadot/util'; import { CoreTimeTypes } from '@polkadot/react-hooks/constants'; +import { BN } from '@polkadot/util'; type FirstCycleStartType = Record< 'block' | 'timeslice', diff --git a/packages/react-components/src/ParaLink.tsx b/packages/react-components/src/ParaLink.tsx index e35ff1b6f161..635f184721e6 100644 --- a/packages/react-components/src/ParaLink.tsx +++ b/packages/react-components/src/ParaLink.tsx @@ -40,47 +40,59 @@ function ParaLink ({ className, id, showLogo = true, type = ParaLinkType.PJS }: ? links[links.length - 1] : endpoints[0]; - const subscanUrl = text && Subscan.chains[text?.toString()] && Subscan.create(Subscan.chains[text?.toString()], '', '').toString(); + const subscanUrl = text && + typeof text === 'string' && + Subscan.chains[text] && + Subscan.create(Subscan.chains[text], '', '').toString(); return ( - {showLogo && - } + {showLogo && ( + + )} {links.length ? ( <> - {type === ParaLinkType.SUBSCAN && !!subscanUrl && - Subscan - } - {type === ParaLinkType.HOME && homepage && - - } - {type === ParaLinkType.PJS && {text}} + {type === ParaLinkType.SUBSCAN && !!subscanUrl && ( + + Subscan + + )} + {type === ParaLinkType.HOME && homepage && ( + + + + )} + {type === ParaLinkType.PJS && ( + + {typeof text === 'string' ? text : text?.toString()} + + )} ) - : type === ParaLinkType.PJS ? text : null + : type === ParaLinkType.PJS ? (typeof text === 'string' ? text : text?.toString()) : null } ); diff --git a/packages/react-hooks/src/useCoretimeInformation.ts b/packages/react-hooks/src/useCoretimeInformation.ts index 728d884f5c51..9f2c97632de0 100644 --- a/packages/react-hooks/src/useCoretimeInformation.ts +++ b/packages/react-hooks/src/useCoretimeInformation.ts @@ -139,17 +139,17 @@ function useCoretimeInformationImpl (api: ApiPromise, ready: boolean): CoretimeI const renewalStatus = chainRenewedCore ? ChainRenewalStatus.Renewed : renewal ? ChainRenewalStatus.Eligible : ChainRenewalStatus.None; const chainRegionEnd = (renewalStatus === ChainRenewalStatus.Renewed ? salesInfo?.regionEnd : salesInfo?.regionBegin); const targetTimeslice = lease?.until || chainRegionEnd; - - const lastBlock = targetTimeslice ? targetTimeslice * coretimeConstants?.relay.blocksPerTimeslice : 0; - + + const lastBlock = targetTimeslice ? targetTimeslice * coretimeConstants?.relay.blocksPerTimeslice : 0; + return { chainRenewedCore, + lastBlock, renewal, renewalStatus, type, workload, - workplan, - lastBlock + workplan }; }); From 42414f2732d9bd492579f3a52bd2c7125d8dec47 Mon Sep 17 00:00:00 2001 From: Daria Mikhailova Date: Mon, 3 Feb 2025 17:13:02 +1300 Subject: [PATCH 10/14] refactor --- .../page-coretime/src/Overview/Filters.tsx | 49 ++++++------ .../src/Overview/filters/useBlockSort.tsx | 49 +++++------- .../src/Overview/filters/useSearchFilter.tsx | 76 ++++++++++--------- .../src/Overview/filters/useTypeFilter.tsx | 29 +++---- packages/page-coretime/src/types.ts | 11 +++ 5 files changed, 101 insertions(+), 113 deletions(-) diff --git a/packages/page-coretime/src/Overview/Filters.tsx b/packages/page-coretime/src/Overview/Filters.tsx index eac2b7a31e05..1e29015fdb90 100644 --- a/packages/page-coretime/src/Overview/Filters.tsx +++ b/packages/page-coretime/src/Overview/Filters.tsx @@ -8,14 +8,9 @@ import React, { useCallback, useState } from 'react'; import { Button, Dropdown, Input } from '@polkadot/react-components'; import { useTranslation } from '../translate.js'; -import { useBlocksSort, useSearchFilter, useTypeFilter } from './filters/index.js'; +import { FilterType, useBlocksSort, useSearchFilter, useTypeFilter } from './filters/index.js'; -type FilterType = 'search' | 'type' | 'blocks'; - -interface ActiveFilters { - search: number[]; - type: number[]; -} +import type { ActiveFilters } from '../types.js'; interface Props { chainInfo: Record; @@ -23,49 +18,53 @@ interface Props { onFilter: (data: number[]) => void; } -function Filters ({ chainInfo, data: initialData, onFilter }: Props): React.ReactElement { +function Filters({ chainInfo, data: initialData, onFilter }: Props): React.ReactElement { const { t } = useTranslation(); const [activeFilters, setActiveFilters] = useState({ search: [], type: [] }); - const { applyBlocksSort, blocksSort, handleBlocksSortClick, resetSort } = useBlocksSort({ + const { apply: applyBlocksSort, direction, onApply: onApplySort, reset: resetSort } = useBlocksSort({ chainInfo, data: initialData, - onFilter: (data) => handleFilter(data, 'blocks') + onFilter: (data) => handleFilter(data, FilterType.BLOCKS) }); - const { applySearchFilter, onInputChange, resetSearch, searchValue } = useSearchFilter({ + const { apply: applySearchFilter, onApply: onApplySearch, reset: resetSearch, searchValue } = useSearchFilter({ data: initialData, - onFilter: (data) => handleFilter(data, 'search') + onFilter: (data) => handleFilter(data, FilterType.SEARCH) }); - const { applyTypeFilter, onDropDownChange, resetType, selectedType, typeOptions } = useTypeFilter({ + const { apply: applyTypeFilter, onApply: onApplyType, reset: resetType, selectedType, typeOptions } = useTypeFilter({ chainInfo, data: initialData, - onFilter: (data) => handleFilter(data, 'type') + onFilter: (data) => handleFilter(data, FilterType.TYPE) }); + /** + * 1. Applies additional filtering already present in the filters + * 2. Performs filtering based on the filter type + */ const handleFilter = useCallback(( filteredData: number[], filterType: FilterType ): void => { let resultData = filteredData; - if (filterType !== 'search') { + if (filterType !== FilterType.SEARCH) { resultData = applySearchFilter(resultData, activeFilters.search); } - if (filterType !== 'type') { + if (filterType !== FilterType.TYPE) { resultData = applyTypeFilter(resultData, activeFilters.type); } - if (filterType !== 'blocks' && blocksSort) { - resultData = applyBlocksSort(resultData, blocksSort); + if (filterType !== FilterType.BLOCKS && direction) { + resultData = applyBlocksSort(resultData, direction); } - if (filterType !== 'blocks') { + if (filterType !== FilterType.BLOCKS) { setActiveFilters((prev) => ({ ...prev, [filterType]: filteredData.length === initialData.length ? [] : filteredData @@ -73,7 +72,7 @@ function Filters ({ chainInfo, data: initialData, onFilter }: Props): React.Reac } onFilter(resultData); - }, [initialData, onFilter, activeFilters, blocksSort, applyBlocksSort, applyTypeFilter, applySearchFilter]); + }, [initialData, onFilter, activeFilters, direction, applyBlocksSort, applyTypeFilter, applySearchFilter]); const resetAllFilters = useCallback(() => { resetSearch(); @@ -83,7 +82,7 @@ function Filters ({ chainInfo, data: initialData, onFilter }: Props): React.Reac onFilter(initialData); }, [initialData, onFilter, resetSearch, resetType, resetSort]); - const hasActiveFilters = searchValue || selectedType || blocksSort; + const hasActiveFilters = searchValue || selectedType || direction; return (
@@ -92,7 +91,7 @@ function Filters ({ chainInfo, data: initialData, onFilter }: Props): React.Reac aria-label={t('Search by parachain id or name')} className='full isSmall' label={t('Search')} - onChange={onInputChange} + onChange={onApplySearch} placeholder={t('parachain id or name')} value={searchValue} /> @@ -100,16 +99,16 @@ function Filters ({ chainInfo, data: initialData, onFilter }: Props): React.Reac
{hasActiveFilters && ( diff --git a/packages/page-coretime/src/Overview/filters/useBlockSort.tsx b/packages/page-coretime/src/Overview/filters/useBlockSort.tsx index b0fc6ada1035..6fc8f4349c00 100644 --- a/packages/page-coretime/src/Overview/filters/useBlockSort.tsx +++ b/packages/page-coretime/src/Overview/filters/useBlockSort.tsx @@ -6,7 +6,7 @@ import type { ChainInfoFilterProps, SortDirection } from '../../types.js'; import { useCallback, useState } from 'react'; -export function sortByBlocks (data: number[], chainInfo: Record, direction: SortDirection): number[] { +export function sortByBlocks(data: number[], chainInfo: Record, direction: SortDirection): number[] { if (!data || !chainInfo || !direction) { return data || []; } @@ -29,47 +29,32 @@ export function sortByBlocks (data: number[], chainInfo: Record(''); +const getNextSortState = (current: SortDirection): SortDirection => + ({ DESC: 'ASC', ASC: '', '': 'DESC' } as const)[current]; - const applyBlocksSort = useCallback((data: number[], sort: SortDirection): number[] => { +export function useBlocksSort({ chainInfo, data, onFilter }: ChainInfoFilterProps) { + const [direction, setDirection] = useState(''); + + const apply = useCallback((data: number[], sort: SortDirection): number[] => { return sort ? sortByBlocks(data, chainInfo, sort) : data; }, [chainInfo]); - const getNextSortState = useCallback((current: SortDirection): SortDirection => { - if (current === 'DESC') { - return 'ASC'; - } - - if (current === 'ASC') { - return ''; - } + const onApply = useCallback(() => { + setDirection(getNextSortState(direction)); + onFilter(direction ? sortByBlocks(data, chainInfo, direction) : data); + }, [data, chainInfo, onFilter]); - return 'DESC'; - }, []); - - const handleSort = useCallback((direction: SortDirection) => { - setBlocksSort(direction); - onFilter(applyBlocksSort(data, direction)); - }, [data, onFilter, applyBlocksSort]); - - const resetSort = useCallback(() => { - setBlocksSort(''); + const reset = useCallback(() => { + setDirection(''); onFilter(data || []); }, [data, onFilter]); - const handleBlocksSortClick = useCallback(() => { - setBlocksSort(getNextSortState(blocksSort)); - }, [blocksSort, getNextSortState]); - return { - applyBlocksSort, - blocksSort, - getNextSortState, - handleBlocksSortClick, - resetSort, - setBlocksSort: handleSort + apply, + direction, + onApply, + reset, }; } diff --git a/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx b/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx index 561221d3beda..277827754954 100644 --- a/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx +++ b/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx @@ -10,29 +10,29 @@ interface UseSearchFilterProps { onFilter: (data: number[]) => void; } -export function useSearchFilter ({ data, onFilter }: UseSearchFilterProps) { +export function useSearchFilter({ data, onFilter }: UseSearchFilterProps) { const [searchValue, setSearchValue] = useState(''); const [activeSearch, setActiveSearch] = useState([]); const endpoints = useRelayEndpoints(); - const endPointsMap = useMemo(() => endpoints.reduce((acc: Record, endpoint) => { - if (endpoint?.text && endpoint.paraId) { - const textValue = React.isValidElement(endpoint.text) - ? '' - : String(endpoint.text); + const endPointsMap = useMemo(() => + Object.fromEntries( + endpoints + .filter(e => e?.text && e.paraId) + .map(e => [ + React.isValidElement(e.text) ? '' : String(e.text), + e.paraId + ]) + ), + [endpoints] + ); - acc[textValue] = endpoint.paraId; - } - - return acc; - }, {}), [endpoints]); - - const applySearchFilter = useCallback((data: number[], activeSearch: number[]): number[] => { + const apply = useCallback((data: number[], activeSearch: number[]): number[] => { return activeSearch.length > 0 ? data.filter((id) => activeSearch.includes(id)) : data; }, []); - const resetSearch = useCallback(() => { + const reset = useCallback(() => { setSearchValue(''); setActiveSearch([]); onFilter(data); @@ -40,39 +40,41 @@ export function useSearchFilter ({ data, onFilter }: UseSearchFilterProps) { const onInputChange = useCallback((v: string) => { setSearchValue(v); - const trimmed = v.trim(); - const searchLower = trimmed.toLowerCase(); + const searchLower = v.trim().toLowerCase(); + + if (!searchLower) { + setActiveSearch(data); + onFilter(data); + return; + } + const matchingIds = new Set(); - if (searchLower) { - data.forEach((item) => { - if (item.toString().toLowerCase().includes(searchLower)) { - matchingIds.add(item); - } - }); + for (const item of data) { + const itemStr = item.toString().toLowerCase(); - Object.entries(endPointsMap).forEach(([key, value]) => { - if (key.toLowerCase().includes(searchLower) && data.includes(value)) { - matchingIds.add(value); - } - }); - } else { - data.forEach((item) => { + if (itemStr.includes(searchLower)) { matchingIds.add(item); - }); + continue; + } + + for (const [key, value] of Object.entries(endPointsMap)) { + if (key.toLowerCase().includes(searchLower) && value === item) { + matchingIds.add(item); + break; + } + } } const filteredData = Array.from(matchingIds); - setActiveSearch(filteredData); - onFilter(applySearchFilter(data, filteredData)); - }, [data, endPointsMap, onFilter, applySearchFilter]); + onFilter(apply(data, filteredData)); + }, [data, endPointsMap, onFilter, apply]); return { - activeSearch, - applySearchFilter, - onInputChange, - resetSearch, + apply, + onApply: onInputChange, + reset, searchValue }; } diff --git a/packages/page-coretime/src/Overview/filters/useTypeFilter.tsx b/packages/page-coretime/src/Overview/filters/useTypeFilter.tsx index a8f5655265cd..38206cdca26b 100644 --- a/packages/page-coretime/src/Overview/filters/useTypeFilter.tsx +++ b/packages/page-coretime/src/Overview/filters/useTypeFilter.tsx @@ -29,17 +29,17 @@ const typeOptions = [ })) ]; -export function useTypeFilter ({ chainInfo, data, onFilter }: ChainInfoFilterProps) { +export function useTypeFilter({ chainInfo, data, onFilter }: ChainInfoFilterProps) { const [selectedType, setSelectedType] = useState(''); const [activeType, setActiveType] = useState([]); - const applyTypeFilter = useCallback((data: number[], activeType: number[]): number[] => { + const apply = useCallback((data: number[], activeType: number[]): number[] => { return activeType.length > 0 ? data.filter((id) => activeType.includes(id)) : data; }, []); - const resetType = useCallback(() => { + const reset = useCallback(() => { setSelectedType(''); setActiveType([]); onFilter(data); @@ -56,28 +56,19 @@ export function useTypeFilter ({ chainInfo, data, onFilter }: ChainInfoFilterPro } const filteredData = data.filter((paraId) => { - if (!chainInfo[paraId]) { - return false; - } - - const taskInfo = chainInfo[paraId].workTaskInfo; - - if (taskInfo.length > 0) { - return taskInfo[0].type.toString() === v; - } - - return false; + const taskInfo = chainInfo[paraId]?.workTaskInfo; + return taskInfo?.length > 0 && taskInfo[0].type.toString() === v; }); setActiveType(filteredData); - onFilter(applyTypeFilter(data, filteredData)); - }, [chainInfo, data, onFilter, applyTypeFilter]); + onFilter(apply(data, filteredData)); + }, [chainInfo, data, onFilter, apply]); return { activeType, - applyTypeFilter, - onDropDownChange, - resetType, + apply, + onApply: onDropDownChange, + reset, selectedType, typeOptions }; diff --git a/packages/page-coretime/src/types.ts b/packages/page-coretime/src/types.ts index ea0a37b5eada..13ee82c0632d 100644 --- a/packages/page-coretime/src/types.ts +++ b/packages/page-coretime/src/types.ts @@ -106,3 +106,14 @@ export interface ChainInfoFilterProps extends BaseFilterProps { } export type SortDirection = 'DESC' | 'ASC' | ''; + +export enum FilterType { + BLOCKS = 'blocks', + SEARCH = 'search', + TYPE = 'type' +} + +export interface ActiveFilters { + search: number[]; + type: number[]; +} From e83da22b922fbc857b1e39ccb633aa8793b41dc1 Mon Sep 17 00:00:00 2001 From: Daria Mikhailova Date: Mon, 3 Feb 2025 17:44:44 +1300 Subject: [PATCH 11/14] unused var --- .../src/Overview/filters/useSearchFilter.tsx | 9 +++------ packages/page-coretime/src/types.ts | 1 + 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx b/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx index 277827754954..2536a0d9ddcb 100644 --- a/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx +++ b/packages/page-coretime/src/Overview/filters/useSearchFilter.tsx @@ -12,13 +12,12 @@ interface UseSearchFilterProps { export function useSearchFilter({ data, onFilter }: UseSearchFilterProps) { const [searchValue, setSearchValue] = useState(''); - const [activeSearch, setActiveSearch] = useState([]); const endpoints = useRelayEndpoints(); const endPointsMap = useMemo(() => Object.fromEntries( endpoints - .filter(e => e?.text && e.paraId) - .map(e => [ + .filter((e) => e?.text && e.paraId) + .map((e) => [ React.isValidElement(e.text) ? '' : String(e.text), e.paraId ]) @@ -34,7 +33,6 @@ export function useSearchFilter({ data, onFilter }: UseSearchFilterProps) { const reset = useCallback(() => { setSearchValue(''); - setActiveSearch([]); onFilter(data); }, [data, onFilter]); @@ -43,8 +41,8 @@ export function useSearchFilter({ data, onFilter }: UseSearchFilterProps) { const searchLower = v.trim().toLowerCase(); if (!searchLower) { - setActiveSearch(data); onFilter(data); + return; } @@ -67,7 +65,6 @@ export function useSearchFilter({ data, onFilter }: UseSearchFilterProps) { } const filteredData = Array.from(matchingIds); - setActiveSearch(filteredData); onFilter(apply(data, filteredData)); }, [data, endPointsMap, onFilter, apply]); diff --git a/packages/page-coretime/src/types.ts b/packages/page-coretime/src/types.ts index 13ee82c0632d..b0afbcb73bfe 100644 --- a/packages/page-coretime/src/types.ts +++ b/packages/page-coretime/src/types.ts @@ -116,4 +116,5 @@ export enum FilterType { export interface ActiveFilters { search: number[]; type: number[]; + blocks: number[]; } From ac393066af45dcaba1f64c5eff26225f17078471 Mon Sep 17 00:00:00 2001 From: Daria Mikhailova Date: Mon, 3 Feb 2025 17:51:10 +1300 Subject: [PATCH 12/14] flipped arrows for ASC and DESC --- .../page-coretime/src/Overview/Filters.tsx | 7 ++++--- .../src/Overview/filters/useBlockSort.tsx | 19 +++++++------------ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/page-coretime/src/Overview/Filters.tsx b/packages/page-coretime/src/Overview/Filters.tsx index 1e29015fdb90..acd6c9960ee1 100644 --- a/packages/page-coretime/src/Overview/Filters.tsx +++ b/packages/page-coretime/src/Overview/Filters.tsx @@ -22,7 +22,8 @@ function Filters({ chainInfo, data: initialData, onFilter }: Props): React.React const { t } = useTranslation(); const [activeFilters, setActiveFilters] = useState({ search: [], - type: [] + type: [], + blocks: [] }); const { apply: applyBlocksSort, direction, onApply: onApplySort, reset: resetSort } = useBlocksSort({ @@ -78,7 +79,7 @@ function Filters({ chainInfo, data: initialData, onFilter }: Props): React.React resetSearch(); resetType(); resetSort(); - setActiveFilters({ search: [], type: [] }); + setActiveFilters({ search: [], type: [], blocks: [] }); onFilter(initialData); }, [initialData, onFilter, resetSearch, resetType, resetSort]); @@ -106,7 +107,7 @@ function Filters({ chainInfo, data: initialData, onFilter }: Props): React.React />