import { useState, useRef, useEffect, memo, useMemo, useCallback } from 'react'; import { App, Popover, Flex, Button, List, Input, Tabs, Tag, Alert, Divider } from 'antd'; import { MessageOutlined, SendOutlined } from '@ant-design/icons'; import useAuthStore from '@/stores/AuthStore'; import useConversationStore from '@/stores/ConversationStore'; import { cloneDeep, getNestedValue, groupBy, objectMapper, removeFormattingChars, sortArrayByOrder, TagColorStyle } from '@/utils/commons'; import { replaceTemplateString } from '@/channel/bubbleMsgUtils'; import { isEmpty } from '@/utils/commons'; import useStyleStore from '@/stores/StyleStore'; const splitTemplate = (template) => { const placeholders = template.match(/{{(.*?)}}/g) || []; const keys = placeholders.map((placeholder) => placeholder.slice(2, -2)); const arr = template.split(/{{(.*?)}}/).filter(Boolean); const obj = arr.reduce((prev, curr, index) => { if (keys.includes(curr)) { prev.push({ key: curr }); } else { prev.push(curr); } return prev; }, []); return obj; }; // UTILITY // MARKETING const templateCaterogyText = { 'UTILITY': '跟进', 'MARKETING': '营销' } const templateCaterogyTipText = { 'UTILITY': '触达率高', 'MARKETING': '' } const CategoryList = ({ dataSource, handleSendTemplate, valueMapped, onInput, activeInput }) => { const currentConversation = useConversationStore((state) => state.currentConversation); const renderForm = ({ tempItem }, key = 'body') => { const templateText = tempItem.components?.[key]?.[0]?.text || '' const tempArr = splitTemplate(templateText) const keys = (templateText.match(/{{(.*?)}}/g) || []).map((key) => key.replace(/{{|}}/g, '')) const paramsVal = keys.reduce((r, k) => ({ ...r, [k]: getNestedValue(valueMapped, [k]) }), {}) return ( <> {tempArr.map((ele) => typeof ele === 'string' ? ( {ele.replace(/\n+/g, '\n')} ) : ele.key.includes('free') || ele.key.includes('detail') ? ( { onInput(tempItem, ele.key, e.target.value, paramsVal) }} className={` w-11/12 `} size={'small'} title={ele.key} placeholder={`${paramsVal[ele.key] || ele.key} 按Tab键跳到下一个空格\n注意: 模板消息无法输入换行`} value={activeInput[tempItem.name]?.[ele.key] || paramsVal[ele.key] || ''} // onPressEnter={() => handleSendTemplate(tempItem)} /> ) : ( { onInput(tempItem, ele.key, e.target.value, paramsVal) }} className={ele.key.includes('free') || ele.key.includes('detail') ? `w-full block ` : `w-auto ${paramsVal[ele.key] ? 'max-w-24' : 'max-w-60'}`} size={'small'} title={ele.key} placeholder={`${paramsVal[ele.key] || ele.key} 按Tab键跳到下一个空格`} value={activeInput[tempItem.name]?.[ele.key] || paramsVal[ele.key] || ''} // onPressEnter={() => handleSendTemplate(tempItem)} /> ), )} > ) }; const renderHeader = ({ tempItem }) => { if (isEmpty(tempItem.components.header)) { return null; } const headerObj = tempItem.components.header[0]; return ( {'text' === headerObj.format.toLowerCase() && {renderForm({ tempItem }, 'header')}} {'image' === headerObj.format.toLowerCase() && } {['document', 'video'].includes(headerObj.format.toLowerCase()) && ( [ {headerObj.format} ]({headerObj.example.header_url}) )} ); } const renderButtons = ({ tempItem }) => { if (isEmpty(tempItem.components.buttons)) { return null; } const buttons = tempItem.components.buttons.reduce((r, c) => r.concat(c.buttons), []); return ( {buttons.map((btn, index) => btn.type.toLowerCase() === 'url' ? ( {btn.text} ) : btn.type.toLowerCase() === 'phone_number' ? ( {btn.text} ({btn.phone_number}) ) : ( {btn.text} ) )} ); } return ( ( {item.components.header?.[0]?.text || (item.displayName)} {item.language.toUpperCase()} {/* {templateCaterogyText[item.category]} */} {templateCaterogyTipText[item.category] && {templateCaterogyTipText[item.category]}} handleSendTemplate(item)} size={'small'} type='link' key={'send'} icon={}> Send } description={ <> {renderHeader({ tempItem: item })} {renderForm({ tempItem: item })} {item.components?.footer?.[0] ? {item.components.footer[0].text || ''} : null} {renderButtons({ tempItem: item })} > } /> )} /> ) } const InputTemplate = ({ disabled = false, invokeSendMessage }) => { const [mobile] = useStyleStore((state) => [state.mobile]); const searchInputRef = useRef(null); const { notification } = App.useApp(); const loginUser = useAuthStore((state) => state.loginUser); const { whatsAppBusiness } = loginUser; loginUser.usernameEN = loginUser.accountList[0].OPI_NameEN.split(' ')?.[0] || loginUser.username; const currentConversation = useConversationStore((state) => state.currentConversation); const templates = useConversationStore((state) => state.templates); const [openTemplates, setOpenTemplates] = useState(false); const [dataSource, setDataSource] = useState(templates); const [templateCMapped, setTemplateCMapped] = useState({}); const [templateLangMapped, setTemplateLangMapped] = useState({}); const [searchContent, setSearchContent] = useState(''); // 用于替换变量: customer, agent const valueMapped = { ...cloneDeep(currentConversation), ...objectMapper(loginUser, { usernameEN: [{ key: 'agent_name' }, { key: 'your_name' }, { key: 'your_name1' }, { key: 'your_name2' }] }) }; useEffect(() => { setDataSource([]); // setDataSource(templates); const mappedByCategory = groupBy(templates, 'category'); const mappedByLang = groupBy(templates, 'language'); setTemplateCMapped(mappedByCategory); setTemplateLangMapped(mappedByLang); return () => {}; }, [templates]); useEffect(() => { setActiveInput({}); return () => {}; }, [currentConversation.sn]) const handleSearchTemplates = (val) => { if (val.toLowerCase().trim() !== '') { const res = templates.filter( (item) => item.name.includes(val.toLowerCase().trim()) || item.components_origin.some((itemc) => (itemc?.text || '').toLowerCase().includes(val.toLowerCase().trim())) ); setDataSource(res); return false; } setDataSource([]); }; const handleSendTemplate = (fromTemplate) => { const mergeInput = { ...cloneDeep(valueMapped), ...activeInput[fromTemplate.name] }; let valid = true; const msgObj = { type: 'whatsappTemplate', // statusTitle: 'Ready to send', template: { name: fromTemplate.name, language: { code: fromTemplate.language }, components: sortArrayByOrder(fromTemplate.components_origin.map((citem) => { const keys = ((citem?.text || '').match(/{{(.*?)}}/g) || []).map((key) => key.replace(/{{|}}/g, '')); const params = keys.map((v) => ({ type: 'text', text: getNestedValue(mergeInput, [v]) })); if (citem.type.toLowerCase() === 'header' && (citem?.format || 'text').toLowerCase() !== 'text') { params[0] = { type: citem.format.toLowerCase(), [citem.format.toLowerCase()]: { link: citem.example.header_url[0] } }; } const paramText = keys.length ? params.map((p) => p.text) : []; const fillTemplate = paramText.length ? replaceTemplateString(citem?.text || '', paramText) : citem?.text || ''; valid = keys.length !== paramText.filter((s) => s).length ? false : valid; return ['body', 'header'].includes(citem.type.toLowerCase()) ? { type: citem.type.toLowerCase(), parameters: params, text: fillTemplate, } : {...citem, type: citem.type.toLowerCase(),}; }), 'type', ['header', 'body', 'footer', 'buttons'] ), }, template_origin: fromTemplate, }; if (valid !== true) { notification.warning({ message: '提示', description: '信息未填写完整, 请补充填写', placement: 'top', duration: 3, closeIcon: false, }); return false; } invokeSendMessage(msgObj); setOpenTemplates(false); setActiveInput({}); }; const [activeInput, setActiveInput] = useState({}); const onInput = (tempItem, key, val, initObj) => { const _val = removeFormattingChars(val); // Param text cannot have new-line/tab characters or more than 4 consecutive spaces setActiveInput((prev) => { return { ...prev, [tempItem.name]: { ...initObj, ...prev[tempItem.name], [key]: _val } }; }); }; return ( <> { setSearchContent(e.target.value); handleSearchTemplates(e.target.value); }} placeholder='搜索名称' /> setOpenTemplates(false)}>× } content={ <> {/* { setSearchContent(e.target.value); handleSearchTemplates(e.target.value); }} placeholder='搜索名称' /> setOpenTemplates(false)}>× */} {isEmpty(dataSource) && isEmpty(searchContent) ? ( }, // { key: 'utility', label: '再次沟通', children: }, ...(Object.keys(templateLangMapped).map(lang => ({ key: lang, label: lang.toUpperCase(), children: }))) ]} defaultActiveKey='utility' tabBarExtraContent={{right: , }} size='small' /> ) : ( // Search result )} {/* ( {item.components.header?.[0]?.text || item.name}{item.language.toUpperCase()} handleSendTemplate(item)} size={'small'} type='link' key={'send'} icon={}> Send } description={ <> {renderHeader({ tempItem: item })} {renderForm({ tempItem: item })} {item.components?.footer?.[0] ? {item.components.footer[0].text || ''} : null} {renderButtons({ tempItem: item })} > } /> )} /> */} > } // title={ // // 🙋打招呼 // setOpenTemplates(false)}>× // } trigger='click' arrow={false} open={openTemplates} onOpenChange={(v) => { setOpenTemplates(v); // setActiveInput({}); }}> } size={'middle'} disabled={disabled} /> > ); }; export default InputTemplate;