From 06fb0bfac23641faa1eacb8d99026dfce0fd0381 Mon Sep 17 00:00:00 2001 From: chaojun Date: Mon, 1 Jun 2026 21:56:23 +0800 Subject: [PATCH 1/6] As an user, I want to know how much fellowship paid to secretaries, #7345 --- .../pages/secretary/statistics/index.jsx | 31 +++ .../expenditure/claimants/index.jsx | 18 +- .../expenditure/rank/doughnutChart/index.jsx | 12 +- .../secretary/statistics/claimants/index.jsx | 215 ++++++++++++++++++ .../secretary/statistics/collectives.jsx | 40 ++++ .../secretary/statistics/summary/index.jsx | 66 ++++++ packages/next-common/services/url.js | 5 + .../utils/consts/menu/secretary.js | 21 +- 8 files changed, 393 insertions(+), 15 deletions(-) create mode 100644 packages/collectives-next/pages/secretary/statistics/index.jsx create mode 100644 packages/next-common/components/secretary/statistics/claimants/index.jsx create mode 100644 packages/next-common/components/secretary/statistics/collectives.jsx create mode 100644 packages/next-common/components/secretary/statistics/summary/index.jsx diff --git a/packages/collectives-next/pages/secretary/statistics/index.jsx b/packages/collectives-next/pages/secretary/statistics/index.jsx new file mode 100644 index 0000000000..724defcef2 --- /dev/null +++ b/packages/collectives-next/pages/secretary/statistics/index.jsx @@ -0,0 +1,31 @@ +import ListLayout from "next-common/components/layout/ListLayout"; +import SecretaryCollectivesStatistics from "next-common/components/secretary/statistics/collectives"; +import CollectivesProvider from "next-common/context/collectives/collectives"; +import { withCommonProps } from "next-common/lib"; +import { backendApi } from "next-common/services/nextApi"; +import { secretaryMembersApiUri } from "next-common/services/url"; + +export default function SecretaryStatisticsPage() { + const category = "Secretary Statistics"; + const seoInfo = { title: category, desc: category }; + + return ( + + + + + + ); +} + +export const getServerSideProps = withCommonProps(async () => { + const { result: secretaryMembers } = await backendApi.fetch( + secretaryMembersApiUri, + ); + + return { + props: { + secretaryMembers: secretaryMembers ?? null, + }, + }; +}); diff --git a/packages/next-common/components/fellowship/statistics/expenditure/claimants/index.jsx b/packages/next-common/components/fellowship/statistics/expenditure/claimants/index.jsx index 05c9604464..ba77d7456f 100644 --- a/packages/next-common/components/fellowship/statistics/expenditure/claimants/index.jsx +++ b/packages/next-common/components/fellowship/statistics/expenditure/claimants/index.jsx @@ -32,16 +32,19 @@ function paginateData(data, page, pageSize) { return data.slice(start, end); } -function StatisticsClaimantsTable({ members = [] }) { +function StatisticsClaimantsTable({ + members = [], + membersApi = fellowshipStatisticsMembersApi, +}) { const [total, setTotal] = useState(0); const [processedData, setProcessedData] = useState([]); const [rowData, setRowData] = useState([]); const [tableLoading, setTableLoading] = useState(false); + const pageSize = defaultPageSize; const { page, component: pageComponent } = usePaginationComponent( total, - defaultPageSize, + pageSize, ); - const membersApi = fellowshipStatisticsMembersApi; const columns = [ useStatisticsClaimantsRankColumn(), @@ -64,7 +67,7 @@ function StatisticsClaimantsTable({ members = [] }) { setTableLoading(false); return []; } - }, []); + }, [membersApi]); useEffect(() => { if (originalMembers && members) { @@ -99,12 +102,15 @@ function StatisticsClaimantsTable({ members = [] }) { ); } -export default function StatisticsClaimants({ members = [] }) { +export default function StatisticsClaimants({ + members = [], + membersApi = fellowshipStatisticsMembersApi, +}) { return (
Top Claimants - +
); diff --git a/packages/next-common/components/fellowship/statistics/expenditure/rank/doughnutChart/index.jsx b/packages/next-common/components/fellowship/statistics/expenditure/rank/doughnutChart/index.jsx index c1290f421b..e21ecd39e7 100644 --- a/packages/next-common/components/fellowship/statistics/expenditure/rank/doughnutChart/index.jsx +++ b/packages/next-common/components/fellowship/statistics/expenditure/rank/doughnutChart/index.jsx @@ -33,9 +33,9 @@ function handleLabelDataArr(members, ranksData) { const totalSalary = getTotalSalary(ranksData); const ranksDataObj = transformRanksDataToObject(ranksData); const dataArr = rankArr.map((rank, index) => { - const count = ranksDataObj[index] || 0; - const percent = ranksDataObj[index] - ? new BigNumber(ranksDataObj[index]).div(totalSalary) + const count = ranksDataObj[rank] || 0; + const percent = ranksDataObj[rank] + ? new BigNumber(ranksDataObj[rank]).div(totalSalary) : ""; return { label: `Rank ${rank}`, @@ -63,7 +63,7 @@ function RankChart({ labelDataArr, data }) { @@ -71,11 +71,11 @@ function RankChart({ labelDataArr, data }) { } export default function RankDoughnutChart({ members = [] }) { + const ranksApi = fellowshipStatisticsRanksApi; + const [labelDataArr, setLabelDataArr] = useState([]); const [contentLoading, setContentLoading] = useState(false); - const ranksApi = fellowshipStatisticsRanksApi; - const { value: ranksData } = useAsync(async () => { setContentLoading(true); if (!ranksApi) { diff --git a/packages/next-common/components/secretary/statistics/claimants/index.jsx b/packages/next-common/components/secretary/statistics/claimants/index.jsx new file mode 100644 index 0000000000..708d63e1bd --- /dev/null +++ b/packages/next-common/components/secretary/statistics/claimants/index.jsx @@ -0,0 +1,215 @@ +import { useMemo } from "react"; +import DataList from "next-common/components/dataList"; +import { backendApi } from "next-common/services/nextApi"; +import { fellowshipStatisticsMembersApi } from "next-common/services/url"; +import { defaultPageSize } from "next-common/utils/constants"; +import { useAsync } from "react-use"; +import { useStatisticsClaimantColumn } from "../../../fellowship/statistics/expenditure/claimants/columns/claimant"; +import { useStatisticsClaimantsCyclesColumn } from "../../../fellowship/statistics/expenditure/claimants/columns/cycles"; +import { useStatisticsClaimantsRankColumn } from "../../../fellowship/statistics/expenditure/claimants/columns/rank"; +import { useState, useEffect } from "react"; +import usePaginationComponent from "next-common/components/pagination/usePaginationComponent"; +import { SecondaryCard } from "next-common/components/styled/containers/secondaryCard"; +import { StatisticsTitle } from "next-common/components/statistics/styled.js"; +import { isNil } from "lodash-es"; +import Link from "next-common/components/link"; +import Tooltip from "next-common/components/tooltip"; +import ValueDisplay from "next-common/components/valueDisplay"; +import { getSalaryAsset } from "next-common/utils/consts/getSalaryAsset"; +import { toPrecision } from "next-common/utils"; + +function handleClaimantsData(originalMembers, members) { + const membersRank = members.reduce((acc, member) => { + acc[member.address] = member.rank; + return acc; + }, {}); + + return originalMembers.map((item) => ({ + ...item, + rank: !isNil(membersRank[item.who]) ? membersRank[item.who] : null, + })); +} + +function paginateData(data, page, pageSize) { + const start = (page - 1) * pageSize; + const end = page * pageSize; + return data.slice(start, end); +} + +function ReferendaCell({ paymentReferenda = [] }) { + if (!paymentReferenda.length) { + return -; + } + + return ( + + {paymentReferenda.map((ref) => ( + + + {toPrecision(ref.value, ref.decimals)} {ref.symbol} + + {" · "} + {ref.title} + + } + > + + #{ref.referendumIndex} + + + ))} + + ); +} + +function useSecretaryClaimantsReferendaColumn(paymentReferenda) { + const referendaByAddress = useMemo(() => { + const map = {}; + for (const ref of paymentReferenda || []) { + if (!map[ref.beneficiary]) { + map[ref.beneficiary] = []; + } + map[ref.beneficiary].push(ref); + } + return map; + }, [paymentReferenda]); + + return { + name: "Referenda", + width: 160, + cellRender(data) { + const refs = referendaByAddress[data.who] || []; + return ; + }, + }; +} + +function useSecretaryClaimantsPaidColumn(paymentReferenda) { + const referendaTotalByAddress = useMemo(() => { + const map = {}; + for (const ref of paymentReferenda || []) { + if (ref.beneficiary) { + map[ref.beneficiary] = + (map[ref.beneficiary] || BigInt(0)) + BigInt(ref.value || 0); + } + } + return map; + }, [paymentReferenda]); + + const { decimals, symbol } = getSalaryAsset(); + + return { + name: "Total Paid", + width: 160, + className: "text-right", + cellRender(data, idx) { + const extra = referendaTotalByAddress[data.who] || BigInt(0); + const totalSalary = BigInt(data.salary || 0) + extra; + return ( + + ); + }, + }; +} + +function SecretaryClaimantsTable({ + members = [], + membersApi = fellowshipStatisticsMembersApi, + paymentReferenda = [], +}) { + const [total, setTotal] = useState(0); + const [processedData, setProcessedData] = useState([]); + const [rowData, setRowData] = useState([]); + const [tableLoading, setTableLoading] = useState(false); + const pageSize = defaultPageSize; + const { page, component: pageComponent } = usePaginationComponent( + total, + pageSize, + ); + + const columns = [ + useStatisticsClaimantsRankColumn(), + useStatisticsClaimantColumn(), + useStatisticsClaimantsCyclesColumn(), + useSecretaryClaimantsReferendaColumn(paymentReferenda), + useSecretaryClaimantsPaidColumn(paymentReferenda), + ]; + + const { value: originalMembers } = useAsync(async () => { + setTableLoading(true); + if (!membersApi) { + return []; + } + try { + const resp = await backendApi.fetch(membersApi, { + page, + pageSize: defaultPageSize, + }); + return resp?.result || []; + } catch { + setTableLoading(false); + return []; + } + }, [membersApi]); + + useEffect(() => { + if (originalMembers && members) { + const processed = handleClaimantsData(originalMembers, members); + setProcessedData(processed); + setTotal(processed.length); + setTableLoading(false); + } + }, [originalMembers, members]); + + useEffect(() => { + if (processedData.length > 0) { + const paginatedData = paginateData(processedData, page, defaultPageSize); + const rows = paginatedData.map((item, idx) => + columns.map((col) => col.cellRender(item, idx)), + ); + setRowData(rows); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [processedData, page]); + + return ( +
+ + {pageComponent} +
+ ); +} + +export default function SecretaryStatisticsClaimants({ + members = [], + membersApi = fellowshipStatisticsMembersApi, + paymentReferenda = [], +}) { + return ( + +
+ Top Beneficiary + +
+
+ ); +} diff --git a/packages/next-common/components/secretary/statistics/collectives.jsx b/packages/next-common/components/secretary/statistics/collectives.jsx new file mode 100644 index 0000000000..143243e628 --- /dev/null +++ b/packages/next-common/components/secretary/statistics/collectives.jsx @@ -0,0 +1,40 @@ +import { useAsync } from "react-use"; +import { backendApi } from "next-common/services/nextApi"; +import { + secretaryStatisticsCyclesApi, + secretaryStatisticsMembersApi, +} from "next-common/services/url"; +import { TitleContainer } from "next-common/components/styled/containers/titleContainer"; +import SecretaryStatisticsSummary from "next-common/components/secretary/statistics/summary"; +import StatisticsCycles from "next-common/components/fellowship/statistics/expenditure/cycles"; +import SecretaryStatisticsClaimants from "next-common/components/secretary/statistics/claimants"; +import { useFellowshipCollectiveMembers } from "next-common/hooks/fellowship/core/useFellowshipCollectiveMembers"; + +export default function SecretaryCollectivesStatistics() { + const { members } = useFellowshipCollectiveMembers(); + + const { value: data, loading } = useAsync(async () => { + const resp = await backendApi.fetch(secretaryStatisticsCyclesApi); + return resp?.result; + }, []); + + const cycles = data?.cycles || []; + const paymentReferenda = data?.paymentReferenda || []; + + return ( +
+ Salary + + + +
+ ); +} diff --git a/packages/next-common/components/secretary/statistics/summary/index.jsx b/packages/next-common/components/secretary/statistics/summary/index.jsx new file mode 100644 index 0000000000..f6f5784bac --- /dev/null +++ b/packages/next-common/components/secretary/statistics/summary/index.jsx @@ -0,0 +1,66 @@ +import { SecondaryCard } from "next-common/components/styled/containers/secondaryCard"; +import SummaryItem from "next-common/components/summary/layout/item"; +import SummaryLayout from "next-common/components/summary/layout/layout"; +import BigNumber from "bignumber.js"; +import ValueDisplay from "next-common/components/valueDisplay"; +import { getSalaryAsset } from "next-common/utils/consts/getSalaryAsset"; +import { toPrecision } from "next-common/utils"; + +function getTotalSpent(cycles, paymentReferenda = []) { + let total = new BigNumber(0); + + if (cycles && cycles.length > 0) { + total = cycles.reduce((acc, item) => { + const registeredPaid = new BigNumber(item.registeredPaid || 0); + const unRegisteredPaid = new BigNumber(item.unRegisteredPaid || 0); + return acc.plus(registeredPaid).plus(unRegisteredPaid); + }, total); + } + + if (paymentReferenda.length > 0) { + total = paymentReferenda.reduce((acc, item) => { + return acc.plus(new BigNumber(item.value || 0)); + }, total); + } + + return total; +} + +function TotalSpent({ cycles, paymentReferenda }) { + const totalSpent = getTotalSpent(cycles, paymentReferenda); + const { symbol, decimals } = getSalaryAsset(); + return ( + + + + ); +} + +function SpentCycles({ count }) { + return {count}; +} + +export default function SecretaryStatisticsSummary({ + cycles = [], + paymentReferenda = [], + loading, +}) { + if (loading) { + return null; + } + + return ( + + + + + + {paymentReferenda?.length || 0} + + + + ); +} diff --git a/packages/next-common/services/url.js b/packages/next-common/services/url.js index 1ae6328bb0..4050f1a03d 100644 --- a/packages/next-common/services/url.js +++ b/packages/next-common/services/url.js @@ -118,6 +118,11 @@ export const secretarySalaryCycleUnregisteredPaymentsApi = (index) => export const secretarySalaryCycleFeedsApi = (index) => `secretary/salary/cycles/${index}/feeds`; +export const secretaryStatisticsCyclesApi = + "secretary/statistics/salary/cycles"; +export const secretaryStatisticsMembersApi = + "secretary/statistics/salary/members"; + // ambassador export const ambassadorParamsApi = "ambassador/params"; export const ambassadorMembersApiUri = "ambassador/members"; diff --git a/packages/next-common/utils/consts/menu/secretary.js b/packages/next-common/utils/consts/menu/secretary.js index af5f433c4d..9f10181362 100644 --- a/packages/next-common/utils/consts/menu/secretary.js +++ b/packages/next-common/utils/consts/menu/secretary.js @@ -38,6 +38,19 @@ function getSecretarySalaryMenu() { }; } +function getSecretaryStatisticsMenu() { + const chainSettings = getChainSettings(process.env.NEXT_PUBLIC_CHAIN); + if (!chainSettings.modules.secretary) { + return null; + } + + return { + value: "secretary-statistics", + name: Names.statistics, + pathname: "/secretary/statistics", + }; +} + export function getSecretaryMenu() { const chainSettings = getChainSettings(process.env.NEXT_PUBLIC_CHAIN); if (!chainSettings.modules.secretary) { @@ -48,8 +61,10 @@ export function getSecretaryMenu() { name: Names.secretary, icon: , pathname: "/secretary", - items: [getSecretaryMembersMenu(), getSecretarySalaryMenu()].filter( - Boolean, - ), + items: [ + getSecretaryMembersMenu(), + getSecretarySalaryMenu(), + getSecretaryStatisticsMenu(), + ].filter(Boolean), }; } From fc87d29f0b5abb060013c48a13ad26002737db31 Mon Sep 17 00:00:00 2001 From: chaojun Date: Tue, 2 Jun 2026 00:14:43 +0800 Subject: [PATCH 2/6] Update --- .../statistics/expenditure/rank/doughnutChart/index.jsx | 2 +- .../components/secretary/statistics/claimants/index.jsx | 2 +- .../components/secretary/statistics/summary/index.jsx | 7 ++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/next-common/components/fellowship/statistics/expenditure/rank/doughnutChart/index.jsx b/packages/next-common/components/fellowship/statistics/expenditure/rank/doughnutChart/index.jsx index e21ecd39e7..0ce28c67ea 100644 --- a/packages/next-common/components/fellowship/statistics/expenditure/rank/doughnutChart/index.jsx +++ b/packages/next-common/components/fellowship/statistics/expenditure/rank/doughnutChart/index.jsx @@ -63,7 +63,7 @@ function RankChart({ labelDataArr, data }) { diff --git a/packages/next-common/components/secretary/statistics/claimants/index.jsx b/packages/next-common/components/secretary/statistics/claimants/index.jsx index 708d63e1bd..144f70f104 100644 --- a/packages/next-common/components/secretary/statistics/claimants/index.jsx +++ b/packages/next-common/components/secretary/statistics/claimants/index.jsx @@ -58,7 +58,7 @@ function ReferendaCell({ paymentReferenda = [] }) { > #{ref.referendumIndex} diff --git a/packages/next-common/components/secretary/statistics/summary/index.jsx b/packages/next-common/components/secretary/statistics/summary/index.jsx index f6f5784bac..dae148309d 100644 --- a/packages/next-common/components/secretary/statistics/summary/index.jsx +++ b/packages/next-common/components/secretary/statistics/summary/index.jsx @@ -5,6 +5,7 @@ import BigNumber from "bignumber.js"; import ValueDisplay from "next-common/components/valueDisplay"; import { getSalaryAsset } from "next-common/utils/consts/getSalaryAsset"; import { toPrecision } from "next-common/utils"; +import { LoadingContent } from "next-common/components/fellowship/statistics/common"; function getTotalSpent(cycles, paymentReferenda = []) { let total = new BigNumber(0); @@ -49,7 +50,11 @@ export default function SecretaryStatisticsSummary({ loading, }) { if (loading) { - return null; + return ( + + + + ); } return ( From 6c6cd8e6cbee6820764e2135175f6165825f7cfd Mon Sep 17 00:00:00 2001 From: chaojun Date: Tue, 2 Jun 2026 10:23:36 +0800 Subject: [PATCH 3/6] fix: DOT and USDT salary display, #7345 --- .../secretary/statistics/claimants/index.jsx | 24 +++++++---- .../secretary/statistics/summary/index.jsx | 43 +++++++++++-------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/packages/next-common/components/secretary/statistics/claimants/index.jsx b/packages/next-common/components/secretary/statistics/claimants/index.jsx index 144f70f104..df04485e3c 100644 --- a/packages/next-common/components/secretary/statistics/claimants/index.jsx +++ b/packages/next-common/components/secretary/statistics/claimants/index.jsx @@ -91,7 +91,7 @@ function useSecretaryClaimantsReferendaColumn(paymentReferenda) { } function useSecretaryClaimantsPaidColumn(paymentReferenda) { - const referendaTotalByAddress = useMemo(() => { + const referendaValueByAddress = useMemo(() => { const map = {}; for (const ref of paymentReferenda || []) { if (ref.beneficiary) { @@ -106,17 +106,23 @@ function useSecretaryClaimantsPaidColumn(paymentReferenda) { return { name: "Total Paid", - width: 160, className: "text-right", cellRender(data, idx) { - const extra = referendaTotalByAddress[data.who] || BigInt(0); - const totalSalary = BigInt(data.salary || 0) + extra; + const salary = BigInt(data.salary || 0); + const extra = referendaValueByAddress[data.who] || BigInt(0); return ( - +
+ + {extra > 0 && ( + + )} +
); }, }; diff --git a/packages/next-common/components/secretary/statistics/summary/index.jsx b/packages/next-common/components/secretary/statistics/summary/index.jsx index dae148309d..1a73bc3eca 100644 --- a/packages/next-common/components/secretary/statistics/summary/index.jsx +++ b/packages/next-common/components/secretary/statistics/summary/index.jsx @@ -7,35 +7,44 @@ import { getSalaryAsset } from "next-common/utils/consts/getSalaryAsset"; import { toPrecision } from "next-common/utils"; import { LoadingContent } from "next-common/components/fellowship/statistics/common"; -function getTotalSpent(cycles, paymentReferenda = []) { - let total = new BigNumber(0); - +function getCyclesTotalSpent(cycles) { if (cycles && cycles.length > 0) { - total = cycles.reduce((acc, item) => { + return cycles.reduce((total, item) => { const registeredPaid = new BigNumber(item.registeredPaid || 0); const unRegisteredPaid = new BigNumber(item.unRegisteredPaid || 0); - return acc.plus(registeredPaid).plus(unRegisteredPaid); - }, total); + return total.plus(registeredPaid).plus(unRegisteredPaid); + }, new BigNumber(0)); } + return new BigNumber(0); +} - if (paymentReferenda.length > 0) { - total = paymentReferenda.reduce((acc, item) => { - return acc.plus(new BigNumber(item.value || 0)); - }, total); +function getReferendaTotalSpent(paymentReferenda) { + if (paymentReferenda && paymentReferenda.length > 0) { + return paymentReferenda.reduce((total, item) => { + return total.plus(new BigNumber(item.value || 0)); + }, new BigNumber(0)); } - - return total; + return new BigNumber(0); } function TotalSpent({ cycles, paymentReferenda }) { - const totalSpent = getTotalSpent(cycles, paymentReferenda); + const cyclesTotal = getCyclesTotalSpent(cycles); + const referendaTotal = getReferendaTotalSpent(paymentReferenda); const { symbol, decimals } = getSalaryAsset(); return ( - +
+ + {referendaTotal.gt(0) && ( + + )} +
); } From 148fb4c11be4ab105e8ee943e86fb2fa7af88015 Mon Sep 17 00:00:00 2001 From: chaojun Date: Tue, 2 Jun 2026 11:32:01 +0800 Subject: [PATCH 4/6] feat: add salary asset fiat value and breakdown display, #7345 --- .../secretary/statistics/assetBreakdown.jsx | 55 +++++++++++++ .../secretary/statistics/claimants/index.jsx | 81 +++++++++++-------- .../statistics/paymentReferendaTooltip.jsx | 33 ++++++++ .../secretary/statistics/summary/index.jsx | 45 +++++++---- .../common/tokenSymbolAsset.jsx | 9 ++- 5 files changed, 169 insertions(+), 54 deletions(-) create mode 100644 packages/next-common/components/secretary/statistics/assetBreakdown.jsx create mode 100644 packages/next-common/components/secretary/statistics/paymentReferendaTooltip.jsx diff --git a/packages/next-common/components/secretary/statistics/assetBreakdown.jsx b/packages/next-common/components/secretary/statistics/assetBreakdown.jsx new file mode 100644 index 0000000000..922751a34c --- /dev/null +++ b/packages/next-common/components/secretary/statistics/assetBreakdown.jsx @@ -0,0 +1,55 @@ +import BigNumber from "bignumber.js"; +import { cn, formatNum } from "next-common/utils"; +import TokenSymbolAsset from "next-common/components/summary/polkadotTreasurySummary/common/tokenSymbolAsset"; + +export default function AssetBreakdown({ + rows = [], + usdExtra, + align = "left", +}) { + const usdFromRows = (rows || []).reduce((acc, row) => { + const amount = new BigNumber(row.value || 0); + const formattedAmount = amount.div(new BigNumber(10).pow(row.decimals)); + if (row.symbol === "USDT") { + return acc.plus(formattedAmount); + } + if (row.price) { + return acc.plus(formattedAmount.times(row.price)); + } + return acc; + }, new BigNumber(0)); + + const totalUsd = usdExtra ? usdFromRows.plus(usdExtra) : usdFromRows; + + const nonEmptyRows = (rows || []).filter((r) => { + const val = new BigNumber(r.value || 0); + return val.gt(0); + }); + + if (nonEmptyRows.length === 0) { + return null; + } + + return ( +
+
+ ${formatNum(totalUsd.toFixed(2))} +
+
+ {nonEmptyRows.map((row, i) => ( + 6 ? 4 : 2)} + symbol={row.symbol} + /> + ))} +
+
+ ); +} diff --git a/packages/next-common/components/secretary/statistics/claimants/index.jsx b/packages/next-common/components/secretary/statistics/claimants/index.jsx index df04485e3c..d8065a2aa9 100644 --- a/packages/next-common/components/secretary/statistics/claimants/index.jsx +++ b/packages/next-common/components/secretary/statistics/claimants/index.jsx @@ -12,8 +12,9 @@ import usePaginationComponent from "next-common/components/pagination/usePaginat import { SecondaryCard } from "next-common/components/styled/containers/secondaryCard"; import { StatisticsTitle } from "next-common/components/statistics/styled.js"; import { isNil } from "lodash-es"; -import Link from "next-common/components/link"; -import Tooltip from "next-common/components/tooltip"; +import BigNumber from "bignumber.js"; +import PaymentReferendaTooltip from "next-common/components/secretary/statistics/paymentReferendaTooltip"; +import AssetBreakdown from "next-common/components/secretary/statistics/assetBreakdown"; import ValueDisplay from "next-common/components/valueDisplay"; import { getSalaryAsset } from "next-common/utils/consts/getSalaryAsset"; import { toPrecision } from "next-common/utils"; @@ -42,29 +43,11 @@ function ReferendaCell({ paymentReferenda = [] }) { } return ( - - {paymentReferenda.map((ref) => ( - - - {toPrecision(ref.value, ref.decimals)} {ref.symbol} - - {" · "} - {ref.title} - - } - > - - #{ref.referendumIndex} - - - ))} - + + + {paymentReferenda.length} + + ); } @@ -102,6 +85,22 @@ function useSecretaryClaimantsPaidColumn(paymentReferenda) { return map; }, [paymentReferenda]); + const referralUsdByAddress = useMemo(() => { + const map = {}; + for (const ref of paymentReferenda || []) { + if (ref.beneficiary) { + const dotAmount = new BigNumber(ref.value || 0).div( + new BigNumber(10).pow(ref.decimals || 10), + ); + const usd = dotAmount.times(ref.price || 0); + map[ref.beneficiary] = (map[ref.beneficiary] || new BigNumber(0)).plus( + usd, + ); + } + } + return map; + }, [paymentReferenda]); + const { decimals, symbol } = getSalaryAsset(); return { @@ -110,19 +109,33 @@ function useSecretaryClaimantsPaidColumn(paymentReferenda) { cellRender(data, idx) { const salary = BigInt(data.salary || 0); const extra = referendaValueByAddress[data.who] || BigInt(0); - return ( -
+ + if (!extra) { + return ( - {extra > 0 && ( - - )} -
+ ); + } + + const referendaUsd = referralUsdByAddress[data.who] || new BigNumber(0); + + return ( + ); }, }; diff --git a/packages/next-common/components/secretary/statistics/paymentReferendaTooltip.jsx b/packages/next-common/components/secretary/statistics/paymentReferendaTooltip.jsx new file mode 100644 index 0000000000..88648ebaee --- /dev/null +++ b/packages/next-common/components/secretary/statistics/paymentReferendaTooltip.jsx @@ -0,0 +1,33 @@ +import Tooltip from "next-common/components/tooltip"; +import Link from "next-common/components/link"; +import { toPrecision } from "next-common/utils"; + +export default function PaymentReferendaTooltip({ + paymentReferenda = [], + children, +}) { + if (!paymentReferenda || paymentReferenda.length === 0) { + return children; + } + + const content = ( +
+ {paymentReferenda.map((ref) => ( +
+ + #{ref.referendumIndex} + {" · "} + {ref.title} + + {" · "} + {toPrecision(ref.value, ref.decimals)} {ref.symbol} +
+ ))} +
+ ); + + return {children}; +} diff --git a/packages/next-common/components/secretary/statistics/summary/index.jsx b/packages/next-common/components/secretary/statistics/summary/index.jsx index 1a73bc3eca..8837ccbb6f 100644 --- a/packages/next-common/components/secretary/statistics/summary/index.jsx +++ b/packages/next-common/components/secretary/statistics/summary/index.jsx @@ -2,10 +2,9 @@ import { SecondaryCard } from "next-common/components/styled/containers/secondar import SummaryItem from "next-common/components/summary/layout/item"; import SummaryLayout from "next-common/components/summary/layout/layout"; import BigNumber from "bignumber.js"; -import ValueDisplay from "next-common/components/valueDisplay"; -import { getSalaryAsset } from "next-common/utils/consts/getSalaryAsset"; -import { toPrecision } from "next-common/utils"; import { LoadingContent } from "next-common/components/fellowship/statistics/common"; +import PaymentReferendaTooltip from "next-common/components/secretary/statistics/paymentReferendaTooltip"; +import AssetBreakdown from "next-common/components/secretary/statistics/assetBreakdown"; function getCyclesTotalSpent(cycles) { if (cycles && cycles.length > 0) { @@ -27,24 +26,32 @@ function getReferendaTotalSpent(paymentReferenda) { return new BigNumber(0); } +function computeReferendaUsd(paymentReferenda) { + return (paymentReferenda || []).reduce((total, ref) => { + const value = new BigNumber(ref.value || 0); + const amount = value.div(new BigNumber(10).pow(ref.decimals || 10)); + return total.plus(amount.times(ref.price || 0)); + }, new BigNumber(0)); +} + function TotalSpent({ cycles, paymentReferenda }) { const cyclesTotal = getCyclesTotalSpent(cycles); const referendaTotal = getReferendaTotalSpent(paymentReferenda); - const { symbol, decimals } = getSalaryAsset(); + const referendaUsd = computeReferendaUsd(paymentReferenda); + + const rows = [{ value: cyclesTotal.toString(), decimals: 6, symbol: "USDT" }]; + + if (referendaTotal.gt(0)) { + rows.push({ + value: referendaTotal.toString(), + decimals: 10, + symbol: "DOT", + }); + } + return ( -
- - {referendaTotal.gt(0) && ( - - )} -
+
); } @@ -72,7 +79,11 @@ export default function SecretaryStatisticsSummary({ - {paymentReferenda?.length || 0} + + + {paymentReferenda?.length || 0} + + diff --git a/packages/next-common/components/summary/polkadotTreasurySummary/common/tokenSymbolAsset.jsx b/packages/next-common/components/summary/polkadotTreasurySummary/common/tokenSymbolAsset.jsx index c8ab88aa41..e1d75a6839 100644 --- a/packages/next-common/components/summary/polkadotTreasurySummary/common/tokenSymbolAsset.jsx +++ b/packages/next-common/components/summary/polkadotTreasurySummary/common/tokenSymbolAsset.jsx @@ -3,6 +3,7 @@ import ValueDisplay from "next-common/components/valueDisplay"; import { cn } from "next-common/utils"; import { useNativeTokenIcon } from "next-common/components/assethubMigrationAssets/known"; import { useMemo } from "react"; +import { useChainSettings } from "next-common/context/chain"; export default function TokenSymbolAsset({ amount, @@ -10,12 +11,14 @@ export default function TokenSymbolAsset({ valueClassName, type = "", }) { - + const { symbol: nativeSymbol } = useChainSettings(); const NativeAssetIcon = useNativeTokenIcon(); const TokenIcon = useMemo(() => { - return type === "native" ? NativeAssetIcon : AssetIcon; - }, [type, NativeAssetIcon]); + return type === "native" || symbol === nativeSymbol + ? NativeAssetIcon + : AssetIcon; + }, [type, symbol, nativeSymbol, NativeAssetIcon]); return (
From 71d6331809a001958c30f0c9eaddbe46514dff5f Mon Sep 17 00:00:00 2001 From: chaojun Date: Tue, 2 Jun 2026 12:04:39 +0800 Subject: [PATCH 5/6] refactor, #7345 --- .../secretary/statistics/assetBreakdown.jsx | 31 ++------- .../secretary/statistics/breakdown.js | 46 +++++++++++++ .../secretary/statistics/claimants/index.jsx | 69 ++++++++----------- .../secretary/statistics/summary/index.jsx | 51 +++++--------- 4 files changed, 97 insertions(+), 100 deletions(-) create mode 100644 packages/next-common/components/secretary/statistics/breakdown.js diff --git a/packages/next-common/components/secretary/statistics/assetBreakdown.jsx b/packages/next-common/components/secretary/statistics/assetBreakdown.jsx index 922751a34c..a9920bc7e5 100644 --- a/packages/next-common/components/secretary/statistics/assetBreakdown.jsx +++ b/packages/next-common/components/secretary/statistics/assetBreakdown.jsx @@ -1,46 +1,25 @@ import BigNumber from "bignumber.js"; -import { cn, formatNum } from "next-common/utils"; +import { cn } from "next-common/utils"; import TokenSymbolAsset from "next-common/components/summary/polkadotTreasurySummary/common/tokenSymbolAsset"; export default function AssetBreakdown({ + usdTotal, rows = [], - usdExtra, align = "left", }) { - const usdFromRows = (rows || []).reduce((acc, row) => { - const amount = new BigNumber(row.value || 0); - const formattedAmount = amount.div(new BigNumber(10).pow(row.decimals)); - if (row.symbol === "USDT") { - return acc.plus(formattedAmount); - } - if (row.price) { - return acc.plus(formattedAmount.times(row.price)); - } - return acc; - }, new BigNumber(0)); - - const totalUsd = usdExtra ? usdFromRows.plus(usdExtra) : usdFromRows; - - const nonEmptyRows = (rows || []).filter((r) => { - const val = new BigNumber(r.value || 0); - return val.gt(0); - }); - - if (nonEmptyRows.length === 0) { + if (!rows || rows.length === 0) { return null; } return (
-
- ${formatNum(totalUsd.toFixed(2))} -
+
${usdTotal}
- {nonEmptyRows.map((row, i) => ( + {rows.map((row, i) => ( { + const registeredPaid = new BigNumber(item.registeredPaid || 0); + const unRegisteredPaid = new BigNumber(item.unRegisteredPaid || 0); + return total.plus(registeredPaid).plus(unRegisteredPaid); + }, new BigNumber(0)); +} + +export function getReferendaTotal(paymentReferenda) { + if (!paymentReferenda || paymentReferenda.length === 0) { + return new BigNumber(0); + } + return paymentReferenda.reduce((total, item) => { + return total.plus(new BigNumber(item.value || 0)); + }, new BigNumber(0)); +} + +export function getReferendaUsd(paymentReferenda) { + if (!paymentReferenda || paymentReferenda.length === 0) { + return new BigNumber(0); + } + return paymentReferenda.reduce((total, ref) => { + const value = new BigNumber(ref.value || 0); + const amount = value.div(new BigNumber(10).pow(ref.decimals || 10)); + return total.plus(amount.times(ref.price || 0)); + }, new BigNumber(0)); +} + +export function getReferendaTotalByAddress(paymentReferenda, address) { + const refs = (paymentReferenda || []).filter( + (r) => r.beneficiary === address, + ); + return getReferendaTotal(refs); +} + +export function getReferendaUsdByAddress(paymentReferenda, address) { + const refs = (paymentReferenda || []).filter( + (r) => r.beneficiary === address, + ); + return getReferendaUsd(refs); +} diff --git a/packages/next-common/components/secretary/statistics/claimants/index.jsx b/packages/next-common/components/secretary/statistics/claimants/index.jsx index d8065a2aa9..cc59ab3546 100644 --- a/packages/next-common/components/secretary/statistics/claimants/index.jsx +++ b/packages/next-common/components/secretary/statistics/claimants/index.jsx @@ -15,9 +15,13 @@ import { isNil } from "lodash-es"; import BigNumber from "bignumber.js"; import PaymentReferendaTooltip from "next-common/components/secretary/statistics/paymentReferendaTooltip"; import AssetBreakdown from "next-common/components/secretary/statistics/assetBreakdown"; +import { + getReferendaTotalByAddress, + getReferendaUsdByAddress, +} from "next-common/components/secretary/statistics/breakdown"; import ValueDisplay from "next-common/components/valueDisplay"; import { getSalaryAsset } from "next-common/utils/consts/getSalaryAsset"; -import { toPrecision } from "next-common/utils"; +import { formatNum, toPrecision } from "next-common/utils"; function handleClaimantsData(originalMembers, members) { const membersRank = members.reduce((acc, member) => { @@ -74,33 +78,6 @@ function useSecretaryClaimantsReferendaColumn(paymentReferenda) { } function useSecretaryClaimantsPaidColumn(paymentReferenda) { - const referendaValueByAddress = useMemo(() => { - const map = {}; - for (const ref of paymentReferenda || []) { - if (ref.beneficiary) { - map[ref.beneficiary] = - (map[ref.beneficiary] || BigInt(0)) + BigInt(ref.value || 0); - } - } - return map; - }, [paymentReferenda]); - - const referralUsdByAddress = useMemo(() => { - const map = {}; - for (const ref of paymentReferenda || []) { - if (ref.beneficiary) { - const dotAmount = new BigNumber(ref.value || 0).div( - new BigNumber(10).pow(ref.decimals || 10), - ); - const usd = dotAmount.times(ref.price || 0); - map[ref.beneficiary] = (map[ref.beneficiary] || new BigNumber(0)).plus( - usd, - ); - } - } - return map; - }, [paymentReferenda]); - const { decimals, symbol } = getSalaryAsset(); return { @@ -108,9 +85,13 @@ function useSecretaryClaimantsPaidColumn(paymentReferenda) { className: "text-right", cellRender(data, idx) { const salary = BigInt(data.salary || 0); - const extra = referendaValueByAddress[data.who] || BigInt(0); + const address = data.who; + const referendaTotal = getReferendaTotalByAddress( + paymentReferenda, + address, + ); - if (!extra) { + if (referendaTotal.isZero()) { return ( ); }, diff --git a/packages/next-common/components/secretary/statistics/summary/index.jsx b/packages/next-common/components/secretary/statistics/summary/index.jsx index 8837ccbb6f..17a8ddcf34 100644 --- a/packages/next-common/components/secretary/statistics/summary/index.jsx +++ b/packages/next-common/components/secretary/statistics/summary/index.jsx @@ -2,44 +2,29 @@ import { SecondaryCard } from "next-common/components/styled/containers/secondar import SummaryItem from "next-common/components/summary/layout/item"; import SummaryLayout from "next-common/components/summary/layout/layout"; import BigNumber from "bignumber.js"; +import { formatNum } from "next-common/utils"; +import { getSalaryAsset } from "next-common/utils/consts/getSalaryAsset"; import { LoadingContent } from "next-common/components/fellowship/statistics/common"; import PaymentReferendaTooltip from "next-common/components/secretary/statistics/paymentReferendaTooltip"; import AssetBreakdown from "next-common/components/secretary/statistics/assetBreakdown"; - -function getCyclesTotalSpent(cycles) { - if (cycles && cycles.length > 0) { - return cycles.reduce((total, item) => { - const registeredPaid = new BigNumber(item.registeredPaid || 0); - const unRegisteredPaid = new BigNumber(item.unRegisteredPaid || 0); - return total.plus(registeredPaid).plus(unRegisteredPaid); - }, new BigNumber(0)); - } - return new BigNumber(0); -} - -function getReferendaTotalSpent(paymentReferenda) { - if (paymentReferenda && paymentReferenda.length > 0) { - return paymentReferenda.reduce((total, item) => { - return total.plus(new BigNumber(item.value || 0)); - }, new BigNumber(0)); - } - return new BigNumber(0); -} - -function computeReferendaUsd(paymentReferenda) { - return (paymentReferenda || []).reduce((total, ref) => { - const value = new BigNumber(ref.value || 0); - const amount = value.div(new BigNumber(10).pow(ref.decimals || 10)); - return total.plus(amount.times(ref.price || 0)); - }, new BigNumber(0)); -} +import { + getCyclesTotal, + getReferendaTotal, + getReferendaUsd, +} from "next-common/components/secretary/statistics/breakdown"; function TotalSpent({ cycles, paymentReferenda }) { - const cyclesTotal = getCyclesTotalSpent(cycles); - const referendaTotal = getReferendaTotalSpent(paymentReferenda); - const referendaUsd = computeReferendaUsd(paymentReferenda); + const { decimals: salaryDecimals } = getSalaryAsset(); + const cyclesTotal = getCyclesTotal(cycles); + const referendaTotal = getReferendaTotal(paymentReferenda); + const referendaUsd = getReferendaUsd(paymentReferenda); + + const cyclesUsd = cyclesTotal.div(new BigNumber(10).pow(salaryDecimals)); + const usdTotal = formatNum(cyclesUsd.plus(referendaUsd).toFixed(2)); - const rows = [{ value: cyclesTotal.toString(), decimals: 6, symbol: "USDT" }]; + const rows = [ + { value: cyclesTotal.toString(), decimals: salaryDecimals, symbol: "USDT" }, + ]; if (referendaTotal.gt(0)) { rows.push({ @@ -51,7 +36,7 @@ function TotalSpent({ cycles, paymentReferenda }) { return ( - + ); } From a3f54969035e62e3ae8672650bd4594e2c4a4b23 Mon Sep 17 00:00:00 2001 From: chaojun Date: Tue, 2 Jun 2026 12:14:07 +0800 Subject: [PATCH 6/6] refactor, #7345 --- .../components/secretary/statistics/assetBreakdown.jsx | 2 +- .../next-common/components/secretary/statistics/breakdown.js | 2 +- .../components/secretary/statistics/claimants/index.jsx | 2 +- .../components/secretary/statistics/summary/index.jsx | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/next-common/components/secretary/statistics/assetBreakdown.jsx b/packages/next-common/components/secretary/statistics/assetBreakdown.jsx index a9920bc7e5..3d061f8108 100644 --- a/packages/next-common/components/secretary/statistics/assetBreakdown.jsx +++ b/packages/next-common/components/secretary/statistics/assetBreakdown.jsx @@ -23,7 +23,7 @@ export default function AssetBreakdown({ 6 ? 4 : 2)} symbol={row.symbol} /> diff --git a/packages/next-common/components/secretary/statistics/breakdown.js b/packages/next-common/components/secretary/statistics/breakdown.js index 5d29f7f3d1..6aab4ad59f 100644 --- a/packages/next-common/components/secretary/statistics/breakdown.js +++ b/packages/next-common/components/secretary/statistics/breakdown.js @@ -26,7 +26,7 @@ export function getReferendaUsd(paymentReferenda) { } return paymentReferenda.reduce((total, ref) => { const value = new BigNumber(ref.value || 0); - const amount = value.div(new BigNumber(10).pow(ref.decimals || 10)); + const amount = value.div(Math.pow(10, ref.decimals || 10)); return total.plus(amount.times(ref.price || 0)); }, new BigNumber(0)); } diff --git a/packages/next-common/components/secretary/statistics/claimants/index.jsx b/packages/next-common/components/secretary/statistics/claimants/index.jsx index cc59ab3546..8dc0a530b4 100644 --- a/packages/next-common/components/secretary/statistics/claimants/index.jsx +++ b/packages/next-common/components/secretary/statistics/claimants/index.jsx @@ -103,7 +103,7 @@ function useSecretaryClaimantsPaidColumn(paymentReferenda) { const referendaUsd = getReferendaUsdByAddress(paymentReferenda, address); const cyclesUsd = new BigNumber(salary.toString()).div( - new BigNumber(10).pow(decimals), + Math.pow(10, decimals), ); const usdTotal = cyclesUsd.plus(referendaUsd).toFixed(2); diff --git a/packages/next-common/components/secretary/statistics/summary/index.jsx b/packages/next-common/components/secretary/statistics/summary/index.jsx index 17a8ddcf34..9fa49dd077 100644 --- a/packages/next-common/components/secretary/statistics/summary/index.jsx +++ b/packages/next-common/components/secretary/statistics/summary/index.jsx @@ -1,7 +1,6 @@ import { SecondaryCard } from "next-common/components/styled/containers/secondaryCard"; import SummaryItem from "next-common/components/summary/layout/item"; import SummaryLayout from "next-common/components/summary/layout/layout"; -import BigNumber from "bignumber.js"; import { formatNum } from "next-common/utils"; import { getSalaryAsset } from "next-common/utils/consts/getSalaryAsset"; import { LoadingContent } from "next-common/components/fellowship/statistics/common"; @@ -19,7 +18,7 @@ function TotalSpent({ cycles, paymentReferenda }) { const referendaTotal = getReferendaTotal(paymentReferenda); const referendaUsd = getReferendaUsd(paymentReferenda); - const cyclesUsd = cyclesTotal.div(new BigNumber(10).pow(salaryDecimals)); + const cyclesUsd = cyclesTotal.div(Math.pow(10, salaryDecimals)); const usdTotal = formatNum(cyclesUsd.plus(referendaUsd).toFixed(2)); const rows = [