import React, { useEffect, useState, useRef } from 'react'; import { TouchableOpacity, ActivityIndicator, StyleSheet, View, Platform, Keyboard, Dimensions, Pressable, SafeAreaView, } from 'react-native'; import { GiftedChat, Actions, ActionsProps, InputToolbar, Composer, Avatar, Send, Bubble, Message, MessageImage, InputToolbarProps, ComposerProps, } from 'react-native-gifted-chat'; import { launchImageLibrary } from 'react-native-image-picker'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons'; import DocumentPicker from 'react-native-document-picker'; import { useTheme } from 'styled-components/native'; import { ToastType, useToast, Messages as MessagesController, useSession, useUtils, useLanguage, useConfig } from 'ordering-components/native'; import { Header, TitleHeader, Wrapper, QuickMessageContainer } from './styles'; import { OIcon, OIconButton, OText, OButton } from '../shared'; import { MessagesParams } from '../../types'; import { USER_TYPE } from '../../config/constants'; import SignatureScreen from 'react-native-signature-canvas'; const filterSpecialStatus = ['prepared_in', 'delivered_in', 'delivery_datetime'] const ChatUI = (props: MessagesParams) => { const { type, order, messages, image, message, messagesToShow, sendMessage, setMessage, canRead, setCanRead, handleSend, setImage, readMessages, } = props; const [{ user }] = useSession(); const [{ parseDate, parseTime, optimizeImage, getTimeAgo }] = useUtils(); const [, t] = useLanguage(); const [, { showToast }] = useToast(); const theme = useTheme(); const [{ configs }] = useConfig(); const [messageList, setMessageList] = useState([]) const previousStatus = [1, 2, 5, 6, 10, 11, 12, 15, 16, 17] const chatDisabled = previousStatus.includes(order?.status) const ORDER_STATUS: any = { 0: t('ORDER_STATUS_PENDING', 'Order status pending'), 1: t('ORDERS_COMPLETED', 'Order completed'), 2: t('ORDER_REJECTED', 'Order rejected'), 3: t('ORDER_STATUS_IN_BUSINESS', 'Order status in business'), 4: t('ORDER_READY', 'Order ready'), 5: t('ORDER_REJECTED_RESTAURANT', 'Order rejected by restaurant'), 6: t('ORDER_STATUS_CANCELLEDBYDRIVER', 'Order status cancelled by driver'), 7: t('ORDER_STATUS_ACCEPTEDBYRESTAURANT', 'Order status accepted by restaurant'), 8: t('ORDER_CONFIRMED_ACCEPTED_BY_DRIVER', 'Order confirmed accepted by driver'), 9: t('ORDER_PICKUP_COMPLETED_BY_DRIVER', 'Order pickup completed by driver'), 10: t('ORDER_PICKUP_FAILED_BY_DRIVER', 'Order pickup failed by driver'), 11: t('ORDER_DELIVERY_COMPLETED_BY_DRIVER', 'Order delivery completed by driver'), 12: t('ORDER_DELIVERY_FAILED_BY_DRIVER', 'Order delivery failed by driver'), 13: t('PREORDER', 'Preorder'), 14: t('ORDER_NOT_READY', 'Order not ready'), 15: t('ORDER_PICKEDUP_COMPLETED_BY_CUSTOMER', 'Order picked up completed by customer'), 16: t('ORDER_STATUS_CANCELLED_BY_CUSTOMER', 'Order status cancelled by customer'), 17: t('ORDER_NOT_PICKEDUP_BY_CUSTOMER', 'Order not picked up by customer'), 18: t('ORDER_DRIVER_ALMOST_ARRIVED_BUSINESS', 'Order driver almost arrived to business'), 19: t('ORDER_DRIVER_ALMOST_ARRIVED_CUSTOMER', 'Order driver almost arrived to customer'), 20: t('ORDER_CUSTOMER_ALMOST_ARRIVED_BUSINESS', 'Order customer almost arrived to business'), 21: t('ORDER_CUSTOMER_ARRIVED_BUSINESS', 'Order customer arrived to business'), 22: t('ORDER_LOOKING_FOR_DRIVER', 'Order looking for driver'), 23: t('ORDER_DRIVER_ON_WAY', 'Driver on way'), 24: t('ORDER_DRIVER_WAITING_FOR_ORDER', 'Driver waiting for order'), 25: t('ORDER_ACCEPTED_BY_DRIVER_COMPANY', 'Accepted by driver company'), 26: t('ORDER_DRIVER_ARRIVED_CUSTOMER', 'Driver arrived to customer') } const getLogisticTag = (status: any) => { const keyList: any = { 0: t('PENDING', 'Pending'), 1: t('IN_PROGRESS', 'In progress'), 2: t('IN_QUEUE', 'In queue'), 3: t('EXPIRED', 'Expired'), 4: t('RESOLVED', 'Resolved'), } return keyList[status] ? keyList[status] : t('UNKNOWN', 'Unknown') } const storeMessageList: any = [ { key: 'store_message_1', text: t('STORE_MESSAGE_1', 'store_message_1') }, { key: 'store_message_2', text: t('STORE_MESSAGE_2', 'store_message_2') }, { key: 'store_message_3', text: t('STORE_MESSAGE_3', 'store_message_3') }, { key: 'store_message_4', text: t('STORE_MESSAGE_4', 'store_message_4') } ] const driverMessageList: any = [ { key: 'driver_message_1', text: t('DRIVER_MESSAGE_1', 'driver_message_1') }, { key: 'driver_message_2', text: t('DRIVER_MESSAGE_2', 'driver_message_2') }, { key: 'driver_message_3', text: t('DRIVER_MESSAGE_3', 'driver_message_3') }, { key: 'driver_message_4', text: t('DRIVER_MESSAGE_4', 'driver_message_4') } ] const adminMessageList: any = [ { key: 'message_1', text: t('ADMIN_MESSAGE_1', 'admin_message_1') }, { key: 'message_2', text: t('ADMIN_MESSAGE_2', 'admin_message_2') }, { key: 'message_3', text: t('ADMIN_MESSAGE_3', 'admin_message_3') }, { key: 'message_4', text: t('ADMIN_MESSAGE_4', 'admin_message_4') } ] const handleClickQuickMessage = (text: string) => { setMessage && setMessage(`${message}${text}`) } const { bottom } = useSafeAreaInsets(); const [formattedMessages, setFormattedMessages] = useState>([]); const [isKeyboardShow, setIsKeyboardShow] = useState(false); const windowWidth = Dimensions.get('window').width < Dimensions.get('window').height ? parseInt(parseFloat(String(Dimensions.get('window').width)).toFixed(0)) : parseInt( parseFloat(String(Dimensions.get('window').height)).toFixed(0), ); const [orientation, setOrientation] = useState( Dimensions.get('window').width < Dimensions.get('window').height ? 'Portrait' : 'Landscape', ); const [isShowSignaturePad, setIsShowSignaturePad] = useState(false) // Events Dimensions.addEventListener('change', ({ window: { width, height } }) => { if (width < height) { setOrientation('Portrait'); } else { setOrientation('Landscape'); } }); const styles = StyleSheet.create({ containerActions: { width: 44, height: 44, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', marginBottom: 0, }, containerSend: { width: 44, height: 44, borderRadius: 7.6, backgroundColor: theme.colors.transparent, alignItems: 'center', justifyContent: 'center', marginHorizontal: 0, }, bubbleText: { color: theme.colors.black, fontFamily: 'Poppins', fontStyle: 'normal', fontWeight: 'normal', fontSize: 12, }, toolbarStyle: { padding: Platform.OS === 'ios' && isKeyboardShow ? 0 : 10, flexDirection: 'column-reverse' }, accessoryIcon: { height: 32, width: 32, minWidth: 32, borderRadius: 7.6, resizeMode: 'stretch', }, shadow: { height: 33, width: 33, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 3, borderRadius: 7.6, shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 2, backgroundColor: theme.colors.clear, }, avatar: { height: 17, width: 17, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 3, borderRadius: 7.6, shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 2, backgroundColor: theme.colors.white, marginLeft: 4, }, avatarIcon: { height: 16, width: 16, borderRadius: 50, resizeMode: 'stretch', }, firstMessage: { justifyContent: 'center', alignItems: 'center', backgroundColor: theme.colors.inputChat, paddingVertical: 10, borderRadius: 7.6, }, firstMessageText: { color: theme.colors.textGray, fontFamily: 'Poppins', fontStyle: 'normal', fontWeight: 'normal', fontSize: 13, }, timeText: { color: theme.colors.white, fontFamily: 'Poppins', fontStyle: 'normal', fontWeight: 'normal', fontSize: 10, paddingLeft: 12, paddingRight: 5, }, editButton: { borderRadius: 50, backgroundColor: '#E9ECEF', marginRight: 10, height: 24, borderWidth: 1, paddingLeft: 0, paddingRight: 0 } }); const [file, setFile] = useState({ uri: '', type: '', name: '', size: 0, }); const onChangeMessage = (val: string) => { setMessage && setMessage(val); }; const removeImage = () => { setImage && setImage(null); setFile && setFile({ ...file, uri: '', type: '', name: '', size: 0 }); }; const attachIcons: any = { jpeg: theme.images.general.imageFile, png: theme.images.general.imageFile, pdf: theme.images.general.pdfFile, doc: theme.images.general.docFile, }; const handleDocumentPicker = async () => { try { const res: any = await DocumentPicker.pick({ type: [DocumentPicker.types.allFiles], }); const { uri, type, name, size } = res; const [, typeFile] = type.split('/'); setFile({ ...file, uri, type: typeFile, name, size }); } catch (err: any) { if (DocumentPicker.isCancel(err)) { // User cancelled the picker, exit any dialogs or menus and move on } else { throw err; } } }; const handleImagePicker = () => { launchImageLibrary( { mediaType: 'photo', maxHeight: 2048, maxWidth: 2048, includeBase64: true, quality: 1 }, (response: any) => { const image = response.assets?.[0]; if (!image) return if (image.didCancel) { console.log('User cancelled image picker'); } else if (image.errorMessage) { console.log('ImagePicker Error: ', image.errorMessage); showToast(ToastType.Error, image.errorMessage); } else { if (image.uri) { const url = `data:${image.type};base64,${image.base64}`; setImage && setImage(url); } else { showToast(ToastType.Error, t('IMAGE_NOT_FOUND', 'Image not found')); } } }, ); }; const handleFilter = (value: any, reader: string) => { const isAdmin = user?.level === 0; const readersCount = [ ...new Map(Object.entries(canRead)).values(), ].filter((read: boolean) => read).length; const minReaders = isAdmin ? 2 : order?.driver ? 3 : 4; if (minReaders === 4) { return; } if (readersCount > minReaders || value[reader]) { setCanRead({ ...canRead, ...value }); } else if (reader !== 'business' && (isAdmin || user?.level === 4)) { setCanRead({ ...canRead, ...value, business: true }); } else if (reader !== 'customer') { setCanRead({ ...canRead, ...value, customer: true }); } else { setCanRead({ ...canRead, ...value, driver: true }); } }; const onSubmit = (values: any) => { handleSend && handleSend(); setImage && setImage(null); setFile && setFile({ ...file, uri: '', type: '', name: '', size: 0 }); setMessage && setMessage(''); setIsShowSignaturePad(false) }; const messageConsole = (message: any) => { return ( { message.change?.attribute === 'driver_group_id' ? message.change.old ? t('ORDER_ATTRIBUTE_CHANGED_FROM_TO') .replace('_attribute_', t(message.change?.attribute.toUpperCase()).toLowerCase()) .replace('_from_', message.change.old) .replace('_to_', message.change.new) : t('ORDER_ATTRIBUTE_CHANGED_TO') .replace('_attribute_', t(message.change?.attribute.toUpperCase()).toLowerCase()) .replace('_to_', message.change.new) : message.change?.attribute !== 'driver_id' ? `${t('ORDER', 'Order')} ${t(message.change.attribute.toUpperCase(), message.change.attribute.replace('_', ' '))} ${t('CHANGED_FROM', 'Changed from')} ${filterSpecialStatus.includes(message.change.attribute) ? `${message.change.old === null ? '0' : message.change.old} ${t('TO', 'to')} ${message.change.new} ${t('MINUTES', 'Minutes')}` : `${message.change?.attribute !== 'logistic_status' ? message.change.old !== null && t(ORDER_STATUS[parseInt(message.change.old, 10)]) : message.change.old !== null && getLogisticTag(message.change.old)} ${t('TO', 'to')} ${message.change?.attribute !== 'logistic_status' ? t(ORDER_STATUS[parseInt(message.change.new, 10)]) : getLogisticTag(message.change.new)}` }` : message.change.new ? `${message.driver?.name} ${message.driver?.lastname !== null ? message.driver.lastname : ''} ${t('WAS_ASSIGNED_AS_DRIVER', 'Was assigned as driver')} ${message.comment ? message.comment.length : ''}` : `${t('DRIVER_UNASSIGNED', 'Driver unassigned')}` } {parseTime(message?.created_at, { outputFormat: 'hh:mma' })} ); }; const AvatarsConsole = () => { return ( {order?.driver && ( )} ); }; useEffect(() => { if (user.level === 0) setMessageList(adminMessageList) else if (user.level === 2) setMessageList(storeMessageList) else if (user.level === 4) setMessageList(driverMessageList) else setMessageList([]) }, [user]) useEffect(() => { let newMessages: Array = []; const console = ( {t('ORDER_PLACED_FOR', 'Order placed for')}{' '} {parseDate(order?.created_at)} {t('VIA', 'Via')}{' '} {order?.app_id ? t(order?.app_id.toUpperCase(), order?.app_id) : t('OTHER', 'Other')} {`${t('SENT_TO', 'Sent to')}:`} ); const firstMessage = { _id: 0, type: 0, text: console, createdAt: parseDate(order?.created_at, { outputFormat: 'YYYY-MM-DD HH:mm:ss' }), system: true, }; messages?.messages.map((message: any) => { let newMessage; if ( parseInt(message.order_id) === order?.id && message.type !== 0 && (messagesToShow?.messages?.length || message?.can_see?.includes('2') || message?.can_see?.includes('0') || (message?.can_see?.includes('4') && type === USER_TYPE.DRIVER)) ) { newMessage = { _id: message.id, text: message.type === 1 ? messageConsole(message) : message.comment, createdAt: message.type !== 0 && parseDate(message?.created_at, { outputFormat: 'YYYY-MM-DD HH:mm:ss' }), image: message.source, type: message.type, system: message.type === 1, user: { _id: message.author?.id, name: message.author?.name, can_see: message?.can_see, level: message.author?.level, avatar: message.author?.id !== user?.id && type === USER_TYPE.DRIVER ? order?.driver?.photo : order?.business?.logo, }, }; } if (message.type === 0) { newMessage = firstMessage; } if (newMessage) { newMessages = [...newMessages, newMessage]; } }); let _arrayMessages = [...newMessages.reverse()] if (configs?.order_logbook_enabled?.value === '0' && user?.level === 4) { _arrayMessages = _arrayMessages.filter(msg => msg.type !== 1 && msg.type !== 0) } setFormattedMessages(_arrayMessages); }, [messages?.messages?.length]); useEffect(() => { const keyboardDidShowListener = Keyboard.addListener( 'keyboardDidShow', () => { setIsKeyboardShow(true); }, ); const keyboardDidHideListener = Keyboard.addListener( 'keyboardDidHide', () => { setIsKeyboardShow(false); }, ); return () => { keyboardDidShowListener.remove(); keyboardDidHideListener.remove(); }; }, []); useEffect(() => { if (!order?.driver) { setCanRead({ ...canRead, driver: false }); } else { setCanRead({ ...canRead, driver: true }); } }, [order?.driver]); const RenderActionsAttach = (props: any) => { return ( ( <> {file?.type.length > 0 && ( removeImage()}> )} )} /> ); }; const renderAccessory = () => ( !chatDisabled &&
{user?.level !== 2 && ( handleFilter({ business: !canRead?.business }, 'business') }> {order?.business?.name} {t('BUSINESS', 'Business')} )} handleFilter({ customer: !canRead?.customer }, 'customer') }> {order?.customer?.name} {t('CUSTOMER', 'Customer')} {order?.driver && user?.level !== 4 && ( handleFilter({ driver: !canRead?.driver }, 'driver') }> {order?.driver?.name} {t('DRIVER', 'Driver')} )}
{messageList.map((quickMessage: any, i: number) => ( handleClickQuickMessage(message?.length > 0 ? ' ' + quickMessage.text : quickMessage.text)} /> ))}
); const userRoles: any = { 0: t('ADMINISTRATOR', 'Administrator'), 1: t('CITY_MANAGER', 'City manager'), 2: t('BUSINESS_OWNER', 'Business owner'), 3: t('CUSTOMER', 'Customer'), 4: t('DRIVER', 'Driver'), 5: t('DRIVER_MANAGER', 'Driver manager'), }; const renderInputToolbar = (props: any) => ( renderAccessory()} /> ); const renderComposer = (props: any) => ( chatDisabled ? ( {t('NOT_SEND_MESSAGES', 'You can\'t send messages because the order has ended')} ) : ( { setImage && setImage(null); setIsShowSignaturePad(!isShowSignaturePad); }}> {!file.type && ( ( <> {!file?.type && ( <> {!!image && !isShowSignaturePad && ( removeImage()}> )} )} )} /> )} ) ); const renderSend = (props: any) => ( !chatDisabled && ); const renderBubble = (props: any) => ( ); const renderMessageImage = (props: any) => ; const renderScrollToBottomComponent = () => ( ); const customMessage = (props: any) => { return ( ); }; const renderAvatar = (props: any) => { const isMine = user?.id === props?.currentMessage?.user?._id; return ( {`${t('SENT_TO', 'Sent to')}:`} {props?.currentMessage?.user?.can_see?.includes('2') && user?.level !== 2 && ( )} {props?.currentMessage?.user?.can_see?.includes('3') && ( )} {props?.currentMessage?.user?.can_see?.includes('4') && user?.level !== 4 && ( )} ); }; const renderTime = (props: any) => ( <> {getTimeAgo(props.currentMessage.createdAt)} ); const renderNameMessage = (props: any) => { const isMine = user?.id === props?.currentMessage?.user?._id; return ( {props?.currentMessage?.user?.name} ( {userRoles[props?.currentMessage?.user?.level]}) ); }; const imgHeight = 120; const signatureStyle = ` .m-signature-pad { box-shadow: none; border: none; } .m-signature-pad--body { border: none; } .m-signature-pad--footer { display: none; margin: 0px; } body,html { height: ${imgHeight}px; border-radius: 7.6px; } .m-signature-pad--footer .button { background-color: red; color: #FFF; } `; const signatureRef = useRef(); const handleClear = () => { setImage && setImage(null); signatureRef.current.clearSignature(); }; const handleEnd = () => { signatureRef.current.readSignature(); }; const handleOK = (signature: any) => { setImage && setImage(signature); }; const renderChatFooter = () => { return ( ) } return ( customMessage(props)} renderBubble={renderBubble} renderMessageImage={renderMessageImage} scrollToBottomComponent={() => renderScrollToBottomComponent()} messagesContainerStyle={{ paddingBottom: 25 }} showUserAvatar={true} bottomOffset={bottom} minInputToolbarHeight={145} isLoadingEarlier={messages?.loading} renderLoading={() => ( )} keyboardShouldPersistTaps="handled" /> ); }; export const Chat = (props: MessagesParams) => { const MessagesProps = { ...props, UIComponent: ChatUI, }; return ; };