page.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. 'use client'
  2. import useSWR from 'swr'
  3. import { Card, CardContent } from '@/components/ui/card'
  4. import { Badge } from '@/components/ui/badge'
  5. import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
  6. const fetcher = (url: string) => fetch(url).then((r) => r.json())
  7. export default function HistoryPage() {
  8. const { data } = useSWR('/api/history?limit=100', fetcher, { refreshInterval: 5000 })
  9. const rows = data || []
  10. const statusBadge = (status: string) => {
  11. switch (status) {
  12. case 'success':
  13. return <Badge variant="success">{status}</Badge>
  14. case 'failed':
  15. return <Badge variant="destructive">{status}</Badge>
  16. case 'executing':
  17. return <Badge variant="warning">{status}</Badge>
  18. case 'skipped':
  19. default:
  20. return <Badge variant="secondary">{status}</Badge>
  21. }
  22. }
  23. return (
  24. <div className="space-y-6">
  25. <h2 className="text-lg font-semibold">Copy Trade History</h2>
  26. <Card>
  27. <CardContent className="p-4">
  28. {rows.length === 0 ? (
  29. <p className="text-xs text-muted-foreground">No history yet</p>
  30. ) : (
  31. <div className="overflow-x-auto">
  32. <Table className="text-xs">
  33. <TableHeader>
  34. <TableRow>
  35. <TableHead>Time</TableHead>
  36. <TableHead>Operation</TableHead>
  37. <TableHead>Target</TableHead>
  38. <TableHead>Target TX</TableHead>
  39. <TableHead>Our TX</TableHead>
  40. <TableHead>Amount A</TableHead>
  41. <TableHead>Amount B</TableHead>
  42. <TableHead>Status</TableHead>
  43. <TableHead>Error</TableHead>
  44. </TableRow>
  45. </TableHeader>
  46. <TableBody>
  47. {rows.map(
  48. (row: {
  49. id: number
  50. created_at: string
  51. operation: string
  52. target_address: string
  53. target_tx_sig: string
  54. our_tx_sig: string | null
  55. our_amount_a: string | null
  56. our_amount_b: string | null
  57. status: string
  58. error_message: string | null
  59. }) => (
  60. <TableRow key={row.id}>
  61. <TableCell className="text-muted-foreground whitespace-nowrap">
  62. {new Date(row.created_at + 'Z').toLocaleString()}
  63. </TableCell>
  64. <TableCell className="whitespace-nowrap">{row.operation}</TableCell>
  65. <TableCell className="text-muted-foreground">
  66. {row.target_address.slice(0, 4)}...{row.target_address.slice(-4)}
  67. </TableCell>
  68. <TableCell>
  69. <a
  70. href={`https://solscan.io/tx/${row.target_tx_sig}`}
  71. target="_blank"
  72. rel="noopener noreferrer"
  73. className="text-primary hover:underline"
  74. >
  75. {row.target_tx_sig.slice(0, 8)}...
  76. </a>
  77. </TableCell>
  78. <TableCell>
  79. {row.our_tx_sig ? (
  80. <a
  81. href={`https://solscan.io/tx/${row.our_tx_sig}`}
  82. target="_blank"
  83. rel="noopener noreferrer"
  84. className="text-primary hover:underline"
  85. >
  86. {row.our_tx_sig.slice(0, 8)}...
  87. </a>
  88. ) : (
  89. <span className="text-muted-foreground">-</span>
  90. )}
  91. </TableCell>
  92. <TableCell className="text-muted-foreground">{row.our_amount_a || '-'}</TableCell>
  93. <TableCell className="text-muted-foreground">{row.our_amount_b || '-'}</TableCell>
  94. <TableCell>{statusBadge(row.status)}</TableCell>
  95. <TableCell className="text-destructive text-[10px] max-w-[200px] truncate">
  96. {row.error_message || ''}
  97. </TableCell>
  98. </TableRow>
  99. ),
  100. )}
  101. </TableBody>
  102. </Table>
  103. </div>
  104. )}
  105. </CardContent>
  106. </Card>
  107. </div>
  108. )
  109. }