mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-04-30 11:58:01 +00:00
feat: initial Pezkuwi Apps rebrand from polkadot-apps
Rebranded terminology: - Polkadot → Pezkuwi - Kusama → Dicle - Westend → Zagros - Rococo → PezkuwiChain - Substrate → Bizinikiwi - parachain → teyrchain Custom logos with Kurdistan brand colors (#e6007a → #86e62a): - bizinikiwi-hexagon.svg - sora-bizinikiwi.svg - hezscanner.svg - heztreasury.svg - pezkuwiscan.svg - pezkuwistats.svg - pezkuwiassembly.svg - pezkuwiholic.svg
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-coretime authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ChainInformation } from '@pezkuwi/react-hooks/types';
|
||||
import type { ActiveFilters } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Button, Dropdown, Input } from '@pezkuwi/react-components';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import { FilterType, useBlocksSort, useSearchFilter, useTypeFilter } from './filters/index.js';
|
||||
|
||||
interface Props {
|
||||
chainInfo: Record<number, ChainInformation>;
|
||||
data: number[];
|
||||
onFilter: (data: number[]) => void;
|
||||
}
|
||||
|
||||
function Filters ({ chainInfo, data: initialData, onFilter }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [activeFilters, setActiveFilters] = useState<ActiveFilters>({
|
||||
search: [],
|
||||
type: []
|
||||
});
|
||||
|
||||
const { apply: applyBlocksSort, direction, onApply: onApplySort, reset: resetSort } = useBlocksSort({
|
||||
chainInfo,
|
||||
data: initialData,
|
||||
onFilter: (data) => handleFilter(data, FilterType.BLOCKS)
|
||||
});
|
||||
|
||||
const { apply: applySearchFilter, onApply: onApplySearch, reset: resetSearch, searchValue } = useSearchFilter({
|
||||
data: initialData,
|
||||
onFilter: (data) => handleFilter(data, FilterType.SEARCH)
|
||||
});
|
||||
|
||||
const { apply: applyTypeFilter, onApply: onApplyType, reset: resetType, selectedType, typeOptions } = useTypeFilter({
|
||||
chainInfo,
|
||||
data: initialData,
|
||||
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 !== FilterType.SEARCH) {
|
||||
resultData = applySearchFilter(resultData, activeFilters.search);
|
||||
}
|
||||
|
||||
if (filterType !== FilterType.TYPE) {
|
||||
resultData = applyTypeFilter(resultData, activeFilters.type);
|
||||
}
|
||||
|
||||
if (filterType !== FilterType.BLOCKS && direction) {
|
||||
resultData = applyBlocksSort(resultData, direction);
|
||||
}
|
||||
|
||||
if (filterType !== FilterType.BLOCKS) {
|
||||
setActiveFilters((prev) => ({
|
||||
...prev,
|
||||
[filterType]: filteredData.length === initialData.length ? [] : filteredData
|
||||
}));
|
||||
}
|
||||
|
||||
onFilter(resultData);
|
||||
}, [initialData, onFilter, activeFilters, direction, applyBlocksSort, applyTypeFilter, applySearchFilter]);
|
||||
|
||||
const resetAllFilters = useCallback(() => {
|
||||
resetSearch();
|
||||
resetType();
|
||||
resetSort();
|
||||
setActiveFilters({ search: [], type: [] });
|
||||
onFilter(initialData);
|
||||
}, [initialData, onFilter, resetSearch, resetType, resetSort]);
|
||||
|
||||
const hasActiveFilters = searchValue || selectedType || direction;
|
||||
|
||||
return (
|
||||
<div style={{ alignItems: 'center', display: 'flex', flexDirection: 'row', gap: '10px' }}>
|
||||
<div style={{ minWidth: '250px' }}>
|
||||
<Input
|
||||
aria-label={t('Search by teyrchain id or name')}
|
||||
className='full isSmall'
|
||||
label={t('Search')}
|
||||
onChange={onApplySearch}
|
||||
placeholder={t('teyrchain id or name')}
|
||||
value={searchValue}
|
||||
/>
|
||||
</div>
|
||||
<Dropdown
|
||||
className='isSmall'
|
||||
label={t('type')}
|
||||
onChange={onApplyType}
|
||||
options={typeOptions}
|
||||
placeholder='select type'
|
||||
value={selectedType}
|
||||
/>
|
||||
<div style={{ height: '20px' }}>
|
||||
<Button
|
||||
icon={direction ? (direction === 'DESC' ? 'arrow-up' : 'arrow-down') : 'sort'}
|
||||
label={t('blocks')}
|
||||
onClick={onApplySort}
|
||||
/>
|
||||
</div>
|
||||
{hasActiveFilters && (
|
||||
<div style={{ height: '20px' }}>
|
||||
<Button
|
||||
icon='times'
|
||||
label={t('Reset filters')}
|
||||
onClick={resetAllFilters}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Filters;
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-coretime authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BrokerStatus, CoreDescription, PalletBrokerConfigRecord, PalletBrokerSaleInfoRecord, RegionInfo } from '@pezkuwi/react-hooks/types';
|
||||
import type { RelayName } from '../types.js';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { CardSummary, SummaryBox } from '@pezkuwi/react-components';
|
||||
import { BN } from '@pezkuwi/util';
|
||||
|
||||
import { useCoretimeContext } from '../CoretimeContext.js';
|
||||
import { useTranslation } from '../translate.js';
|
||||
import { FirstCycleStart } from '../utils/index.js';
|
||||
|
||||
interface Props {
|
||||
coreDscriptors?: CoreDescription[];
|
||||
saleInfo: PalletBrokerSaleInfoRecord
|
||||
config: PalletBrokerConfigRecord,
|
||||
region: RegionInfo[],
|
||||
status: BrokerStatus,
|
||||
teyrchainCount: number
|
||||
relayName: RelayName,
|
||||
}
|
||||
|
||||
function Summary ({ config, teyrchainCount, relayName, status }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { coretimeInfo, currentRegionEnd, currentRegionStart, saleEndDate, saleStartDate } = useCoretimeContext();
|
||||
|
||||
const saleNumber = useMemo(() => {
|
||||
if (relayName && currentRegionEnd) {
|
||||
return Math.floor(
|
||||
(currentRegionEnd - FirstCycleStart.timeslice.coretime[relayName]) / config.regionLength
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [currentRegionEnd, relayName, config]);
|
||||
|
||||
const timeslicesSinceCycleStart = useMemo(() => currentRegionEnd && new BN(config?.regionLength).sub((new BN(currentRegionEnd)).sub(new BN(status.lastTimeslice))), [status, config, currentRegionEnd]);
|
||||
|
||||
return (
|
||||
<SummaryBox>
|
||||
<section>
|
||||
{status &&
|
||||
<CardSummary label={t('sale number')}>
|
||||
<div>
|
||||
{saleNumber}
|
||||
</div>
|
||||
</CardSummary>
|
||||
}
|
||||
<CardSummary label={t('timeslice')}>
|
||||
{status?.lastTimeslice}
|
||||
</CardSummary>
|
||||
<CardSummary label={t('teyrchains')}>
|
||||
{teyrchainCount && teyrchainCount}
|
||||
</CardSummary>
|
||||
{config && status && currentRegionEnd && saleEndDate && saleStartDate && timeslicesSinceCycleStart && coretimeInfo?.constants &&
|
||||
<>
|
||||
<CardSummary
|
||||
className='media--800'
|
||||
label={t('timeslice progress')}
|
||||
progress={{
|
||||
hideGraph: true,
|
||||
hideValue: false,
|
||||
isBlurred: false,
|
||||
total: new BN(config?.regionLength),
|
||||
value: timeslicesSinceCycleStart,
|
||||
withTime: false
|
||||
}}
|
||||
/>
|
||||
<CardSummary
|
||||
label={t('cycle')}
|
||||
progress={{
|
||||
total: new BN(config.regionLength).mul(new BN(coretimeInfo?.constants.relay.blocksPerTimeslice)),
|
||||
value: timeslicesSinceCycleStart.mul(new BN(coretimeInfo?.constants.relay.blocksPerTimeslice)),
|
||||
withTime: true
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
}
|
||||
</section>
|
||||
<section className='media--1200'>
|
||||
<CardSummary label={t('sale dates')}>
|
||||
<div>
|
||||
<div style={{ fontSize: '14px' }}>{saleStartDate}</div>
|
||||
<div style={{ fontSize: '14px' }}>{saleEndDate}</div>
|
||||
</div>
|
||||
</CardSummary>
|
||||
<CardSummary label={t('sale ts')}>
|
||||
<div>
|
||||
<div style={{ fontSize: '14px' }}>{currentRegionStart}</div>
|
||||
<div style={{ fontSize: '14px' }}>{currentRegionEnd}</div>
|
||||
</div>
|
||||
</CardSummary>
|
||||
|
||||
</section>
|
||||
</SummaryBox>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Summary);
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2017-2025 @pezkuwi/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';
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-coretime authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ChainInformation } from '@pezkuwi/react-hooks/types';
|
||||
import type { ChainInfoFilterProps, SortDirection } from '../../types.js';
|
||||
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
export function sortByBlocks (data: number[], chainInfo: Record<number, ChainInformation>, direction: SortDirection): number[] {
|
||||
if (!data || !chainInfo || !direction) {
|
||||
return data || [];
|
||||
}
|
||||
|
||||
const filteredData = data.filter((block) => !!chainInfo[block]?.workTaskInfo[0]);
|
||||
|
||||
return [...filteredData].sort((a, b) => {
|
||||
const aInfo = chainInfo[a]?.workTaskInfo[0];
|
||||
const bInfo = chainInfo[b]?.workTaskInfo[0];
|
||||
|
||||
return direction === 'DESC'
|
||||
? bInfo.lastBlock - aInfo.lastBlock
|
||||
: aInfo.lastBlock - bInfo.lastBlock;
|
||||
});
|
||||
}
|
||||
|
||||
const getNextSortState = (current: SortDirection): SortDirection =>
|
||||
({ '': 'DESC', ASC: '', DESC: 'ASC' } as const)[current];
|
||||
|
||||
export function useBlocksSort ({ chainInfo, data, onFilter }: ChainInfoFilterProps) {
|
||||
const [direction, setDirection] = useState<SortDirection>('');
|
||||
|
||||
const apply = useCallback((data: number[], sort: SortDirection): number[] => {
|
||||
return sort
|
||||
? sortByBlocks(data, chainInfo, sort)
|
||||
: data;
|
||||
}, [chainInfo]);
|
||||
|
||||
const onApply = useCallback(() => {
|
||||
const nextDirection = getNextSortState(direction);
|
||||
|
||||
setDirection(nextDirection);
|
||||
onFilter(nextDirection ? sortByBlocks(data, chainInfo, nextDirection) : data);
|
||||
}, [data, chainInfo, onFilter, direction]);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setDirection('');
|
||||
onFilter(data || []);
|
||||
}, [data, onFilter]);
|
||||
|
||||
return {
|
||||
apply,
|
||||
direction,
|
||||
onApply,
|
||||
reset
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-coretime authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { useRelayEndpoints } from '@pezkuwi/react-hooks/useParaEndpoints';
|
||||
|
||||
interface UseSearchFilterProps {
|
||||
data: number[];
|
||||
onFilter: (data: number[]) => void;
|
||||
}
|
||||
|
||||
export function useSearchFilter ({ data, onFilter }: UseSearchFilterProps) {
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const endpoints = useRelayEndpoints();
|
||||
const endPointsMap = useMemo(() =>
|
||||
Object.fromEntries(
|
||||
endpoints
|
||||
.filter((e) => e?.text && e.paraId)
|
||||
.map((e) => [
|
||||
React.isValidElement(e.text) ? '' : String(e.text),
|
||||
e.paraId
|
||||
])
|
||||
),
|
||||
[endpoints]
|
||||
);
|
||||
|
||||
const apply = useCallback((data: number[], activeSearch: number[]): number[] => {
|
||||
return activeSearch.length > 0
|
||||
? data.filter((id) => activeSearch.includes(id))
|
||||
: data;
|
||||
}, []);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setSearchValue('');
|
||||
onFilter(data);
|
||||
}, [data, onFilter]);
|
||||
|
||||
const onInputChange = useCallback((v: string) => {
|
||||
setSearchValue(v);
|
||||
const searchLower = v.trim().toLowerCase();
|
||||
|
||||
if (!searchLower) {
|
||||
onFilter(data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const matchingIds = new Set<number>();
|
||||
|
||||
for (const item of data) {
|
||||
const itemStr = item.toString().toLowerCase();
|
||||
|
||||
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);
|
||||
|
||||
onFilter(apply(data, filteredData));
|
||||
}, [data, endPointsMap, onFilter, apply]);
|
||||
|
||||
return {
|
||||
apply,
|
||||
onApply: onInputChange,
|
||||
reset,
|
||||
searchValue
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-coretime authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { FlagColor } from '@pezkuwi/react-components/types';
|
||||
import type { ChainInfoFilterProps } from '../../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Tag } from '@pezkuwi/react-components';
|
||||
import { CoreTimeTypes } from '@pezkuwi/react-hooks/constants';
|
||||
|
||||
import { coretimeTypeColours } from '../../utils/index.js';
|
||||
|
||||
const coretimeTypes = Object.keys(CoreTimeTypes).filter((key) => isNaN(Number(key)));
|
||||
|
||||
const typeOptions = [
|
||||
{
|
||||
text: 'All',
|
||||
value: 'All'
|
||||
},
|
||||
...coretimeTypes.map((type) => ({
|
||||
text: (
|
||||
<Tag
|
||||
color={coretimeTypeColours[CoreTimeTypes[type as keyof typeof CoreTimeTypes]] as FlagColor}
|
||||
label={type}
|
||||
/>
|
||||
),
|
||||
value: CoreTimeTypes[type as keyof typeof CoreTimeTypes].toString()
|
||||
}))
|
||||
];
|
||||
|
||||
export function useTypeFilter ({ chainInfo, data, onFilter }: ChainInfoFilterProps) {
|
||||
const [selectedType, setSelectedType] = useState<string>('');
|
||||
const [activeType, setActiveType] = useState<number[]>([]);
|
||||
|
||||
const apply = useCallback((data: number[], activeType: number[]): number[] => {
|
||||
return activeType.length > 0
|
||||
? data.filter((id) => activeType.includes(id))
|
||||
: data;
|
||||
}, []);
|
||||
|
||||
const reset = 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) => {
|
||||
const taskInfo = chainInfo[paraId]?.workTaskInfo;
|
||||
|
||||
return taskInfo?.length > 0 && taskInfo[0].type.toString() === v;
|
||||
});
|
||||
|
||||
setActiveType(filteredData);
|
||||
onFilter(apply(data, filteredData));
|
||||
}, [chainInfo, data, onFilter, apply]);
|
||||
|
||||
return {
|
||||
activeType,
|
||||
apply,
|
||||
onApply: onDropDownChange,
|
||||
reset,
|
||||
selectedType,
|
||||
typeOptions
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-coretime authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { RelayName } from '../types.js';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useCoretimeContext } from '../CoretimeContext.js';
|
||||
import TeyrchainsTable from '../TeyrchainsTable.js';
|
||||
import Summary from './Summary.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
relayName: RelayName
|
||||
}
|
||||
|
||||
function Overview ({ className, relayName }: Props): React.ReactElement<Props> {
|
||||
const { coretimeInfo } = useCoretimeContext();
|
||||
|
||||
return (
|
||||
<main className={className}>
|
||||
{coretimeInfo && (
|
||||
<Summary
|
||||
config={coretimeInfo?.config}
|
||||
teyrchainCount={coretimeInfo.taskIds?.length || 0}
|
||||
region={coretimeInfo?.region}
|
||||
relayName={relayName}
|
||||
saleInfo={coretimeInfo?.salesInfo}
|
||||
status={coretimeInfo?.status}
|
||||
/>
|
||||
)}
|
||||
{!!coretimeInfo &&
|
||||
<TeyrchainsTable
|
||||
coretimeInfo={coretimeInfo}
|
||||
relayName={relayName}
|
||||
/>
|
||||
}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Overview);
|
||||
Reference in New Issue
Block a user