|
|
|
@ -3,8 +3,8 @@ import { App, Popover, Flex, Button, List, Input, Tabs, Tag, Alert, Divider } fr
|
|
|
|
|
import { MessageOutlined, SendOutlined } from '@ant-design/icons';
|
|
|
|
|
import useAuthStore from '@/stores/AuthStore';
|
|
|
|
|
import useConversationStore from '@/stores/ConversationStore';
|
|
|
|
|
import { cloneDeep, getNestedValue, groupBy, objectMapper, removeFormattingChars, sortArrayByOrder, sortObjectsByKeysMap, TagColorStyle } from '@/utils/commons';
|
|
|
|
|
import { replaceTemplateString } from '@/channel/bubbleMsgUtils';
|
|
|
|
|
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';
|
|
|
|
|
|
|
|
|
@ -36,16 +36,33 @@ const CategoryList = ({ dataSource, handleSendTemplate, valueMapped, onInput, ac
|
|
|
|
|
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) =>
|
|
|
|
|
{tempArr.map((ele, i) =>
|
|
|
|
|
typeof ele === 'string' ? (
|
|
|
|
|
<span key={ele.trim()} className=' text-wrap'>
|
|
|
|
|
{ele.replace(/\n+/g, '\n')}
|
|
|
|
|
</span>
|
|
|
|
|
) : ele.key.includes('free') || ele.key.includes('detail') ? (
|
|
|
|
|
<Input.TextArea
|
|
|
|
|
key={ele.key}
|
|
|
|
|
key={`${ele.key}_${i}`}
|
|
|
|
|
rows={2}
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
onInput(tempItem, ele.key, e.target.value, paramsVal)
|
|
|
|
@ -59,14 +76,14 @@ const CategoryList = ({ dataSource, handleSendTemplate, valueMapped, onInput, ac
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<Input
|
|
|
|
|
key={ele.key}
|
|
|
|
|
key={`${ele.key}_${i}`}
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
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键跳到下一个空格`}
|
|
|
|
|
placeholder={`${paramsVal[ele.key] || ele.key} ${ele?.placeholder || '按Tab键跳到下一个空格'}`}
|
|
|
|
|
value={activeInput[tempItem.name]?.[ele.key] || paramsVal[ele.key] || ''}
|
|
|
|
|
// onPressEnter={() => handleSendTemplate(tempItem)}
|
|
|
|
|
/>
|
|
|
|
@ -83,14 +100,19 @@ const CategoryList = ({ dataSource, handleSendTemplate, valueMapped, onInput, ac
|
|
|
|
|
return (
|
|
|
|
|
<div className='pb-1'>
|
|
|
|
|
{'text' === headerObj.format.toLowerCase() && <div>{renderForm({ tempItem }, 'header')}</div>}
|
|
|
|
|
{'image' === headerObj.format.toLowerCase() && <img src={headerObj.example.header_url} height={100}></img>}
|
|
|
|
|
{'image' === headerObj.format.toLowerCase() && (
|
|
|
|
|
<div className='flex items-center'>
|
|
|
|
|
<img src={headerObj.example.header_url} height={100} className='mr-1'></img>
|
|
|
|
|
{renderForm({ tempItem }, 'header')}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{['document', 'video'].includes(headerObj.format.toLowerCase()) && (
|
|
|
|
|
<a href={headerObj.example.header_url} target='_blank' key={headerObj.format} rel='noreferrer' className='text-sm'>
|
|
|
|
|
[ {headerObj.format} ]({headerObj.example.header_url})
|
|
|
|
|
</a>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
const renderButtons = ({ tempItem }) => {
|
|
|
|
|
if (isEmpty(tempItem.components.buttons)) {
|
|
|
|
@ -100,7 +122,8 @@ const CategoryList = ({ dataSource, handleSendTemplate, valueMapped, onInput, ac
|
|
|
|
|
return (
|
|
|
|
|
<div className='flex gap-1 pt-1'>
|
|
|
|
|
{buttons.map((btn, index) =>
|
|
|
|
|
btn.type.toLowerCase() === 'url' ? (
|
|
|
|
|
<>
|
|
|
|
|
{btn.type.toLowerCase() === 'url' ? (
|
|
|
|
|
<Button className='text-blue-500' size={'small'} href={btn.url} target={'_blank'} key={btn.url} rel='noreferrer'>
|
|
|
|
|
{btn.text}
|
|
|
|
|
</Button>
|
|
|
|
@ -108,11 +131,18 @@ const CategoryList = ({ dataSource, handleSendTemplate, valueMapped, onInput, ac
|
|
|
|
|
<Button className='text-blue-500' size={'small'} key={btn.phone_number} rel='noreferrer'>
|
|
|
|
|
{btn.text} ({btn.phone_number})
|
|
|
|
|
</Button>
|
|
|
|
|
) : (
|
|
|
|
|
<Button className='text-blue-500' size={'small'} key={btn.type} rel='noreferrer'>
|
|
|
|
|
)
|
|
|
|
|
: btn.type.toLowerCase() === 'copy_code' ? null
|
|
|
|
|
// (
|
|
|
|
|
// <span>{renderForm({ tempItem }, 'buttons')}</span>
|
|
|
|
|
// )
|
|
|
|
|
: (
|
|
|
|
|
<Button className='text-blue-500' size={'small'} key={`${btn.type}_${index}`} rel='noreferrer'>
|
|
|
|
|
{btn.text}
|
|
|
|
|
</Button>
|
|
|
|
|
)
|
|
|
|
|
)}
|
|
|
|
|
{Object.prototype.hasOwnProperty.call(btn, "example") ? (<span key={`${btn.type}_${index}`}>{renderForm({ tempItem }, 'buttons')}</span>) : null}
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
@ -213,6 +243,7 @@ const InputTemplate = ({ disabled = false, invokeSendMessage, channel }) => {
|
|
|
|
|
|
|
|
|
|
const handleSendTemplate = (fromTemplate) => {
|
|
|
|
|
const mergeInput = { ...cloneDeep(valueMapped), ...activeInput[fromTemplate.name] };
|
|
|
|
|
// console.log('----------------------------------------------', mergeInput)
|
|
|
|
|
let valid = true;
|
|
|
|
|
const msgObj = {
|
|
|
|
|
type: 'whatsappTemplate',
|
|
|
|
@ -220,29 +251,82 @@ const InputTemplate = ({ disabled = false, invokeSendMessage, channel }) => {
|
|
|
|
|
template: {
|
|
|
|
|
name: fromTemplate.name,
|
|
|
|
|
language: { code: fromTemplate.language },
|
|
|
|
|
components: sortArrayByOrder(fromTemplate.components_origin.map((citem) => {
|
|
|
|
|
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: citem.example.header_url[0] } };
|
|
|
|
|
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;
|
|
|
|
|
return ['body', 'header'].includes(citem.type.toLowerCase()) ? {
|
|
|
|
|
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,
|
|
|
|
|
} : {...citem, type: citem.type.toLowerCase(),};
|
|
|
|
|
}), 'type', ['header', 'body', 'footer', 'buttons'] ),
|
|
|
|
|
}] : ['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.map((citem) => citem.text).join(''),
|
|
|
|
|
text: msgObj.template.components.filter(com => com.type.toLowerCase() === 'body').map((citem) => citem.text).join(''),
|
|
|
|
|
};
|
|
|
|
|
if (valid !== true) {
|
|
|
|
|
notification.warning({
|
|
|
|
@ -254,6 +338,7 @@ const InputTemplate = ({ disabled = false, invokeSendMessage, channel }) => {
|
|
|
|
|
});
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// console.log('------------------------------------------------------------------------------', msgObj );
|
|
|
|
|
invokeSendMessage(channel === 'waba' ? msgObj : plainTextMsgObj);
|
|
|
|
|
setOpenTemplates(false);
|
|
|
|
|
setActiveInput({});
|
|
|
|
@ -267,10 +352,6 @@ const InputTemplate = ({ disabled = false, invokeSendMessage, channel }) => {
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<Popover
|
|
|
|
@ -318,7 +399,7 @@ const InputTemplate = ({ disabled = false, invokeSendMessage, channel }) => {
|
|
|
|
|
...(Object.keys(templateLangMapped).map(lang => ({
|
|
|
|
|
key: lang, label: lang.toUpperCase(), children: <CategoryList key={'lang-templates-'+lang} dataSource={templateLangMapped[lang]} {...{ handleSendTemplate, activeInput, onInput, valueMapped}} />
|
|
|
|
|
})))
|
|
|
|
|
]} defaultActiveKey='utility' tabBarExtraContent={{right: <Alert type='info' message={channel==='waba'?'为提升账号质量, 请尽量使用"触达率高"模板': '模板消息将用纯文本发送'} showIcon className='py-0' />, }} size='small' />
|
|
|
|
|
]} defaultActiveKey='utility' tabBarExtraContent={{right: <Alert type='info' message={channel==='waba'?'请优先使用"触达率高"模板': '模板消息将用纯文本发送'} showIcon className='py-0' />, }} size='small' />
|
|
|
|
|
) :
|
|
|
|
|
(
|
|
|
|
|
// Search result
|
|
|
|
|