permission-modal.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  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 Checkbox from '@/app/components/base/checkbox'
  7. import Loading from '@/app/components/base/loading'
  8. import { fetchDownloadPermission, updateDownloadPermission } from '@/service/datasets'
  9. import { useToastContext } from '@/app/components/base/toast'
  10. type User = {
  11. id: string
  12. name: string
  13. email: string
  14. has_permission: boolean
  15. }
  16. type PermissionModalProps = {
  17. datasetId: string
  18. documentId: string
  19. documentName: string
  20. onClose: () => void
  21. }
  22. const PermissionModal = ({
  23. datasetId,
  24. documentId,
  25. documentName,
  26. onClose,
  27. }: PermissionModalProps) => {
  28. const { t } = useTranslation()
  29. const { notify } = useToastContext()
  30. const [loading, setLoading] = useState(true)
  31. const [saving, setSaving] = useState(false)
  32. const [isRestricted, setIsRestricted] = useState(false)
  33. const [users, setUsers] = useState<User[]>([])
  34. // Load permission configuration
  35. useEffect(() => {
  36. loadPermission()
  37. }, [])
  38. const loadPermission = async () => {
  39. try {
  40. const data = await fetchDownloadPermission(datasetId, documentId)
  41. setIsRestricted(data.is_restricted)
  42. setUsers(data.users)
  43. }
  44. catch (error) {
  45. notify({ type: 'error', message: t('common.actionMsg.loadFailed') })
  46. }
  47. finally {
  48. setLoading(false)
  49. }
  50. }
  51. const handleSave = async () => {
  52. setSaving(true)
  53. try {
  54. // If not restricted, pass empty array; otherwise pass selected user IDs
  55. const userIds = isRestricted
  56. ? users.filter(u => u.has_permission).map(u => u.id)
  57. : []
  58. await updateDownloadPermission(datasetId, documentId, {
  59. is_restricted: isRestricted,
  60. user_ids: userIds,
  61. })
  62. notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
  63. onClose()
  64. }
  65. catch (error) {
  66. notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
  67. }
  68. finally {
  69. setSaving(false)
  70. }
  71. }
  72. const toggleUser = (userId: string) => {
  73. setUsers(users.map(u =>
  74. u.id === userId ? { ...u, has_permission: !u.has_permission } : u,
  75. ))
  76. }
  77. const selectAll = () => {
  78. setUsers(users.map(u => ({ ...u, has_permission: true })))
  79. }
  80. const deselectAll = () => {
  81. setUsers(users.map(u => ({ ...u, has_permission: false })))
  82. }
  83. const selectedCount = users.filter(u => u.has_permission).length
  84. return (
  85. <Modal
  86. isShow
  87. onClose={onClose}
  88. title={
  89. <div>
  90. <div className="text-lg font-semibold text-gray-900">
  91. {t('dataset.downloadPermission.title')}
  92. </div>
  93. <div className="text-sm font-normal text-gray-500 mt-1 truncate max-w-[500px]">
  94. {documentName}
  95. </div>
  96. </div>
  97. }
  98. className="w-[600px]"
  99. >
  100. {loading
  101. ? (
  102. <div className="flex items-center justify-center py-12">
  103. <Loading />
  104. </div>
  105. )
  106. : (
  107. <>
  108. <div className="py-4 space-y-6">
  109. {/* Enable restriction toggle */}
  110. <div className="flex items-start justify-between">
  111. <div className="flex-1">
  112. <div className="text-sm font-medium text-gray-900">
  113. {t('dataset.downloadPermission.enableRestriction')}
  114. </div>
  115. <div className="text-xs text-gray-500 mt-1">
  116. {t('dataset.downloadPermission.enableRestrictionDesc')}
  117. </div>
  118. </div>
  119. <div className="ml-4 flex-shrink-0">
  120. <Checkbox
  121. checked={isRestricted}
  122. onCheck={() => setIsRestricted(!isRestricted)}
  123. />
  124. </div>
  125. </div>
  126. {/* User list */}
  127. {isRestricted && (
  128. <div>
  129. <div className="flex items-center justify-between mb-3">
  130. <div className="text-sm font-medium text-gray-900">
  131. {t('dataset.downloadPermission.selectUsers')}
  132. </div>
  133. <div className="flex gap-2 text-xs">
  134. <button
  135. onClick={selectAll}
  136. className="text-primary-600 hover:text-primary-700"
  137. >
  138. {t('common.operation.selectAll')}
  139. </button>
  140. <span className="text-gray-300">|</span>
  141. <button
  142. onClick={deselectAll}
  143. className="text-gray-600 hover:text-gray-700"
  144. >
  145. {t('common.operation.deselectAll')}
  146. </button>
  147. </div>
  148. </div>
  149. <div className="border border-gray-200 rounded-lg max-h-[400px] overflow-y-auto">
  150. {users.length === 0
  151. ? (
  152. <div className="py-8 text-center text-sm text-gray-500">
  153. {t('dataset.downloadPermission.noUsers')}
  154. </div>
  155. )
  156. : (
  157. <div className="divide-y divide-gray-100">
  158. {users.map(user => (
  159. <label
  160. key={user.id}
  161. className="flex items-center gap-3 px-4 py-3 hover:bg-gray-50 cursor-pointer transition-colors"
  162. >
  163. <Checkbox
  164. checked={user.has_permission}
  165. onCheck={() => toggleUser(user.id)}
  166. />
  167. <div className="flex-1 min-w-0">
  168. <div className="text-sm font-medium text-gray-900 truncate">
  169. {user.name}
  170. </div>
  171. <div className="text-xs text-gray-500 truncate">
  172. {user.email}
  173. </div>
  174. </div>
  175. </label>
  176. ))}
  177. </div>
  178. )}
  179. </div>
  180. {users.length > 0 && (
  181. <div className="mt-2 text-xs text-gray-500">
  182. {t('dataset.downloadPermission.selectedCount', {
  183. count: selectedCount,
  184. total: users.length,
  185. })}
  186. </div>
  187. )}
  188. </div>
  189. )}
  190. </div>
  191. {/* Footer buttons */}
  192. <div className="flex justify-end gap-2 pt-4 border-t border-gray-200">
  193. <Button onClick={onClose}>
  194. {t('common.operation.cancel')}
  195. </Button>
  196. <Button
  197. variant="primary"
  198. onClick={handleSave}
  199. disabled={saving}
  200. loading={saving}
  201. >
  202. {t('common.operation.save')}
  203. </Button>
  204. </div>
  205. </>
  206. )}
  207. </Modal>
  208. )
  209. }
  210. export default PermissionModal