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, flush, getNestedValue, groupBy, objectMapper, removeFormattingChars, sortArrayByOrder, sortObjectsByKeysMap, TagColorStyle } from '@/utils/commons'; import { replaceTemplateString, whatsappTemplateBtnParamTypesMapped } 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]) }), {}) if (key === 'header' && tempItem.components?.header?.[0]?.example?.header_url) { const headerImg = { key: 'header_url', placeholder: '头图地址' }; tempArr.unshift(headerImg); } if (key === 'buttons' && (tempItem.components?.buttons?.[0]?.buttons || []).findIndex(btn => btn.type === 'COPY_CODE') !== -1) { const btnCode = { key: 'copy_code', placeholder: '复制条码' }; tempArr.push(btnCode); } if (key === 'buttons' ) { (tempItem.components?.buttons?.[0]?.buttons || []).filter(btn0 => btn0.type === 'URL').forEach((btn) => { const hasParam = Object.prototype.hasOwnProperty.call(btn, "example"); const templateUrl = btn.url || '' const urlParamKeys = (templateUrl.match(/{{(.*?)}}/g) || []).map((key) => key.replace(/{{|}}/g, '')).map(key => ({ key })) hasParam ? tempArr.push(...urlParamKeys) : false; }); } return ( <> {tempArr.map((ele, i) => 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} ${ele?.placeholder || '按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() && (
{renderForm({ tempItem }, 'header')}
)} {['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.type.toLowerCase() === 'phone_number' ? ( ) : btn.type.toLowerCase() === 'copy_code' ? null // ( // {renderForm({ tempItem }, 'buttons')} // ) : ( )} {Object.prototype.hasOwnProperty.call(btn, "example") ? ({renderForm({ tempItem }, 'buttons')}) : null} )}
); } return ( ( {item.components.header?.[0]?.text || (item.displayName)} {item.language.toUpperCase()} {/* {templateCaterogyText[item.category]} */} {templateCaterogyTipText[item.category] && {templateCaterogyTipText[item.category]}} } 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, channel }) => { 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' }] }), ...{ order_number: currentConversation.coli_id } }; useEffect(() => { setDataSource([]); // setDataSource(templates); const mappedByCategory = groupBy(templates, 'category'); const mappedByLang = sortObjectsByKeysMap(groupBy(templates, 'displayLanguage'), ['en']); // todo: 按账户语种排序 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.displayName.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] }; // console.log('----------------------------------------------', mergeInput) let valid = true; const msgObj = { type: 'whatsappTemplate', // statusTitle: 'Ready to send', template: { name: fromTemplate.name, language: { code: fromTemplate.language }, components: sortArrayByOrder(fromTemplate.components_origin.reduce((r, citem) => { const keys = ((citem?.text || '').match(/{{(.*?)}}/g) || []).map((key) => key.replace(/{{|}}/g, '')); const params = keys.map((v) => ({ type: 'text', text: getNestedValue(mergeInput, [v]) })); const notTextKeys = []; const paramNotText = []; if (citem.type.toLowerCase() === 'header' && (citem?.format || 'text').toLowerCase() !== 'text') { params[0] = { type: citem.format.toLowerCase(), [citem.format.toLowerCase()]: { link: mergeInput?.header_url || citem.example.header_url[0] } }; // 头图可以不替换 // notTextKeys.push('header_url'); // mergeInput?.header_url ? paramNotText.push(mergeInput?.header_url || '') : false; } let buttonsComponents; if (citem.type.toLowerCase() === 'buttons' ) { buttonsComponents = citem.buttons.map((btn, i) => { const hasParam = Object.prototype.hasOwnProperty.call(btn, "example"); // whatsappTemplateBtnParamTypesMapped let fillBtn = {}; const paramKey = whatsappTemplateBtnParamTypesMapped[btn.type.toLowerCase()]; if (paramKey) { params[0] = { type: paramKey, [paramKey]: mergeInput?.[btn.type.toLowerCase()] || '' }; notTextKeys.push(paramKey); mergeInput?.[btn.type.toLowerCase()] ? paramNotText.push(mergeInput?.[btn.type.toLowerCase()] || '') : false; fillBtn = { text: btn.text || `${btn.type}:${mergeInput?.[btn.type.toLowerCase()] || ''}` }; } if (btn.type.toLowerCase() === 'url') { const templateUrl = btn.url || ''; const urlParamKeys = (templateUrl.match(/{{(.*?)}}/g) || []).map((key) => key.replace(/{{|}}/g, '')); const urlParams = urlParamKeys.map(key => ({ type: 'text', text: getNestedValue(mergeInput, [key]) })) params.push(...urlParams); notTextKeys.push(...urlParamKeys); urlParamKeys.forEach(key => { if (getNestedValue(mergeInput, [key])) { paramNotText.push(getNestedValue(mergeInput, [key])); } }); fillBtn = hasParam ? { text: btn.text, url: replaceTemplateString(templateUrl, paramNotText) } : {}; } // if (hasParam) { // notTextKeys.push(paramKey); // mergeInput?.[btn.type.toLowerCase()] ? paramNotText.push(mergeInput?.[btn.type.toLowerCase()] || '') : false; // } return hasParam ? { type: 'button', index: i, sub_type: btn.type.toLowerCase(), parameters: params, ...fillBtn } : null; }) buttonsComponents = flush(buttonsComponents); } // console.log('******', buttonsComponents, '\n', notTextKeys, '\n', paramNotText) 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; valid = notTextKeys.length !== paramNotText.filter((s) => s).length ? false : valid; const _components = ['body', 'header'].includes(citem.type.toLowerCase()) ? [{ type: citem.type.toLowerCase(), parameters: params, text: fillTemplate, }] : ['buttons'].includes(citem.type.toLowerCase()) ? buttonsComponents : [{...citem, type: citem.type.toLowerCase(),}]; return r.concat(_components); }, []), 'type', ['body', 'header', 'footer', 'button', 'buttons'] ), components_omit: fromTemplate.components_origin.reduce((r, citem) => { const _componentItems = citem.type.toLowerCase() === 'buttons' ? citem.buttons.map((btn, index) => ({ ...btn, index, type: 'button', sub_type: btn.type.toLowerCase() })) : [{ ...citem, type: citem.type.toLowerCase() }] const staticComponents = _componentItems.filter((item) => !item.example && item.type.toLowerCase() !== 'body') return r.concat(staticComponents) }, []), }, template_origin: fromTemplate, }; const plainTextMsgObj = { type: 'text', text: msgObj.template.components.filter(com => com.type.toLowerCase() === 'body').map((citem) => citem.text).join(''), }; if (valid !== true) { notification.warning({ message: '提示', description: '信息未填写完整, 请补充填写', placement: 'top', duration: 3, closeIcon: false, }); return false; } // console.log('------------------------------------------------------------------------------', msgObj ); invokeSendMessage(channel === 'waba' ? msgObj : plainTextMsgObj); 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='搜索名称' /> } content={ <> {/*
{ setSearchContent(e.target.value); handleSearchTemplates(e.target.value); }} placeholder='搜索名称' />
*/} {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()} } description={ <>
{renderHeader({ tempItem: item })}
{renderForm({ tempItem: item })}
{item.components?.footer?.[0] ?
{item.components.footer[0].text || ''}
: null} {renderButtons({ tempItem: item })}
} />
)} /> */} } // title={ //
//
🙋打招呼
// //
} trigger='click' arrow={false} open={openTemplates} onOpenChange={(v) => { setOpenTemplates(v); // setActiveInput({}); }}>