modal.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useCallback, useState } from 'react'
  4. import Modal from '../../../base/modal'
  5. import type { BuiltInMetadataItem, MetadataItemInBatchEdit } from '../types'
  6. import { type MetadataItemWithEdit, UpdateType } from '../types'
  7. import EditMetadataBatchItem from './edit-row'
  8. import AddedMetadataItem from './add-row'
  9. import Button from '../../../base/button'
  10. import { useTranslation } from 'react-i18next'
  11. import Checkbox from '../../../base/checkbox'
  12. import Tooltip from '../../../base/tooltip'
  13. import SelectMetadataModal from '../metadata-dataset/select-metadata-modal'
  14. import { RiQuestionLine } from '@remixicon/react'
  15. import Divider from '@/app/components/base/divider'
  16. import AddMetadataButton from '../add-metadata-button'
  17. import produce from 'immer'
  18. import useCheckMetadataName from '../hooks/use-check-metadata-name'
  19. import Toast from '@/app/components/base/toast'
  20. import { useCreateMetaData } from '@/service/knowledge/use-metadata'
  21. const i18nPrefix = 'dataset.metadata.batchEditMetadata'
  22. type Props = {
  23. datasetId: string,
  24. documentNum: number
  25. list: MetadataItemInBatchEdit[]
  26. onSave: (editedList: MetadataItemInBatchEdit[], addedList: MetadataItemInBatchEdit[], isApplyToAllSelectDocument: boolean) => void
  27. onHide: () => void
  28. onShowManage: () => void
  29. }
  30. const EditMetadataBatchModal: FC<Props> = ({
  31. datasetId,
  32. documentNum,
  33. list,
  34. onSave,
  35. onHide,
  36. onShowManage,
  37. }) => {
  38. const { t } = useTranslation()
  39. const [templeList, setTempleList] = useState<MetadataItemWithEdit[]>(list)
  40. const handleTemplesChange = useCallback((payload: MetadataItemWithEdit) => {
  41. const newTempleList = produce(templeList, (draft) => {
  42. const index = draft.findIndex(i => i.id === payload.id)
  43. if (index !== -1) {
  44. draft[index] = payload
  45. draft[index].isUpdated = true
  46. draft[index].updateType = UpdateType.changeValue
  47. }
  48. },
  49. )
  50. setTempleList(newTempleList)
  51. }, [templeList])
  52. const handleTempleItemRemove = useCallback((id: string) => {
  53. const newTempleList = produce(templeList, (draft) => {
  54. const index = draft.findIndex(i => i.id === id)
  55. if (index !== -1) {
  56. draft[index].isUpdated = true
  57. draft[index].updateType = UpdateType.delete
  58. }
  59. })
  60. setTempleList(newTempleList)
  61. }, [templeList])
  62. const handleItemReset = useCallback((id: string) => {
  63. const newTempleList = produce(templeList, (draft) => {
  64. const index = draft.findIndex(i => i.id === id)
  65. if (index !== -1) {
  66. draft[index] = { ...list[index] }
  67. draft[index].isUpdated = false
  68. delete draft[index].updateType
  69. }
  70. })
  71. setTempleList(newTempleList)
  72. }, [list, templeList])
  73. const { checkName } = useCheckMetadataName()
  74. const { mutate: doAddMetaData } = useCreateMetaData(datasetId)
  75. const handleAddMetaData = useCallback(async (payload: BuiltInMetadataItem) => {
  76. const errorMsg = checkName(payload.name).errorMsg
  77. if (errorMsg) {
  78. Toast.notify({
  79. message: errorMsg,
  80. type: 'error',
  81. })
  82. return Promise.reject(new Error(errorMsg))
  83. }
  84. await doAddMetaData(payload)
  85. Toast.notify({
  86. type: 'success',
  87. message: t('common.api.actionSuccess'),
  88. })
  89. }, [checkName, doAddMetaData, t])
  90. const [addedList, setAddedList] = useState<MetadataItemWithEdit[]>([])
  91. const handleAddedListChange = useCallback((payload: MetadataItemWithEdit) => {
  92. const newAddedList = addedList.map(i => i.id === payload.id ? payload : i)
  93. setAddedList(newAddedList)
  94. }, [addedList])
  95. const handleAddedItemRemove = useCallback((removeIndex: number) => {
  96. return () => {
  97. const newAddedList = addedList.filter((i, index) => index !== removeIndex)
  98. setAddedList(newAddedList)
  99. }
  100. }, [addedList])
  101. const [isApplyToAllSelectDocument, setIsApplyToAllSelectDocument] = useState(false)
  102. const handleSave = useCallback(() => {
  103. onSave(templeList.filter(item => item.updateType !== UpdateType.delete), addedList, isApplyToAllSelectDocument)
  104. }, [templeList, addedList, isApplyToAllSelectDocument, onSave])
  105. return (
  106. <Modal
  107. title={t(`${i18nPrefix}.editMetadata`)}
  108. isShow
  109. closable
  110. onClose={onHide}
  111. className='!max-w-[640px]'
  112. >
  113. <div className='mt-1 system-xs-medium text-text-accent'>{t(`${i18nPrefix}.editDocumentsNum`, { num: documentNum })}</div>
  114. <div className='ml-[-16px] max-h-[305px] overflow-y-auto'>
  115. <div className='mt-4 space-y-2'>
  116. {templeList.map(item => (
  117. <EditMetadataBatchItem
  118. key={item.id}
  119. payload={item}
  120. onChange={handleTemplesChange}
  121. onRemove={handleTempleItemRemove}
  122. onReset={handleItemReset}
  123. />
  124. ))}
  125. </div>
  126. <div className='mt-4 pl-[18px]'>
  127. <div className='flex items-center'>
  128. <div className='mr-2 shrink-0 system-xs-medium-uppercase text-text-tertiary'>{t('dataset.metadata.createMetadata.title')}</div>
  129. <Divider bgStyle='gradient' />
  130. </div>
  131. <div className='mt-2 space-y-2'>
  132. {addedList.map((item, i) => (
  133. <AddedMetadataItem
  134. key={i}
  135. payload={item}
  136. onChange={handleAddedListChange}
  137. onRemove={handleAddedItemRemove(i)}
  138. />
  139. ))}
  140. </div>
  141. <div className='mt-3'>
  142. <SelectMetadataModal
  143. datasetId={datasetId}
  144. popupPlacement='top-start'
  145. popupOffset={{ mainAxis: 4, crossAxis: 0 }}
  146. trigger={
  147. <AddMetadataButton />
  148. }
  149. onSave={handleAddMetaData}
  150. onSelect={data => setAddedList([...addedList, data as MetadataItemWithEdit])}
  151. onManage={onShowManage}
  152. />
  153. </div>
  154. </div>
  155. </div>
  156. <div className='mt-4 flex items-center justify-between'>
  157. <div className='flex items-center select-none'>
  158. <Checkbox checked={isApplyToAllSelectDocument} onCheck={() => setIsApplyToAllSelectDocument(!isApplyToAllSelectDocument)} />
  159. <div className='ml-2 mr-1 system-xs-medium text-text-secondary'>{t(`${i18nPrefix}.applyToAllSelectDocument`)}</div>
  160. <Tooltip popupContent={
  161. <div className='max-w-[240px]'>{t(`${i18nPrefix}.applyToAllSelectDocumentTip`)}</div>
  162. } >
  163. <div className='p-px cursor-pointer'>
  164. <RiQuestionLine className='size-3.5 text-text-tertiary' />
  165. </div>
  166. </Tooltip>
  167. </div>
  168. <div className='flex items-center space-x-2'>
  169. <Button
  170. onClick={onHide}>{t('common.operation.cancel')}</Button>
  171. <Button
  172. onClick={handleSave}
  173. variant='primary'
  174. >{t('common.operation.save')}</Button>
  175. </div>
  176. </div>
  177. </Modal>
  178. )
  179. }
  180. export default React.memo(EditMetadataBatchModal)