download-logs-modal.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. 'use client'
  2. import { useEffect, useState } from 'react'
  3. import { useTranslation } from 'react-i18next'
  4. import Modal from '@/app/components/base/modal'
  5. import Button from '@/app/components/base/button'
  6. import Loading from '@/app/components/base/loading'
  7. import { fetchDownloadLogs } from '@/service/datasets'
  8. import { useToastContext } from '@/app/components/base/toast'
  9. type DownloadLog = {
  10. user_id: string
  11. user_name: string
  12. download_time: string
  13. ip: string
  14. }
  15. type DownloadLogsModalProps = {
  16. datasetId: string
  17. documentId: string
  18. documentName: string
  19. onClose: () => void
  20. }
  21. const DownloadLogsModal = ({
  22. datasetId,
  23. documentId,
  24. documentName,
  25. onClose,
  26. }: DownloadLogsModalProps) => {
  27. const { t } = useTranslation()
  28. const { notify } = useToastContext()
  29. const [loading, setLoading] = useState(true)
  30. const [logs, setLogs] = useState<DownloadLog[]>([])
  31. const [total, setTotal] = useState(0)
  32. const [uniqueUsers, setUniqueUsers] = useState(0)
  33. useEffect(() => {
  34. loadLogs()
  35. }, [])
  36. const loadLogs = async () => {
  37. try {
  38. const data = await fetchDownloadLogs(datasetId, documentId)
  39. setLogs(data.logs)
  40. setTotal(data.total)
  41. setUniqueUsers(data.unique_users)
  42. }
  43. catch (error) {
  44. notify({ type: 'error', message: t('common.actionMsg.loadFailed') })
  45. }
  46. finally {
  47. setLoading(false)
  48. }
  49. }
  50. return (
  51. <Modal
  52. isShow
  53. onClose={onClose}
  54. title={
  55. <div>
  56. <div className="text-lg font-semibold text-gray-900">
  57. {t('dataset.downloadLogs.title')}
  58. </div>
  59. <div className="text-sm font-normal text-gray-500 mt-1 truncate max-w-[600px]">
  60. {documentName}
  61. </div>
  62. </div>
  63. }
  64. className="w-[900px]"
  65. >
  66. {loading
  67. ? (
  68. <div className="flex items-center justify-center py-12">
  69. <Loading />
  70. </div>
  71. )
  72. : (
  73. <>
  74. {/* Statistics cards */}
  75. <div className="grid grid-cols-2 gap-4 mb-4">
  76. <div className="bg-gradient-to-br from-blue-50 to-blue-100 rounded-lg p-4">
  77. <div className="text-sm text-blue-600 font-medium">
  78. {t('dataset.downloadLogs.totalDownloads')}
  79. </div>
  80. <div className="text-2xl font-bold text-blue-900 mt-1">
  81. {total}
  82. </div>
  83. </div>
  84. <div className="bg-gradient-to-br from-green-50 to-green-100 rounded-lg p-4">
  85. <div className="text-sm text-green-600 font-medium">
  86. {t('dataset.downloadLogs.uniqueUsers')}
  87. </div>
  88. <div className="text-2xl font-bold text-green-900 mt-1">
  89. {uniqueUsers}
  90. </div>
  91. </div>
  92. </div>
  93. {/* Download logs table */}
  94. <div className="border border-gray-200 rounded-lg">
  95. <div className="bg-gray-50 px-4 py-3 border-b border-gray-200">
  96. <div className="grid grid-cols-12 gap-4 text-xs font-medium text-gray-700">
  97. <div className="col-span-3">{t('dataset.downloadLogs.user')}</div>
  98. <div className="col-span-5">{t('dataset.downloadLogs.time')}</div>
  99. <div className="col-span-4">{t('dataset.downloadLogs.ip')}</div>
  100. </div>
  101. </div>
  102. <div className="max-h-[500px] overflow-y-auto">
  103. {logs.length === 0
  104. ? (
  105. <div className="py-12 text-center text-sm text-gray-500">
  106. {t('dataset.downloadLogs.noData')}
  107. </div>
  108. )
  109. : (
  110. <div className="divide-y divide-gray-100">
  111. {logs.map((log, index) => (
  112. <div
  113. key={index}
  114. className="grid grid-cols-12 gap-4 px-4 py-3 hover:bg-gray-50 transition-colors"
  115. >
  116. <div className="col-span-3">
  117. <div className="text-sm font-medium text-gray-900 truncate">
  118. {log.user_name}
  119. </div>
  120. </div>
  121. <div className="col-span-5">
  122. <div className="text-sm text-gray-700">
  123. {log.download_time}
  124. </div>
  125. </div>
  126. <div className="col-span-4">
  127. <div className="text-sm text-gray-500 font-mono">
  128. {log.ip}
  129. </div>
  130. </div>
  131. </div>
  132. ))}
  133. </div>
  134. )}
  135. </div>
  136. </div>
  137. {/* Footer */}
  138. <div className="flex justify-end pt-4 border-t border-gray-200 mt-4">
  139. <Button onClick={onClose}>
  140. {t('common.operation.close')}
  141. </Button>
  142. </div>
  143. </>
  144. )}
  145. </Modal>
  146. )
  147. }
  148. export default DownloadLogsModal