import type { FC } from 'react'
|
import { useState } from 'react'
|
import {
|
RiDeleteBinLine,
|
RiEditLine,
|
} from '@remixicon/react'
|
import { useDebounceFn } from 'ahooks'
|
import { useContext } from 'use-context-selector'
|
import { useTranslation } from 'react-i18next'
|
import { useStore as useTagStore } from './store'
|
import Confirm from '@/app/components/base/confirm'
|
import cn from '@/utils/classnames'
|
import type { Tag } from '@/app/components/base/tag-management/constant'
|
import { ToastContext } from '@/app/components/base/toast'
|
import {
|
deleteTag,
|
updateTag,
|
} from '@/service/tag'
|
|
type TagItemEditorProps = {
|
tag: Tag
|
}
|
const TagItemEditor: FC<TagItemEditorProps> = ({
|
tag,
|
}) => {
|
const { t } = useTranslation()
|
const { notify } = useContext(ToastContext)
|
const tagList = useTagStore(s => s.tagList)
|
const setTagList = useTagStore(s => s.setTagList)
|
|
const [isEditing, setIsEditing] = useState(false)
|
const [name, setName] = useState(tag.name)
|
const editTag = async (tagID: string, name: string) => {
|
if (name === tag.name) {
|
setIsEditing(false)
|
return
|
}
|
if (!name) {
|
notify({ type: 'error', message: 'tag name is empty' })
|
setName(tag.name)
|
setIsEditing(false)
|
return
|
}
|
try {
|
const newList = tagList.map((tag) => {
|
if (tag.id === tagID) {
|
return {
|
...tag,
|
name,
|
}
|
}
|
return tag
|
})
|
setTagList([
|
...newList,
|
])
|
setIsEditing(false)
|
await updateTag(tagID, name)
|
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
setName(name)
|
}
|
catch (e: any) {
|
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
setName(tag.name)
|
const recoverList = tagList.map((tag) => {
|
if (tag.id === tagID) {
|
return {
|
...tag,
|
name: tag.name,
|
}
|
}
|
return tag
|
})
|
setTagList([
|
...recoverList,
|
])
|
setIsEditing(false)
|
}
|
}
|
const [showRemoveModal, setShowRemoveModal] = useState(false)
|
const [pending, setPending] = useState<Boolean>(false)
|
const removeTag = async (tagID: string) => {
|
if (pending)
|
return
|
try {
|
setPending(true)
|
await deleteTag(tagID)
|
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
const newList = tagList.filter(tag => tag.id !== tagID)
|
setTagList([
|
...newList,
|
])
|
setPending(false)
|
}
|
catch (e: any) {
|
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
|
setPending(false)
|
}
|
}
|
const { run: handleRemove } = useDebounceFn(() => {
|
removeTag(tag.id)
|
}, { wait: 200 })
|
|
return (
|
<>
|
<div className={cn('shrink-0 flex items-center gap-0.5 pr-1 pl-2 py-1 rounded-lg border border-gray-200 text-sm leading-5 text-gray-700')}>
|
{!isEditing && (
|
<>
|
<div className='text-sm leading-5 text-gray-700'>
|
{tag.name}
|
</div>
|
<div className='shrink-0 px-1 text-sm leading-4.5 text-gray-500 font-medium'>{tag.binding_count}</div>
|
<div className='group/edit shrink-0 p-1 rounded-md cursor-pointer hover:bg-black/5' onClick={() => setIsEditing(true)}>
|
<RiEditLine className='w-3 h-3 text-gray-500 group-hover/edit:text-gray-800' />
|
</div>
|
<div className='group/remove shrink-0 p-1 rounded-md cursor-pointer hover:bg-black/5' onClick={() => {
|
if (tag.binding_count)
|
setShowRemoveModal(true)
|
else
|
handleRemove()
|
}}>
|
<RiDeleteBinLine className='w-3 h-3 text-gray-500 group-hover/remove:text-gray-800' />
|
</div>
|
</>
|
)}
|
{isEditing && (
|
<input
|
className='shrink-0 outline-none appearance-none placeholder:text-gray-300 caret-primary-600'
|
autoFocus
|
value={name}
|
onChange={e => setName(e.target.value)}
|
onKeyDown={e => e.key === 'Enter' && editTag(tag.id, name)}
|
onBlur={() => editTag(tag.id, name)}
|
/>
|
)}
|
</div>
|
<Confirm
|
title={`${t('common.tag.delete')} "${tag.name}"`}
|
isShow={showRemoveModal}
|
content={t('common.tag.deleteTip')}
|
onConfirm={() => {
|
handleRemove()
|
setShowRemoveModal(false)
|
}}
|
onCancel={() => setShowRemoveModal(false)}
|
/>
|
</>
|
)
|
}
|
|
export default TagItemEditor
|