Feat/dataset notion import (#392)

Co-authored-by: StyleZhang <jasonapring2015@outlook.com>
Co-authored-by: JzoNg <jzongcode@gmail.com>
This commit is contained in:
Jyong
2023-06-16 21:47:51 +08:00
committed by GitHub
parent f350948bde
commit 9253f72dea
96 changed files with 4479 additions and 367 deletions

View File

@@ -0,0 +1,102 @@
import { useTranslation } from 'react-i18next'
import Link from 'next/link'
import { PlusIcon } from '@heroicons/react/24/solid'
import cn from 'classnames'
import Indicator from '../../../indicator'
import Operate from './operate'
import s from './style.module.css'
import NotionIcon from '@/app/components/base/notion-icon'
import { apiPrefix } from '@/config'
import type { DataSourceNotion as TDataSourceNotion } from '@/models/common'
type DataSourceNotionProps = {
workspaces: TDataSourceNotion[]
}
const DataSourceNotion = ({
workspaces,
}: DataSourceNotionProps) => {
const { t } = useTranslation()
const connected = !!workspaces.length
return (
<div className='mb-2 border-[0.5px] border-gray-200 bg-gray-50 rounded-xl'>
<div className='flex items-center px-3 py-[9px]'>
<div className={cn(s['notion-icon'], 'w-8 h-8 mr-3 border border-gray-100 rounded-lg')} />
<div className='grow'>
<div className='leading-5 text-sm font-medium text-gray-800'>
{t('common.dataSource.notion.title')}
</div>
{
!connected && (
<div className='leading-5 text-xs text-gray-500'>
{t('common.dataSource.notion.description')}
</div>
)
}
</div>
{
!connected
? (
<Link
className='flex items-center ml-3 px-3 h-7 bg-white border border-gray-200 rounded-md text-xs font-medium text-gray-700 cursor-pointer'
href={`${apiPrefix}/oauth/data-source/notion`}>
{t('common.dataSource.connect')}
</Link>
)
: (
<Link
href={`${apiPrefix}/oauth/data-source/notion`}
className='flex items-center px-3 h-7 bg-white border-[0.5px] border-gray-200 text-xs font-medium text-primary-600 rounded-md cursor-pointer'>
<PlusIcon className='w-[14px] h-[14px] mr-[5px]' />
{t('common.dataSource.notion.addWorkspace')}
</Link>
)
}
</div>
{
connected && (
<div className='flex items-center px-3 h-[18px]'>
<div className='text-xs font-medium text-gray-500'>
{t('common.dataSource.notion.connectedWorkspace')}
</div>
<div className='grow ml-3 border-t border-t-gray-100' />
</div>
)
}
{
connected && (
<div className='px-3 pt-2 pb-3'>
{
workspaces.map(workspace => (
<div className={cn(s['workspace-item'], 'flex items-center mb-1 py-1 pr-1 bg-white rounded-lg')} key={workspace.id}>
<NotionIcon
className='ml-3 mr-[6px]'
src={workspace.source_info.workspace_icon}
name={workspace.source_info.workspace_name}
/>
<div className='grow py-[7px] leading-[18px] text-[13px] font-medium text-gray-700 truncate' title={workspace.source_info.workspace_name}>{workspace.source_info.workspace_name}</div>
{
workspace.is_bound
? <Indicator className='shrink-0 mr-[6px]' />
: <Indicator className='shrink-0 mr-[6px]' color='yellow' />
}
<div className='shrink-0 mr-3 text-xs font-medium'>
{
workspace.is_bound
? t('common.dataSource.notion.connected')
: t('common.dataSource.notion.disconnected')
}
</div>
<div className='mr-2 w-[1px] h-3 bg-gray-100' />
<Operate workspace={workspace} />
</div>
))
}
</div>
)
}
</div>
)
}
export default DataSourceNotion

View File

@@ -0,0 +1,14 @@
.file-icon {
background: url(../../../../assets/file.svg) center center no-repeat;
background-size: contain;
}
.sync-icon {
background: url(../../../../assets/sync.svg) center center no-repeat;
background-size: contain;
}
.trash-icon {
background: url(../../../../assets/trash.svg) center center no-repeat;
background-size: contain;
}

View File

@@ -0,0 +1,107 @@
'use client'
import { useTranslation } from 'react-i18next'
import { Fragment } from 'react'
import Link from 'next/link'
import { useSWRConfig } from 'swr'
import { EllipsisHorizontalIcon } from '@heroicons/react/24/solid'
import { Menu, Transition } from '@headlessui/react'
import cn from 'classnames'
import s from './index.module.css'
import { apiPrefix } from '@/config'
import { syncDataSourceNotion, updateDataSourceNotionAction } from '@/service/common'
import Toast from '@/app/components/base/toast'
import type { DataSourceNotion } from '@/models/common'
type OperateProps = {
workspace: DataSourceNotion
}
export default function Operate({
workspace,
}: OperateProps) {
const itemClassName = `
flex px-3 py-2 hover:bg-gray-50 text-sm text-gray-700
cursor-pointer
`
const itemIconClassName = `
mr-2 mt-[2px] w-4 h-4
`
const { t } = useTranslation()
const { mutate } = useSWRConfig()
const updateIntegrates = () => {
Toast.notify({
type: 'success',
message: t('common.api.success'),
})
mutate({ url: 'data-source/integrates' })
}
const handleSync = async () => {
await syncDataSourceNotion({ url: `/oauth/data-source/notion/${workspace.id}/sync` })
updateIntegrates()
}
const handleRemove = async () => {
await updateDataSourceNotionAction({ url: `/data-source/integrates/${workspace.id}/disable` })
updateIntegrates()
}
return (
<Menu as="div" className="relative inline-block text-left">
{
({ open }) => (
<>
<Menu.Button className={`flex items-center justify-center w-8 h-8 rounded-lg hover:bg-gray-100 ${open && 'bg-gray-100'}`}>
<EllipsisHorizontalIcon className='w-4 h-4' />
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
className="
absolute right-0 top-9 w-60 max-w-80
divide-y divide-gray-100 origin-top-right rounded-lg bg-white
shadow-[0_10px_15px_-3px_rgba(0,0,0,0.1),0_4px_6px_rgba(0,0,0,0.05)]
"
>
<div className="px-1 py-1">
<Menu.Item>
<Link
className={itemClassName}
href={`${apiPrefix}/oauth/data-source/notion`}>
<div className={cn(s['file-icon'], itemIconClassName)}></div>
<div>
<div className='leading-5'>{t('common.dataSource.notion.changeAuthorizedPages')}</div>
<div className='leading-5 text-xs text-gray-500'>
{workspace.source_info.total} {t('common.dataSource.notion.pagesAuthorized')}
</div>
</div>
</Link>
</Menu.Item>
<Menu.Item>
<div className={itemClassName} onClick={handleSync}>
<div className={cn(s['sync-icon'], itemIconClassName)} />
<div className='leading-5'>{t('common.dataSource.notion.sync')}</div>
</div>
</Menu.Item>
</div>
<Menu.Item>
<div className='p-1'>
<div className={itemClassName} onClick={handleRemove}>
<div className={cn(s['trash-icon'], itemIconClassName)} />
<div className='leading-5'>{t('common.dataSource.notion.remove')}</div>
</div>
</div>
</Menu.Item>
</Menu.Items>
</Transition>
</>
)
}
</Menu>
)
}

View File

@@ -0,0 +1,12 @@
.notion-icon {
background: #ffffff url(../../../assets/notion.svg) center center no-repeat;
background-size: 20px 20px;
}
.workspace-item {
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
}
.workspace-item:last-of-type {
margin-bottom: 0;
}

View File

@@ -0,0 +1,17 @@
import useSWR from 'swr'
import { useTranslation } from 'react-i18next'
import DataSourceNotion from './data-source-notion'
import { fetchDataSource } from '@/service/common'
export default function DataSourcePage() {
const { t } = useTranslation()
const { data } = useSWR({ url: 'data-source/integrates' }, fetchDataSource)
const notionWorkspaces = data?.data.filter(item => item.provider === 'notion') || []
return (
<div className='mb-8'>
<div className='mb-2 text-sm font-medium text-gray-900'>{t('common.dataSource.add')}</div>
<DataSourceNotion workspaces={notionWorkspaces} />
</div>
)
}