Compare commits

...

24 Commits
v1.6.0 ... main

@ -1,7 +1,7 @@
{
"name": "global-sales",
"private": true,
"version": "1.6.0",
"version": "1.6.7",
"type": "module",
"scripts": {
"dev": "vite",

@ -11,6 +11,9 @@ import dayjs from 'dayjs';
*/
export const fetchTemplates = async (params) => {
const data = await fetchJSON(`${API_HOST}/listtemplates`, params);
const leftPageCnt = Math.ceil( data?.result?.total/100 || 0)-1;
const leftData = await Promise.all(Array.from({ length: leftPageCnt }, (_, i) => fetchJSON(`${API_HOST}/listtemplates`, {...params, page: i+2})));
const leftItems = leftData.map(item => item.result.items).flat();
const topName = [
'agent_intro_with_update_v1',
'online_inquiry_received',
@ -51,7 +54,7 @@ export const fetchTemplates = async (params) => {
'7notification_of_one_day_before_ending_the_trip_only_asean_by_cr_v7',
'notification_of_one_day_before_ending_the_trip_by_cr_v2',
];
const crNamesOmit = [
const NamesOmit = [
'birthday_greetings_by_marketing','one_day_before_ending_the_trip_by_marketing',
'introduce_the_voucher_one_day_before_ending_the_trip_by_marketing',
'birthday_greetings_by_customer_relations_0',
@ -66,8 +69,9 @@ export const fetchTemplates = async (params) => {
'notification_of_account_updated_by_cr',
'birthday_greetings_by_customer_relations',
'one_day_before_ending_the_trip_by_customer_relations',
'network_troubleshooting',
]
const canUseTemplates = (data?.result?.items || [])
const canUseTemplates = (data?.result?.items || []).concat(leftItems)
.filter((_t) => _t.status === 'APPROVED' && !['say_hello_from_trip_advisor', 'free_style_7', 'free_style_1', 'free_style_2'].includes(_t.name))
.map((ele, i) => ({
...ele,
@ -76,15 +80,15 @@ export const fetchTemplates = async (params) => {
key: ele.name,
// displayName: ele.name.startsWith('order_updated') ? templatesDisplayNameMap['order_updated']+`_${i}` : templatesDisplayNameMap?.[ele.name] || ele.name,
displayName: templatesDisplayNameMap?.[ele.name] || (ele.name.startsWith('order_updated') ? templatesDisplayNameMap['order_updated'] + `_${i}` : ele.name),
displayLanguage: crNamesOmit.includes(ele.name)
? '客运-'
: crNamesAuto.includes(ele.name)
? '客运Task'
: crNames.includes(ele.name) || ele.name.includes('by_cr')
? ele.language + '-客运'
: scNames.includes(ele.name)
? ele.language + '-示例'
: ele.language,
displayLanguage: NamesOmit.includes(ele.name)
? '--'
: crNamesAuto.includes(ele.name)
? '客运Task'
: crNames.includes(ele.name) || ele.name.includes('by_cr')
? ele.language + '-客运'
: scNames.includes(ele.name)
? ele.language + '-示例'
: ele.language.slice(0, 2),
}))
const top2Name = topName.concat(canUseTemplates.filter(_t => _t.name.startsWith('order_updated')).map(_tem => _tem.name));
@ -94,7 +98,7 @@ export const fetchTemplates = async (params) => {
const secondS = second.sort(sortBy('name'));
const raw = canUseTemplates.filter((_t) => !top2Name.includes(_t.name) && !_t.name.includes('free_style'));
// 剩下的排序
const rawS = sortArrayByOrder(raw, 'name', [...crNames, ...scNames, ...crNamesOmit ]);
const rawS = sortArrayByOrder(raw, 'name', [...crNames, ...scNames, ...NamesOmit ]);
return [...top, ...secondS, ...rawS];
};
/**

@ -58,6 +58,27 @@ export const WABAccounts = [
"requestedVerifiedName": "Customer Relation Specialist",
"rejectionReason": "NONE"
},
{
"id": "955633124303178",
"phoneNumber": "+85265210895",
"wabaId": "190290134156880",
"verifiedName": "Customer Relation Specialist at Highlights",
"qualityRating": "UNKNOWN",
"qualityUpdateEvent": "ONBOARDING",
"messagingLimit": "TIER_2K",
"whatsappBusinessManagerMessagingLimit": "TIER_2K",
"isOfficialBusinessAccount": false,
"codeVerificationStatus": "VERIFIED",
"status": "CONNECTED",
"displayPhoneNumber": "+852 6521 0895",
"nameStatus": "APPROVED",
"newName": "Customer Relation Specialist at Highlights",
"newNameStatus": "NONE",
"decision": "APPROVED",
"requestedVerifiedName": "Customer Relation Specialist at Highlights",
"rejectionReason": "NONE",
"isOnBizApp": false
},
];
export const WABAccountsMapped = WABAccounts.reduce((a, c) => ({ ...a, [removeFirstPlus(c.phoneNumber)]: c, [c.phoneNumber]: c }), {})

@ -3,7 +3,7 @@ import useConversationStore from '@/stores/ConversationStore';
const useShortUrlChange = () => {
const setGlobalNotify = useConversationStore((state) => state.setGlobalNotify);
const apiPrefix = {
"japanhighlights.com": "https://www.japanhighlights.com/index.php",
"chinahighlights.com": "https://www.chinahighlights.com/guide-use.php",
@ -15,7 +15,7 @@ const useShortUrlChange = () => {
const fetchNowConversationsitems = async (base64Url, apiUrl) => {
try {
const formData = new FormData();
formData.append('url', base64Url);
formData.append(' url', base64Url);
formData.append('type', 'info');
const response = await fetch(`${apiUrl}/apps/short_link/index/create`, {
method: 'POST',
@ -35,11 +35,11 @@ const useShortUrlChange = () => {
const urlBase64 = (longUrl) => {
try {
const extracted2 = longUrl.match(/https:\/\/www\.([^\/]+)/)?.[1] || '';
const encoder = new TextEncoder();
const utf8Bytes = encoder.encode(longUrl);
const base64Url = btoa(String.fromCharCode(...utf8Bytes));
return { base64Url, extracted2 };
} catch (error) {
setGlobalNotify([{
@ -52,24 +52,42 @@ const useShortUrlChange = () => {
return { base64Url: '', extracted2: '' };
}
};
const normalizeUrl = (longUrl) => {
try {
const hasProtocol = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(longUrl);
const url = new URL(hasProtocol ? longUrl : `http://${longUrl}`);
const params = Object.fromEntries(url.searchParams.entries());
return {
url: url.href,
valid: true,
origin: url.origin,
hostname: url.hostname,
pathname: url.pathname,
params
};
} catch (e) {
return { valid: false, error: "Invalid URL" };
}
};
const convertUrl = useCallback(async (longUrl) => {
if (!longUrl.trim()) {
const normalizedUrl = normalizeUrl(longUrl);
if (!normalizedUrl.valid || Object.keys(normalizedUrl?.params || {}).length === 0) {
setGlobalNotify([{
key: Date.now().toString(),
title: '错误',
content: '不是有效的长链接',
type: 'error'
content: '没有需要转换的长链接, 请重新输入',
type: 'warning'
}]);
return null;
}
const { base64Url, extracted2 } = urlBase64(longUrl);
const { base64Url, extracted2 } = urlBase64(normalizedUrl.url);
if (base64Url) {
const apiUrl = apiPrefix[extracted2] || apiPrefix["chinahighlights.com"];
const extracted1 = apiUrl.match(/^https?:\/\/[^\/]*\.com/)?.[0] || '';
const data = await fetchNowConversationsitems(base64Url, apiUrl);
if (data) {
const resultShortUrl = extracted1 + data.isl_link;
@ -84,7 +102,7 @@ const useShortUrlChange = () => {
setGlobalNotify([{
key: Date.now().toString(),
title: '错误',
content: '转换失败请检查输入的URL是否正确',
content: '转换失败请检查输入的URL是否正确',
type: 'error'
}]);
return null;
@ -103,4 +121,4 @@ const useShortUrlChange = () => {
return { convertUrl };
};
export default useShortUrlChange;
export default useShortUrlChange;

@ -6,9 +6,9 @@ import ShorturlConversion from '@/views/accounts/ShorturlConversion'
const GenerateShorturlDrawer = ({ ...props }) => {
const [openShorturlDrawer, closeShorturlDrawer, shorturlDrawerOpen] = useUrlStore((state) => [state.openDrawer, state.closeDrawer, state.drawerOpen])
return (
<Drawer title='短链接转换' placement={'top'} size={'large'} onClose={() => closeShorturlDrawer()} open={shorturlDrawerOpen}>
<Drawer title='短链接转换' placement={'top'} onClose={() => closeShorturlDrawer()} open={shorturlDrawerOpen}>
<ShorturlConversion/>
</Drawer>
)
}
export default GenerateShorturlDrawer
export default GenerateShorturlDrawer

@ -336,13 +336,6 @@ const Conversations = () => {
<Button onClick={toggleClosedConversationsList} icon={activeList ? '🗂' : '📌'} type='text' />
</Tooltip>
}
{mobile && (
<AudioTwoTone className='px-3'
onClick={() => {
navigate(`/callcenter/call`)
}}
/>
)}
</div>
<div className='flex-1 overflow-x-hidden overflow-y-auto relative'>
{/* {mobile && conversationsListLoading && dataSource.length === 0 ? ( */}

@ -7,6 +7,7 @@ import PaymentlinkBtn from './PaymentlinkBtn'
import SnippestBtn from './SnippestBtn'
import useConversationStore from '@/stores/ConversationStore'
import { isEmpty } from '@haina/utils-commons'
import ShortlinkBtn from './ShortlinkBtn'
const ComposerTools = ({ channel, invokeSendUploadMessage, invokeSendMessage, invokeUploadFileMessage, inputEmoji, ...props }) => {
const websocket = useConversationStore((state) => state.websocket)
@ -25,6 +26,7 @@ const ComposerTools = ({ channel, invokeSendUploadMessage, invokeSendMessage, in
<PaymentlinkBtn />
<SnippestBtn />
<ShortlinkBtn />
{/* <Button type='text' className='' icon={<YoutubeOutlined />} size={'middle'} />
<Button type='text' className='' icon={<AudioOutlined />} size={'middle'} /> */}

@ -0,0 +1,24 @@
import { createContext, useEffect, useState } from 'react'
import { Tooltip, Button } from 'antd'
import useUrlStore from '@/stores/UrlStore'
const ShortlinkBtn = ({ type, ...props }) => {
const [openShorturlDrawer] = useUrlStore((state) => [state.openDrawer])
return (
<>
<Tooltip title='短链接'>
{type === 'link' ? (
<Button type={'link'} onClick={() => openShorturlDrawer()}>
短链接
</Button>
) : (
<Button type='text' onClick={() => openShorturlDrawer()} size={'middle'} className='px-1'>
🔗
</Button>
)}
</Tooltip>
</>
)
}
export default ShortlinkBtn

@ -27,6 +27,7 @@ const splitTemplate = (template) => {
// MARKETING
const templateCaterogyText = { 'UTILITY': '跟进', 'MARKETING': '营销' }
const templateCaterogyTipText = { 'UTILITY': '触达率高', 'MARKETING': '' }
const templateCaterogyTipText2 = { 'UTILITY': '', 'MARKETING': '美国(+1)❌' }
const CategoryList = ({ dataSource, handleSendTemplate, valueMapped, onInput, activeInput }) => {
const currentConversation = useConversationStore((state) => state.currentConversation);
@ -60,7 +61,7 @@ const CategoryList = ({ dataSource, handleSendTemplate, valueMapped, onInput, ac
<span key={ele.trim()} className=' text-wrap'>
{ele.replace(/\n+/g, '\n')}
</span>
) : ele.key.includes('free') || ele.key.includes('detail') ? (
) : (ele.key.includes('free') || ele.key.includes('detail') || ele.key.includes('update') || ele.key.includes('info')) ? (
<Input.TextArea
key={`${ele.key}_${i}`}
rows={2}
@ -70,7 +71,8 @@ const CategoryList = ({ dataSource, handleSendTemplate, valueMapped, onInput, ac
className={` w-11/12 `}
size={'small'}
title={ele.key}
placeholder={`${paramsVal[ele.key] || ele.key} 按Tab键跳到下一个空格\n注意: 模板消息无法输入换行`}
// ${paramsVal[ele.key] || ele.key}
placeholder={`按Tab键跳到下一个空格\n注意: 模板消息无法输入换行`}
value={activeInput[tempItem.name]?.[ele.key] || paramsVal[ele.key] || ''}
// onPressEnter={() => handleSendTemplate(tempItem)}
/>
@ -163,10 +165,11 @@ const CategoryList = ({ dataSource, handleSendTemplate, valueMapped, onInput, ac
<span>
{item.components.header?.[0]?.text || (item.displayName)}
<Tag style={{ ...TagColorStyle(item.language.toUpperCase(), true) }} className='ml-1'>
{item.language.toUpperCase()}
{item.language.slice(-2).toUpperCase()}
</Tag>
{/* <Tag style={{...TagColorStyle(item.category.toUpperCase(), true)}}>{templateCaterogyText[item.category]}</Tag> */}
{templateCaterogyTipText[item.category] && <Tag style={{ ...TagColorStyle(item.category.toUpperCase(), true) }}>{templateCaterogyTipText[item.category]}</Tag>}
{/* {templateCaterogyTipText2[item.category] && <Tag style={{ ...TagColorStyle(templateCaterogyTipText2[item.category].toUpperCase(), true) }}>{templateCaterogyTipText2[item.category]}</Tag>} */}
</span>
<Button onClick={() => handleSendTemplate(item)} size={'small'} type='link' key={'send'} icon={<SendOutlined />}>
Send

@ -525,6 +525,7 @@ const NewEmail = () => {
})
// body.externalID = stickToCid
// body.actionID = `${stickToCid}.${msgObj.id}`
body.actionID = `0.${uuid()}`
body.contenttype = isRichText ? 'text/html' : 'text/plain'
try {

@ -155,7 +155,6 @@ function GeneratePayment() {
{ value: 'SGD', label: '新加坡元' },
{ value: 'NZD', label: '新西兰元' },
{ value: 'THB', label: '泰铢' },
{ value: 'MYR', label: '马币' },
]}></Select>
</Form.Item>
}

@ -89,23 +89,12 @@ function Profile() {
label: 'GH 客运(+639454682947)',
},
{
value: '+8618174165365',
label: '国际事业部(+8618174165365)',
},
{
value: 'GH 客服',
label: 'GH 客服(无)',
disabled: true,
value: '+85265210895',
label: 'GH 客运 HK(+85265210895)',
},
{
value: 'CT 事业部',
label: 'CT 事业部(无)',
disabled: true,
},
{
value: '花梨鹰事业部',
label: '花梨鹰事业部(无)',
disabled: true,
value: '+8618174165365',
label: '国际事业部(+8618174165365)',
},
]}
/>

@ -1,6 +1,6 @@
import { Input, Button, Space, Typography } from 'antd'
import { LinkOutlined } from '@ant-design/icons'
import useShortUrlChange from '@/components/Shorturlchange'
import useShortUrlChange from '@/hooks/useShorturlchange'
import { useState } from 'react'
const { Title, Text, Paragraph } = Typography
@ -25,6 +25,7 @@ function ShorturlConversion() {
placeholder="输入需要转换的长链接"
value={longUrl}
onChange={(e) => setLongUrl(e.target.value)}
onPressEnter={handleConvert}
prefix={<LinkOutlined />}
size="large"
/>
@ -47,8 +48,18 @@ function ShorturlConversion() {
} : false}
style={{ fontSize: '17px' }}
>
{shortUrl}
<a
href={shortUrl}
target="_blank"
rel="noopener noreferrer"
style={{ textDecoration: 'underline' }}
>
{shortUrl}
</a>
</Paragraph>
<Text type="secondary" style={{ fontSize: '14px', marginTop: '4px', display: 'block' }}>
说明使用短链接前可点击测试短链接是否可用
</Text>
</div>)}
</Space>
);

@ -41,7 +41,7 @@ function Login() {
corpId: 'ding48bce8fd3957c96b',
success: (res) => {
const { code } = res
navigate('/p/dingding/callback?state=jsapi-auth&authCode=' + code, {
navigate('/p/dingding/callback?origin_url='+originUrl+'&state=jsapi-auth&authCode=' + code, {
replace: true,
})
},

@ -2,7 +2,7 @@ import { Layout, Button } from 'antd';
import MessagesHeader from '@/views/Conversations/Online/MessagesHeader';
import MessagesWrapper from '@/views/Conversations/Online/MessagesWrapper';
import InputComposer from '@/views/Conversations/Online/Input/InputComposer';
import { UnorderedListOutlined, MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons';
import { UnorderedListOutlined, MenuUnfoldOutlined, MenuFoldOutlined, LeftOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import ReplyWrapper from '../Conversations/Online/ReplyWrapper';
@ -14,7 +14,7 @@ function Chat() {
<>
<Layout className='h-full chatwindow-wrapper mobilechat-wrapper' style={{ maxHeight: 'calc(100vh - 20px)', height: 'calc(100vh - 20px)', minWidth: '360px' }}>
<Header className=' px-2 ant-layout-sider-light bg-white ant-card h-auto flex justify-between gap-1 items-center'>
<Button type='text' icon={<MenuFoldOutlined />} onClick={() => navigate('/m/conversation')} className=' rounded-none rounded-l' />
<Button type='text' icon={<LeftOutlined />} onClick={() => navigate('/m/conversation')} className=' rounded-none rounded-l' />
<MessagesHeader />
<Button type='text' icon={<MenuUnfoldOutlined />} onClick={() => navigate('/m/order')} className=' rounded-none rounded-r' />
</Header>

Loading…
Cancel
Save