feat(mining-admin-web): 添加全局兑换记录页面

后端(trading-service):
- OrderRepository 新增 findAllOrders/findAllTrades 全局查询方法
- AdminController 新增 GET /admin/orders 和 GET /admin/trades 端点
  支持 type/status/source/search/日期范围筛选 + 分页

前端(mining-admin-web):
- 新增 /exchange-records 页面,包含「订单记录」和「成交明细」两个 Tab
- 订单 Tab: 支持按类型/状态/来源筛选,显示订单号/账号/价格/数量等
- 成交 Tab: 支持按来源筛选,显示买卖双方/价格/数量/销毁量/手续费等
- 侧边栏添加「兑换记录」菜单项

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-02 07:38:46 -08:00
parent a55201b3b3
commit 41818eb8e2
6 changed files with 670 additions and 2 deletions

View File

@ -1,8 +1,10 @@
import { Controller, Get, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { Controller, Get, Post, Body, Query, HttpCode, HttpStatus } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger';
import { IsBoolean } from 'class-validator';
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
import { TradingConfigRepository } from '../../infrastructure/persistence/repositories/trading-config.repository';
import { OrderRepository } from '../../infrastructure/persistence/repositories/order.repository';
import { OrderType, OrderStatus, OrderSource } from '../../domain/aggregates/order.aggregate';
import { Public } from '../../shared/guards/jwt-auth.guard';
class SetBuyEnabledDto {
@ -21,6 +23,7 @@ export class AdminController {
constructor(
private readonly prisma: PrismaService,
private readonly tradingConfigRepository: TradingConfigRepository,
private readonly orderRepository: OrderRepository,
) {}
@Get('accounts/sync')
@ -184,6 +187,88 @@ export class AdminController {
};
}
@Get('orders')
@Public()
@ApiOperation({ summary: '获取全局订单列表' })
@ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'pageSize', required: false, type: Number })
@ApiQuery({ name: 'type', required: false, enum: ['BUY', 'SELL'] })
@ApiQuery({ name: 'status', required: false, enum: ['PENDING', 'PARTIAL', 'FILLED', 'CANCELLED'] })
@ApiQuery({ name: 'source', required: false, enum: ['USER', 'MARKET_MAKER', 'DEX_BOT', 'SYSTEM'] })
@ApiQuery({ name: 'search', required: false, type: String })
@ApiQuery({ name: 'startDate', required: false, type: String })
@ApiQuery({ name: 'endDate', required: false, type: String })
async getOrders(
@Query('page') page?: number,
@Query('pageSize') pageSize?: number,
@Query('type') type?: string,
@Query('status') status?: string,
@Query('source') source?: string,
@Query('search') search?: string,
@Query('startDate') startDate?: string,
@Query('endDate') endDate?: string,
) {
const p = page ? Number(page) : 1;
const ps = pageSize ? Number(pageSize) : 20;
const result = await this.orderRepository.findAllOrders({
type: type as OrderType | undefined,
status: status as OrderStatus | undefined,
source: source as OrderSource | undefined,
search: search || undefined,
startDate: startDate ? new Date(startDate) : undefined,
endDate: endDate ? new Date(endDate) : undefined,
page: p,
pageSize: ps,
});
return {
data: result.data,
total: result.total,
page: p,
pageSize: ps,
totalPages: Math.ceil(result.total / ps),
};
}
@Get('trades')
@Public()
@ApiOperation({ summary: '获取全局成交记录' })
@ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'pageSize', required: false, type: Number })
@ApiQuery({ name: 'source', required: false, enum: ['USER', 'MARKET_MAKER', 'DEX_BOT', 'SYSTEM'] })
@ApiQuery({ name: 'search', required: false, type: String })
@ApiQuery({ name: 'startDate', required: false, type: String })
@ApiQuery({ name: 'endDate', required: false, type: String })
async getTrades(
@Query('page') page?: number,
@Query('pageSize') pageSize?: number,
@Query('source') source?: string,
@Query('search') search?: string,
@Query('startDate') startDate?: string,
@Query('endDate') endDate?: string,
) {
const p = page ? Number(page) : 1;
const ps = pageSize ? Number(pageSize) : 20;
const result = await this.orderRepository.findAllTrades({
source: source || undefined,
search: search || undefined,
startDate: startDate ? new Date(startDate) : undefined,
endDate: endDate ? new Date(endDate) : undefined,
page: p,
pageSize: ps,
});
return {
data: result.data,
total: result.total,
page: p,
pageSize: ps,
totalPages: Math.ceil(result.total / ps),
};
}
@Post('trading/depth-enabled')
@Public() // TODO: 生产环境应添加管理员权限验证
@HttpCode(HttpStatus.OK)

View File

@ -324,6 +324,154 @@ export class OrderRepository {
};
}
/**
*
*/
async findAllOrders(options?: {
type?: OrderType;
status?: OrderStatus;
source?: OrderSource;
search?: string;
startDate?: Date;
endDate?: Date;
page?: number;
pageSize?: number;
}): Promise<{ data: any[]; total: number }> {
const where: any = {};
if (options?.type) where.type = options.type;
if (options?.status) where.status = options.status;
if (options?.source) where.source = options.source;
if (options?.search) {
where.OR = [
{ accountSequence: { contains: options.search } },
{ orderNo: { contains: options.search } },
];
}
if (options?.startDate || options?.endDate) {
where.createdAt = {};
if (options?.startDate) where.createdAt.gte = options.startDate;
if (options?.endDate) where.createdAt.lte = options.endDate;
}
const page = options?.page ?? 1;
const pageSize = options?.pageSize ?? 20;
const [records, total] = await Promise.all([
this.prisma.order.findMany({
where,
orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
}),
this.prisma.order.count({ where }),
]);
return {
data: records.map((o) => ({
id: o.id,
orderNo: o.orderNo,
accountSequence: o.accountSequence,
type: o.type,
status: o.status,
source: o.source,
sourceLabel: o.sourceLabel,
price: o.price.toString(),
quantity: o.quantity.toString(),
filledQuantity: o.filledQuantity.toString(),
remainingQuantity: o.remainingQuantity.toString(),
averagePrice: o.averagePrice.toString(),
totalAmount: o.totalAmount.toString(),
burnQuantity: o.burnQuantity.toString(),
burnMultiplier: o.burnMultiplier.toString(),
effectiveQuantity: o.effectiveQuantity.toString(),
createdAt: o.createdAt,
completedAt: o.completedAt,
cancelledAt: o.cancelledAt,
})),
total,
};
}
/**
*
*/
async findAllTrades(options?: {
source?: string;
search?: string;
startDate?: Date;
endDate?: Date;
page?: number;
pageSize?: number;
}): Promise<{ data: any[]; total: number }> {
const where: any = {};
if (options?.source) {
where.OR = [
{ buyerSource: options.source },
{ sellerSource: options.source },
];
}
if (options?.search) {
const searchCondition = {
OR: [
{ buyerSequence: { contains: options.search } },
{ sellerSequence: { contains: options.search } },
{ tradeNo: { contains: options.search } },
],
};
if (where.OR) {
where.AND = [{ OR: where.OR }, searchCondition];
delete where.OR;
} else {
where.OR = searchCondition.OR;
}
}
if (options?.startDate || options?.endDate) {
where.createdAt = {};
if (options?.startDate) where.createdAt.gte = options.startDate;
if (options?.endDate) where.createdAt.lte = options.endDate;
}
const page = options?.page ?? 1;
const pageSize = options?.pageSize ?? 20;
const [records, total] = await Promise.all([
this.prisma.trade.findMany({
where,
orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
}),
this.prisma.trade.count({ where }),
]);
return {
data: records.map((t) => ({
id: t.id,
tradeNo: t.tradeNo,
buyOrderId: t.buyOrderId,
sellOrderId: t.sellOrderId,
buyerSequence: t.buyerSequence,
sellerSequence: t.sellerSequence,
price: t.price.toString(),
quantity: t.quantity.toString(),
originalQuantity: t.originalQuantity.toString(),
burnQuantity: t.burnQuantity.toString(),
amount: t.amount.toString(),
fee: t.fee.toString(),
buyerSource: t.buyerSource,
sellerSource: t.sellerSource,
createdAt: t.createdAt,
})),
total,
};
}
private toDomain(record: any): OrderAggregate {
return OrderAggregate.reconstitute({
id: record.id,

View File

@ -0,0 +1,336 @@
'use client';
import { useState } from 'react';
import { PageHeader } from '@/components/layout/page-header';
import { useAdminOrders, useAdminTrades } from '@/features/trading/hooks/use-trading';
import type { AdminOrder, AdminTrade } from '@/features/trading/api/trading.api';
import { Card, CardContent } from '@/components/ui/card';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Skeleton } from '@/components/ui/skeleton';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { ChevronLeft, ChevronRight, Search } from 'lucide-react';
import { formatDecimal } from '@/lib/utils/format';
import { formatDateTime } from '@/lib/utils/date';
const orderStatusLabels: Record<string, { label: string; className: string }> = {
PENDING: { label: '待成交', className: 'bg-yellow-100 text-yellow-700' },
PARTIAL: { label: '部分成交', className: 'bg-blue-100 text-blue-700' },
FILLED: { label: '已成交', className: 'bg-green-100 text-green-700' },
CANCELLED: { label: '已取消', className: 'bg-gray-100 text-gray-600' },
};
const sourceLabels: Record<string, { label: string; className: string }> = {
USER: { label: '用户', className: 'bg-gray-100 text-gray-700' },
MARKET_MAKER: { label: '做市商', className: 'bg-purple-100 text-purple-700' },
DEX_BOT: { label: 'DEX Bot', className: 'bg-blue-100 text-blue-700' },
SYSTEM: { label: '系统', className: 'bg-gray-100 text-gray-600' },
};
export default function ExchangeRecordsPage() {
const [tab, setTab] = useState<'orders' | 'trades'>('orders');
// 订单 tab 状态
const [ordersPage, setOrdersPage] = useState(1);
const [orderType, setOrderType] = useState<string>('ALL');
const [orderStatus, setOrderStatus] = useState<string>('ALL');
const [orderSource, setOrderSource] = useState<string>('ALL');
// 成交 tab 状态
const [tradesPage, setTradesPage] = useState(1);
const [tradeSource, setTradeSource] = useState<string>('ALL');
// 共享搜索状态
const [searchInput, setSearchInput] = useState('');
const [search, setSearch] = useState('');
const pageSize = 20;
const { data: ordersData, isLoading: ordersLoading } = useAdminOrders({
page: ordersPage,
pageSize,
type: orderType === 'ALL' ? undefined : orderType as 'BUY' | 'SELL',
status: orderStatus === 'ALL' ? undefined : orderStatus as any,
source: orderSource === 'ALL' ? undefined : orderSource as any,
search: search || undefined,
});
const { data: tradesData, isLoading: tradesLoading } = useAdminTrades({
page: tradesPage,
pageSize,
source: tradeSource === 'ALL' ? undefined : tradeSource as any,
search: search || undefined,
});
const handleSearch = () => {
setSearch(searchInput);
setOrdersPage(1);
setTradesPage(1);
};
const handleClearSearch = () => {
setSearchInput('');
setSearch('');
setOrdersPage(1);
setTradesPage(1);
};
const handleTabChange = (value: string) => {
setTab(value as 'orders' | 'trades');
};
const renderBadge = (text: string, className: string) => (
<span className={`inline-flex items-center px-2 py-1 rounded text-xs font-medium ${className}`}>
{text}
</span>
);
const renderSkeletonRows = (cols: number) =>
[...Array(5)].map((_, i) => (
<TableRow key={i}>
{[...Array(cols)].map((_, j) => (
<TableCell key={j}><Skeleton className="h-4 w-full" /></TableCell>
))}
</TableRow>
));
const renderPagination = (
data: { total: number; totalPages: number } | undefined,
page: number,
setPage: (p: number) => void,
) => {
if (!data || data.totalPages <= 1) return null;
return (
<div className="flex items-center justify-between p-4 border-t">
<p className="text-sm text-muted-foreground">
{data.total} {page} / {data.totalPages}
</p>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm" onClick={() => setPage(page - 1)} disabled={page <= 1}>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm" onClick={() => setPage(page + 1)} disabled={page >= data.totalPages}>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
);
};
return (
<div className="space-y-6">
<PageHeader title="兑换记录" description="查看全部积分股兑换订单与成交明细" />
{/* 搜索栏 */}
<Card>
<CardContent className="p-4">
<div className="flex gap-2">
<Input
placeholder="搜索账号、订单号、成交号..."
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
className="max-w-md"
/>
<Button onClick={handleSearch} variant="outline">
<Search className="h-4 w-4 mr-2" />
</Button>
{search && (
<Button onClick={handleClearSearch} variant="ghost">
</Button>
)}
</div>
</CardContent>
</Card>
{/* Tabs */}
<Tabs value={tab} onValueChange={handleTabChange}>
<TabsList>
<TabsTrigger value="orders"></TabsTrigger>
<TabsTrigger value="trades"></TabsTrigger>
</TabsList>
{/* 订单记录 Tab */}
<TabsContent value="orders" className="space-y-4">
{/* 筛选栏 */}
<Card>
<CardContent className="p-4">
<div className="flex gap-3 flex-wrap">
<Select value={orderType} onValueChange={(v) => { setOrderType(v); setOrdersPage(1); }}>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL"></SelectItem>
<SelectItem value="BUY"></SelectItem>
<SelectItem value="SELL"></SelectItem>
</SelectContent>
</Select>
<Select value={orderStatus} onValueChange={(v) => { setOrderStatus(v); setOrdersPage(1); }}>
<SelectTrigger className="w-[130px]">
<SelectValue placeholder="状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL"></SelectItem>
<SelectItem value="PENDING"></SelectItem>
<SelectItem value="PARTIAL"></SelectItem>
<SelectItem value="FILLED"></SelectItem>
<SelectItem value="CANCELLED"></SelectItem>
</SelectContent>
</Select>
<Select value={orderSource} onValueChange={(v) => { setOrderSource(v); setOrdersPage(1); }}>
<SelectTrigger className="w-[130px]">
<SelectValue placeholder="来源" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL"></SelectItem>
<SelectItem value="USER"></SelectItem>
<SelectItem value="MARKET_MAKER"></SelectItem>
<SelectItem value="DEX_BOT">DEX Bot</SelectItem>
<SelectItem value="SYSTEM"></SelectItem>
</SelectContent>
</Select>
</div>
</CardContent>
</Card>
{/* 订单表格 */}
<Card>
<CardContent className="p-0">
<Table>
<TableHeader>
<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></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{ordersLoading ? renderSkeletonRows(10) : !ordersData?.data?.length ? (
<TableRow>
<TableCell colSpan={10} className="text-center py-8 text-muted-foreground">
</TableCell>
</TableRow>
) : (
ordersData.data.map((order: AdminOrder) => {
const status = orderStatusLabels[order.status] || { label: order.status, className: '' };
const source = sourceLabels[order.source] || { label: order.source, className: '' };
return (
<TableRow key={order.id}>
<TableCell className="font-mono text-xs max-w-[120px] truncate" title={order.orderNo}>
{order.orderNo}
</TableCell>
<TableCell className="font-mono text-xs">{order.accountSequence}</TableCell>
<TableCell>
{renderBadge(
order.type === 'BUY' ? '买入' : '卖出',
order.type === 'BUY' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700',
)}
</TableCell>
<TableCell>{renderBadge(status.label, status.className)}</TableCell>
<TableCell>{renderBadge(source.label, source.className)}</TableCell>
<TableCell className="text-right font-mono text-sm">{formatDecimal(order.price, 8)}</TableCell>
<TableCell className="text-right font-mono text-sm">{formatDecimal(order.quantity, 2)}</TableCell>
<TableCell className="text-right font-mono text-sm">{formatDecimal(order.filledQuantity, 2)}</TableCell>
<TableCell className="text-right font-mono text-sm">{formatDecimal(order.totalAmount, 2)}</TableCell>
<TableCell className="text-sm whitespace-nowrap">{formatDateTime(order.createdAt)}</TableCell>
</TableRow>
);
})
)}
</TableBody>
</Table>
{renderPagination(ordersData, ordersPage, setOrdersPage)}
</CardContent>
</Card>
</TabsContent>
{/* 成交明细 Tab */}
<TabsContent value="trades" className="space-y-4">
{/* 筛选栏 */}
<Card>
<CardContent className="p-4">
<div className="flex gap-3">
<Select value={tradeSource} onValueChange={(v) => { setTradeSource(v); setTradesPage(1); }}>
<SelectTrigger className="w-[130px]">
<SelectValue placeholder="来源" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL"></SelectItem>
<SelectItem value="USER"></SelectItem>
<SelectItem value="MARKET_MAKER"></SelectItem>
<SelectItem value="DEX_BOT">DEX Bot</SelectItem>
<SelectItem value="SYSTEM"></SelectItem>
</SelectContent>
</Select>
</div>
</CardContent>
</Card>
{/* 成交表格 */}
<Card>
<CardContent className="p-0">
<Table>
<TableHeader>
<TableRow>
<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>
</TableRow>
</TableHeader>
<TableBody>
{tradesLoading ? renderSkeletonRows(10) : !tradesData?.data?.length ? (
<TableRow>
<TableCell colSpan={10} className="text-center py-8 text-muted-foreground">
</TableCell>
</TableRow>
) : (
tradesData.data.map((trade: AdminTrade) => (
<TableRow key={trade.id}>
<TableCell className="font-mono text-xs max-w-[120px] truncate" title={trade.tradeNo}>
{trade.tradeNo}
</TableCell>
<TableCell className="font-mono text-xs">{trade.buyerSequence}</TableCell>
<TableCell className="font-mono text-xs">{trade.sellerSequence}</TableCell>
<TableCell className="text-right font-mono text-sm">{formatDecimal(trade.price, 8)}</TableCell>
<TableCell className="text-right font-mono text-sm">{formatDecimal(trade.quantity, 2)}</TableCell>
<TableCell className="text-right font-mono text-sm">{formatDecimal(trade.originalQuantity, 2)}</TableCell>
<TableCell className="text-right font-mono text-sm text-orange-600">{formatDecimal(trade.burnQuantity, 2)}</TableCell>
<TableCell className="text-right font-mono text-sm">{formatDecimal(trade.amount, 2)}</TableCell>
<TableCell className="text-right font-mono text-sm text-orange-600">{formatDecimal(trade.fee, 2)}</TableCell>
<TableCell className="text-sm whitespace-nowrap">{formatDateTime(trade.createdAt)}</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
{renderPagination(tradesData, tradesPage, setTradesPage)}
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}

View File

@ -20,6 +20,7 @@ import {
FileSpreadsheet,
SendHorizontal,
HardDrive,
Repeat,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
@ -27,6 +28,7 @@ const menuItems = [
{ name: '仪表盘', href: '/dashboard', icon: LayoutDashboard },
{ name: '用户管理', href: '/users', icon: Users },
{ name: '交易管理', href: '/trading', icon: ArrowLeftRight },
{ name: '兑换记录', href: '/exchange-records', icon: Repeat },
{ name: 'P2P划转', href: '/p2p-transfers', icon: SendHorizontal },
{ name: '做市商管理', href: '/market-maker', icon: Bot },
{ name: 'C2C Bot', href: '/c2c-bot', icon: Zap },

View File

@ -76,6 +76,76 @@ export interface BurnRecordsResponse {
total: number;
}
// ==================== Admin 全局兑换记录类型 ====================
export interface AdminOrder {
id: string;
orderNo: string;
accountSequence: string;
type: 'BUY' | 'SELL';
status: 'PENDING' | 'PARTIAL' | 'FILLED' | 'CANCELLED';
source: 'USER' | 'MARKET_MAKER' | 'DEX_BOT' | 'SYSTEM';
sourceLabel: string | null;
price: string;
quantity: string;
filledQuantity: string;
remainingQuantity: string;
averagePrice: string;
totalAmount: string;
burnQuantity: string;
burnMultiplier: string;
effectiveQuantity: string;
createdAt: string;
completedAt: string | null;
cancelledAt: string | null;
}
export interface AdminTrade {
id: string;
tradeNo: string;
buyOrderId: string;
sellOrderId: string;
buyerSequence: string;
sellerSequence: string;
price: string;
quantity: string;
originalQuantity: string;
burnQuantity: string;
amount: string;
fee: string;
buyerSource: string;
sellerSource: string;
createdAt: string;
}
export interface AdminPaginatedResponse<T> {
data: T[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}
export interface AdminOrdersParams {
page?: number;
pageSize?: number;
type?: 'BUY' | 'SELL';
status?: 'PENDING' | 'PARTIAL' | 'FILLED' | 'CANCELLED';
source?: 'USER' | 'MARKET_MAKER' | 'DEX_BOT' | 'SYSTEM';
search?: string;
startDate?: string;
endDate?: string;
}
export interface AdminTradesParams {
page?: number;
pageSize?: number;
source?: 'USER' | 'MARKET_MAKER' | 'DEX_BOT' | 'SYSTEM';
search?: string;
startDate?: string;
endDate?: string;
}
export const tradingApi = {
// 获取交易系统状态
getTradingStatus: async (): Promise<TradingStatus> => {
@ -133,6 +203,18 @@ export const tradingApi = {
return response.data.data || response.data;
},
// 获取全局订单列表 (admin)
getAdminOrders: async (params: AdminOrdersParams): Promise<AdminPaginatedResponse<AdminOrder>> => {
const response = await tradingClient.get('/admin/orders', { params });
return response.data.data || response.data;
},
// 获取全局成交记录 (admin)
getAdminTrades: async (params: AdminTradesParams): Promise<AdminPaginatedResponse<AdminTrade>> => {
const response = await tradingClient.get('/admin/trades', { params });
return response.data.data || response.data;
},
// 获取市场概览
getMarketOverview: async (): Promise<{
price: string;

View File

@ -1,5 +1,6 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { tradingApi } from '../api/trading.api';
import type { AdminOrdersParams, AdminTradesParams } from '../api/trading.api';
import { useToast } from '@/lib/hooks/use-toast';
export function useTradingStatus() {
@ -97,6 +98,20 @@ export function useBurnRecords(
});
}
export function useAdminOrders(params: AdminOrdersParams) {
return useQuery({
queryKey: ['trading', 'admin-orders', params],
queryFn: () => tradingApi.getAdminOrders(params),
});
}
export function useAdminTrades(params: AdminTradesParams) {
return useQuery({
queryKey: ['trading', 'admin-trades', params],
queryFn: () => tradingApi.getAdminTrades(params),
});
}
export function useMarketOverview() {
return useQuery({
queryKey: ['trading', 'market-overview'],