feat(mining-admin-web): update contribution records display to match backend API

- Update ContributionRecord type to match backend response fields (sourceType, baseContribution, distributionRate, etc.)
- Update contribution-records-list component with improved UI showing source type badges, user info, tree count, and expiry status

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-12 07:30:00 -08:00
parent dbe9ab223f
commit 04fd7b946a
2 changed files with 64 additions and 29 deletions

View File

@ -3,22 +3,26 @@
import { useState } from 'react'; import { useState } from 'react';
import { useContributionRecords } from '../hooks/use-users'; import { useContributionRecords } from '../hooks/use-users';
import { formatDecimal, formatPercent } from '@/lib/utils/format'; import { formatDecimal, formatPercent } from '@/lib/utils/format';
import { formatDateTime } from '@/lib/utils/date'; import { formatDateTime, formatDate } from '@/lib/utils/date';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Skeleton } from '@/components/ui/skeleton'; import { Skeleton } from '@/components/ui/skeleton';
import { Badge } from '@/components/ui/badge';
import { ChevronLeft, ChevronRight } from 'lucide-react'; import { ChevronLeft, ChevronRight } from 'lucide-react';
const contributionTypeLabels: Record<string, string> = { const sourceTypeLabels: Record<string, string> = {
PERSONAL: '个人', PERSONAL: '个人认种',
SYSTEM_OPERATION: '系统运营',
SYSTEM_PROVINCE: '系统省级',
SYSTEM_CITY: '系统市级',
TEAM_LEVEL: '团队层级', TEAM_LEVEL: '团队层级',
TEAM_BONUS: '团队奖励', TEAM_BONUS: '团队奖励',
}; };
const sourceTypeBadgeVariant: Record<string, 'default' | 'secondary' | 'outline'> = {
PERSONAL: 'default',
TEAM_LEVEL: 'secondary',
TEAM_BONUS: 'outline',
};
interface ContributionRecordsListProps { interface ContributionRecordsListProps {
accountSequence: string; accountSequence: string;
} }
@ -35,20 +39,22 @@ export function ContributionRecordsList({ accountSequence }: ContributionRecords
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead></TableHead> <TableHead></TableHead>
<TableHead></TableHead> <TableHead></TableHead>
<TableHead></TableHead> <TableHead className="text-right"></TableHead>
<TableHead className="text-right"></TableHead> <TableHead className="text-right"></TableHead>
<TableHead className="text-right"></TableHead> <TableHead className="text-right"></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead>/</TableHead> <TableHead>/</TableHead>
<TableHead></TableHead> <TableHead></TableHead>
<TableHead></TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{isLoading ? ( {isLoading ? (
[...Array(5)].map((_, i) => ( [...Array(5)].map((_, i) => (
<TableRow key={i}> <TableRow key={i}>
{[...Array(7)].map((_, j) => ( {[...Array(9)].map((_, j) => (
<TableCell key={j}> <TableCell key={j}>
<Skeleton className="h-4 w-full" /> <Skeleton className="h-4 w-full" />
</TableCell> </TableCell>
@ -57,18 +63,34 @@ export function ContributionRecordsList({ accountSequence }: ContributionRecords
)) ))
) : data?.items.length === 0 ? ( ) : data?.items.length === 0 ? (
<TableRow> <TableRow>
<TableCell colSpan={7} className="text-center py-8 text-muted-foreground"> <TableCell colSpan={9} className="text-center py-8 text-muted-foreground">
</TableCell> </TableCell>
</TableRow> </TableRow>
) : ( ) : (
data?.items.map((record) => ( data?.items.map((record) => (
<TableRow key={record.id}> <TableRow key={record.id} className={record.isExpired ? 'opacity-50' : ''}>
<TableCell className="font-mono">{record.sourceAccountSequence}</TableCell> <TableCell>
<TableCell>{contributionTypeLabels[record.contributionType] || record.contributionType}</TableCell> <Badge variant={sourceTypeBadgeVariant[record.sourceType] || 'default'}>
<TableCell>{record.distributionType === 'UPSTREAM' ? '上游' : '下游'}</TableCell> {sourceTypeLabels[record.sourceType] || record.sourceType}
<TableCell className="text-right">{formatPercent(record.rate)}</TableCell> </Badge>
<TableCell className="text-right font-mono">{formatDecimal(record.amount, 4)}</TableCell> </TableCell>
<TableCell>
{record.sourceType === 'PERSONAL' ? (
<span className="text-muted-foreground"></span>
) : record.sourceUserInfo ? (
<div className="flex flex-col">
<span className="font-medium">{record.sourceUserInfo.nickname || record.sourceUserInfo.phone}</span>
<span className="text-xs text-muted-foreground font-mono">{record.sourceAccountSequence}</span>
</div>
) : (
<span className="font-mono text-sm">{record.sourceAccountSequence}</span>
)}
</TableCell>
<TableCell className="text-right">{record.treeCount}</TableCell>
<TableCell className="text-right font-mono">{formatDecimal(record.baseContribution, 2)}</TableCell>
<TableCell className="text-right">{formatPercent(record.distributionRate)}</TableCell>
<TableCell className="text-right font-mono font-medium">{formatDecimal(record.amount, 4)}</TableCell>
<TableCell> <TableCell>
{record.levelDepth !== null {record.levelDepth !== null
? `L${record.levelDepth}` ? `L${record.levelDepth}`
@ -76,7 +98,14 @@ export function ContributionRecordsList({ accountSequence }: ContributionRecords
? `T${record.bonusTier}` ? `T${record.bonusTier}`
: '-'} : '-'}
</TableCell> </TableCell>
<TableCell className="text-sm">{formatDateTime(record.createdAt)}</TableCell> <TableCell className="text-sm">{formatDate(record.effectiveDate)}</TableCell>
<TableCell>
{record.isExpired ? (
<Badge variant="destructive"></Badge>
) : (
<Badge variant="secondary"></Badge>
)}
</TableCell>
</TableRow> </TableRow>
)) ))
)} )}

View File

@ -65,15 +65,21 @@ export interface ContributionBreakdown {
export interface ContributionRecord { export interface ContributionRecord {
id: string; id: string;
accountSequence: number; sourceType: string; // PERSONAL, TEAM_LEVEL, TEAM_BONUS
sourceAccountSequence: number; sourceAccountSequence: string;
adoptionId: string; sourceUserInfo?: {
contributionType: string; nickname: string | null;
distributionType: string; phone: string;
amount: string; } | null;
rate: string; treeCount: number;
baseContribution: string;
distributionRate: string;
levelDepth: number | null; levelDepth: number | null;
bonusTier: number | null; bonusTier: number | null;
amount: string;
effectiveDate: string;
expireDate: string;
isExpired: boolean;
createdAt: string; createdAt: string;
} }