diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/email-configure-modal.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/email-configure-modal.tsx index 06d7354520..35e85755c2 100644 --- a/web/app/components/workflow/nodes/human-input/components/delivery-method/email-configure-modal.tsx +++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/email-configure-modal.tsx @@ -4,8 +4,9 @@ import { RiCloseLine } from '@remixicon/react' import Modal from '@/app/components/base/modal' import Input from '@/app/components/base/input' import Button from '@/app/components/base/button' +import Recipient from './recipient' import MailBodyInput from './mail-body-input' -import type { EmailConfig, Recipient } from '../../types' +import type { EmailConfig } from '../../types' import type { Node, NodeOutPutVar, @@ -33,7 +34,7 @@ const EmailConfigureModal = ({ }: EmailConfigureModalProps) => { const { t } = useTranslation() - const [recipients, setRecipients] = useState(config?.recipients || []) + const [recipients, setRecipients] = useState(config?.recipients || { whole_workspace: false, items: [] }) const [subject, setSubject] = useState(config?.subject || '') const [body, setBody] = useState(config?.body || '') @@ -43,7 +44,7 @@ const EmailConfigureModal = ({ subject, body, }) - }, [recipients, subject, body, onConfirm]) + }, [subject, body, onConfirm]) return ( {t(`${i18nPrefix}.deliveryMethod.emailConfigure.recipient`)} +
@@ -91,13 +96,13 @@ const EmailConfigureModal = ({ diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/index.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/index.tsx new file mode 100644 index 0000000000..e54b8eaf34 --- /dev/null +++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/index.tsx @@ -0,0 +1,78 @@ +import { memo } from 'react' +import useSWR from 'swr' +import { useTranslation } from 'react-i18next' +import { RiGroupLine } from '@remixicon/react' +import { useAppContext } from '@/context/app-context' +import Switch from '@/app/components/base/switch' +import MemberSelector from './member-selector' +import { fetchMembers } from '@/service/common' +import type { RecipientData } from '../../../types' +import cn from '@/utils/classnames' +import { produce } from 'immer' + +const i18nPrefix = 'workflow.nodes.humanInput' + +type Props = { + data: RecipientData + onChange: (data: RecipientData) => void +} + +const Recipient = ({ + data, + onChange, +}: Props) => { + const { t } = useTranslation() + const { userProfile, currentWorkspace } = useAppContext() + const { data: members } = useSWR( + { + url: '/workspaces/current/members', + params: {}, + }, + fetchMembers, + ) + const accounts = members?.accounts || [] + + const handleMemberSelect = (id: string) => { + onChange( + produce(data, (draft) => { + draft.items.push({ + type: 'member', + user_id: id, + }) + }), + ) + } + + return ( +
+
+
+
+ +
{t(`${i18nPrefix}.deliveryMethod.emailConfigure.memberSelector.title`)}
+
+
+ +
+
+
+
+
+ {currentWorkspace?.name[0]?.toLocaleUpperCase()} +
+
{t(`${i18nPrefix}.deliveryMethod.emailConfigure.allMembers`, { workspaceName: currentWorkspace.name.replace(/'/g, '’') })}
+ onChange({ ...data, whole_workspace: checked })} + /> +
+
+ ) +} + +export default memo(Recipient) diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/member-list.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/member-list.tsx new file mode 100644 index 0000000000..ccd24be37c --- /dev/null +++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/member-list.tsx @@ -0,0 +1,79 @@ +'use client' +import type { FC } from 'react' +import React, { useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import Input from '@/app/components/base/input' +import Avatar from '@/app/components/base/avatar' +import type { Member } from '@/models/common' +import cn from '@/utils/classnames' + +const i18nPrefix = 'workflow.nodes.humanInput' + +type Props = { + value: any[] + searchValue: string + onSearchChange: (value: string) => void + list: Member[] + onSelect: (value: any) => void + email: string +} + +const MemberList: FC = ({ searchValue, list, value, onSearchChange, onSelect, email }) => { + const { t } = useTranslation() + + const filteredList = useMemo(() => { + if (!list.length) return [] + if (!searchValue) return list + return list.filter((account) => { + const name = account.name || '' + const email = account.email || '' + return name.toLowerCase().includes(searchValue.toLowerCase()) + || email.toLowerCase().includes(searchValue.toLowerCase()) + }) + }, [list, searchValue]) + + return ( +
+
+ onSearchChange(e.target.value)} + /> +
+
+ {filteredList.map(account => ( +
item.user_id === account.id) && 'bg-transparent hover:bg-transparent', + )} + onClick={() => { + if (value.some((item: { user_id: string }) => item.user_id === account.id)) return + onSelect(account.id) + }} + > + item.user_id === account.id) && 'opacity-50')} avatar={account.avatar_url} size={24} name={account.name} /> +
item.user_id === account.id) && 'opacity-50')}> +
+ {account.name} + {account.status === 'pending' && {t('common.members.pending')}} + {email === account.email && {t('common.members.you')}} +
+
{account.email}
+
+ {!value.some((item: { user_id: string }) => item.user_id === account.id) && ( +
{t(`${i18nPrefix}.deliveryMethod.emailConfigure.memberSelector.add`)}
+ )} + {value.some((item: { user_id: string }) => item.user_id === account.id) && ( +
{t(`${i18nPrefix}.deliveryMethod.emailConfigure.memberSelector.added`)}
+ )} +
+ ))} +
+
+ ) +} + +export default MemberList diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/member-selector.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/member-selector.tsx new file mode 100644 index 0000000000..1b5ad92877 --- /dev/null +++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/recipient/member-selector.tsx @@ -0,0 +1,68 @@ +'use client' +import type { FC } from 'react' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + RiContactsBookLine, +} from '@remixicon/react' +import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' +import Button from '@/app/components/base/button' +import MemberList from './member-list' +import type { Member } from '@/models/common' +import cn from '@/utils/classnames' + +const i18nPrefix = 'workflow.nodes.humanInput' + +type Props = { + value: any[] + email: string + onSelect: (value: any) => void + list: Member[] +} + +const MemberSelector: FC = ({ + value, + email, + onSelect, + list = [], +}) => { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + const [searchValue, setSearchValue] = useState('') + + return ( + + setOpen(v => !v)} + > + + + + + + + ) +} +export default MemberSelector diff --git a/web/app/components/workflow/nodes/human-input/types.ts b/web/app/components/workflow/nodes/human-input/types.ts index 41c7a6a11e..136ff58767 100644 --- a/web/app/components/workflow/nodes/human-input/types.ts +++ b/web/app/components/workflow/nodes/human-input/types.ts @@ -21,8 +21,13 @@ export type Recipient = { user_id?: string } +export type RecipientData = { + whole_workspace: boolean + items: Recipient[] +} + export type EmailConfig = { - recipients: Recipient[] + recipients: RecipientData subject: string body: string }