Skip to content

Commit

Permalink
feat: fellowship salary claimants (#4071)
Browse files Browse the repository at this point in the history
* init files

* update

* filter

* extract consts

* fix: read value

* fix: responsive

* fix status from datalist

* add hover for registered

* order by rank desc

* sort claim status
  • Loading branch information
2nthony authored Apr 2, 2024
1 parent f5ab5f6 commit 5cc9cad
Show file tree
Hide file tree
Showing 11 changed files with 292 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const CLAIM_STATS = {
Registered: "Registered",
Nothing: "Nothing",
Attempted: "Attempted",
};

export const claimStatsValues = Object.values(CLAIM_STATS);

export const claimantListColumns = [
{
name: "Rank",
width: 80,
},
{
name: "Claimant",
className: "min-w-[200px]",
},
{
name: "isRegistered",
className: "text-right",
width: 160,
},
{
name: "Last Active At",
className: "text-right",
width: 160,
},
{
name: "Active Salary",
className: "text-right",
width: 160,
},
{
name: "Passive Salary",
className: "text-right",
width: 160,
},
{
name: <span>Status</span>,
className: "text-right",
width: 160,
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import FellowshipSalaryActiveCycle from "../cycles/current";
import FellowshipSalaryClaimants from "./list";

export default function FellowshipSalaryClaimantsContainer() {
return (
<div className="space-y-6">
<FellowshipSalaryActiveCycle />
<FellowshipSalaryClaimants />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SystemVoteAbstain, SystemVoteAye } from "@osn/icons/subsquare";

export default function FellowshipSalaryMemberIsRegistered({ status }) {
const registered = status?.attempted || status?.registered;

return registered ? (
<SystemVoteAye className="inline-block w-4 h-4" />
) : (
<SystemVoteAbstain className="inline-block w-4 h-4" />
);
}
115 changes: 115 additions & 0 deletions packages/next-common/components/fellowship/salary/claimants/list.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import DataList from "next-common/components/dataList";
import { SecondaryCard } from "next-common/components/styled/containers/secondaryCard";
import FellowshipRank from "../../rank";
import { usePageProps } from "next-common/context/page";
import { find, has, isNil, orderBy } from "lodash-es";
import AddressUser from "next-common/components/user/addressUser";
import FellowshipSalaryMemberIsRegistered from "./isRegistered";
import Link from "next/link";
import { useSalaryAsset } from "next-common/hooks/useSalaryAsset";
import ValueDisplay from "next-common/components/valueDisplay";
import { toPrecision } from "next-common/utils";
import FellowshipSalaryMemberStatus from "./status";
import useRankFilter from "next-common/hooks/fellowship/useRankFilter";
import { TitleContainer } from "next-common/components/styled/containers/titleContainer";
import { useFellowshipSalaryMemberStatusFilter } from "next-common/hooks/fellowship/salary/useFellowshipSalaryStatusFilter";
import { claimStatsValues, claimantListColumns } from "./consts";

export default function FellowshipSalaryClaimants() {
const { fellowshipParams, fellowshipMembers, fellowshipSalaryClaimants } =
usePageProps();
const { symbol, decimals } = useSalaryAsset();

const resolvedClaimants = orderBy(
fellowshipSalaryClaimants.map((claimant) => {
const address = claimant?.address;
const member = find(fellowshipMembers, { address });
const rank = member?.rank;

return {
rank,
...claimant,
};
}),
"rank",
"desc",
);

const ranks = [...new Set(fellowshipMembers.map((m) => m.rank))];
const { rank, component: rankFilterComponent } = useRankFilter(ranks);

const { status, component: statusFilterComponent } =
useFellowshipSalaryMemberStatusFilter(claimStatsValues);

const filteredClaimants =
isNil(rank) && isNil(status)
? resolvedClaimants
: resolvedClaimants.filter((claimant) => {
return (
(isNil(rank) || claimant.rank === rank) &&
(isNil(status) || has(claimant?.status?.status, status))
);
});

const { activeSalary = [], passiveSalary = [] } = fellowshipParams ?? {};

const rows = filteredClaimants?.map((claimant) => {
const address = claimant?.address;

return [
<FellowshipRank key={`rank-${address}`} rank={claimant.rank} />,
<AddressUser key={`address-${address}`} add={address} />,
<FellowshipSalaryMemberIsRegistered
key={`isRegistered-${address}`}
status={claimant?.status?.status}
/>,
<Link
key={`lastActive-${address}`}
href={`/fellowship/salary/cycles/${claimant?.status?.lastActive}`}
className="text14Medium text-theme500"
>
#{claimant?.status?.lastActive}
</Link>,
<ValueDisplay
key={`active-salary-${address}`}
value={toPrecision(activeSalary[claimant.rank - 1] || 0, decimals)}
symbol={symbol}
/>,
<ValueDisplay
key={`passive-salary-${address}`}
value={toPrecision(passiveSalary[claimant.rank - 1] || 0, decimals)}
symbol={symbol}
/>,
<FellowshipSalaryMemberStatus
key={`status-${address}`}
status={claimant?.status?.status}
/>,
];
});

return (
<div>
<TitleContainer className="gap-3">
<div>
List
<span className="text-textTertiary text14Medium ml-1">
{filteredClaimants.length}
</span>
</div>
<div className="flex items-center gap-x-4">
{rankFilterComponent}
{statusFilterComponent}
</div>
</TitleContainer>

<SecondaryCard className="mt-4">
<DataList
className="text14Medium"
columns={claimantListColumns}
noDataText="No Claimants"
rows={rows}
/>
</SecondaryCard>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { has } from "lodash-es";
import Tooltip from "next-common/components/tooltip";
import { useSalaryAsset } from "next-common/hooks/useSalaryAsset";
import { toPrecision } from "next-common/utils";

export default function FellowshipSalaryMemberStatus({ status }) {
const { decimals, symbol } = useSalaryAsset();

let content = <span className="text-textTertiary">-</span>;
let tooltipContent;

if (has(status, "attempted")) {
content = <span className="text-blue500">Attempted</span>;
} else if (has(status, "registered")) {
tooltipContent = `${toPrecision(status.registered, decimals)} ${symbol}`;
content = <span className="text-green500">Registered</span>;
} else if (has(status, "nothing")) {
content = <span className="text-textTertiary">Nothing</span>;
}

return <Tooltip content={tooltipContent}>{content}</Tooltip>;
}
5 changes: 5 additions & 0 deletions packages/next-common/components/fellowship/salary/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ export default function FellowshipSalaryCommon({ children, ...props }) {
url: "/fellowship/salary",
exactMatch: true,
},
{
label: "Claimants",
url: "/fellowship/salary/claimants",
exactMatch: true,
},
{
label: "Feeds",
url: "/fellowship/salary/feeds",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { lowerFirst } from "lodash-es";
import Select from "next-common/components/select";
import { useState } from "react";

export function useFellowshipSalaryMemberStatusFilter(statusValues = []) {
const options = [
{
label: "All",
value: null,
},
...statusValues.map((value) => {
return {
label: value,
value: lowerFirst(value),
};
}),
];

const [status, setStatus] = useState(options[0].value);

const component = (
<div className="text12Medium text-textPrimary flex items-center gap-x-2">
<div>Status</div>
<Select
className="w-32"
small
value={status}
options={options}
onChange={(option) => {
setStatus(option.value);
}}
/>
</div>
);

return {
status,
component,
};
}
2 changes: 1 addition & 1 deletion packages/next-common/hooks/fellowship/useRankFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function useRankFilter(ranks = []) {
}));

options.unshift({
label: "-",
label: "All",
value: null,
});

Expand Down
2 changes: 2 additions & 0 deletions packages/next-common/services/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export const fellowshipSalaryCycleUnregisteredPaymentsApi = (index) =>
export const fellowshipSalaryCycleFeedsApi = (index) =>
`fellowship/salary/cycles/${index}/feeds`;

export const fellowshipSalaryClaimantsApi = "fellowship/salary/claimants";

// calender events
/**
* param `begin_time`, `end_time`
Expand Down
1 change: 1 addition & 0 deletions packages/next-common/utils/consts/menu/fellowship.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ function getFellowshipSalaryMenu() {
pathname: "/fellowship/salary",
extraMatchNavMenuActivePathnames: [
"/fellowship/salary/feeds",
"/fellowship/salary/claimants",
"/fellowship/salary/cycles/[...params]",
],
};
Expand Down
41 changes: 41 additions & 0 deletions packages/next/pages/fellowship/salary/claimants/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import FellowshipSalaryCommon from "next-common/components/fellowship/salary/common";
import FellowshipSalaryClaimantsContainer from "next-common/components/fellowship/salary/claimants/container";
import { withCommonProps } from "next-common/lib";
import { fetchOpenGovTracksProps } from "next-common/services/serverSide";
import nextApi from "next-common/services/nextApi";
import {
fellowshipMembersApiUri,
fellowshipParamsApi,
fellowshipSalaryClaimantsApi,
} from "next-common/services/url";

export default function FellowshipSalaryClaimantsPage() {
return (
<FellowshipSalaryCommon>
<FellowshipSalaryClaimantsContainer />
</FellowshipSalaryCommon>
);
}

export const getServerSideProps = withCommonProps(async () => {
const [
tracksProps,
{ result: fellowshipMembers },
{ result: fellowshipParams = {} },
{ result: fellowshipSalaryClaimants },
] = await Promise.all([
fetchOpenGovTracksProps(),
nextApi.fetch(fellowshipMembersApiUri),
nextApi.fetch(fellowshipParamsApi),
nextApi.fetch(fellowshipSalaryClaimantsApi),
]);

return {
props: {
...tracksProps,
fellowshipMembers,
fellowshipParams,
fellowshipSalaryClaimants,
},
};
});

0 comments on commit 5cc9cad

Please sign in to comment.