Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Search, create and edit contacts #33591

Merged
merged 7 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/early-oranges-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/i18n': minor
'@rocket.chat/meteor': minor
---

Allows agents to add multiple emails and phone numbers to a contact
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import React from 'react';
import AutoCompleteAgent from '../../components/AutoCompleteAgent';
import { useHasLicenseModule } from '../../hooks/useHasLicenseModule';

export const ContactManager = ({ value: userId, handler }) => {
type ContactManagerInputProps = {
value: string;
handler: (currentValue: string) => void;
};

export const ContactManagerInput = ({ value: userId, handler }: ContactManagerInputProps) => {
const t = useTranslation();
const hasLicense = useHasLicenseModule('livechat-enterprise');

Expand All @@ -23,4 +28,4 @@ export const ContactManager = ({ value: userId, handler }) => {
);
};

export default ContactManager;
export default ContactManagerInput;
4 changes: 2 additions & 2 deletions apps/meteor/client/views/omnichannel/additionalForms.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import BusinessHoursMultiple from '../../omnichannel/additionalForms/BusinessHoursMultiple';
import ContactManager from '../../omnichannel/additionalForms/ContactManager';
import ContactManagerInput from '../../omnichannel/additionalForms/ContactManagerInput';
import CurrentChatTags from '../../omnichannel/additionalForms/CurrentChatTags';
import CustomFieldsAdditionalForm from '../../omnichannel/additionalForms/CustomFieldsAdditionalForm';
import DepartmentBusinessHours from '../../omnichannel/additionalForms/DepartmentBusinessHours';
Expand All @@ -20,7 +20,7 @@ export {
EeTextAreaInput,
BusinessHoursMultiple,
EeTextInput,
ContactManager,
ContactManagerInput,
CurrentChatTags,
DepartmentBusinessHours,
DepartmentForwarding,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
import type { ILivechatContact, Serialized } from '@rocket.chat/core-typings';
import { Box, IconButton, Tabs, TabsItem } from '@rocket.chat/fuselage';
import { UserAvatar } from '@rocket.chat/ui-avatar';
import type { RouteName } from '@rocket.chat/ui-contexts';
import { useTranslation, useEndpoint, usePermission, useRouter, useRouteParameter } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import React from 'react';

import { ContextualbarHeader, ContextualbarIcon, ContextualbarTitle, ContextualbarClose } from '../../../components/Contextualbar';
import { useFormatDate } from '../../../hooks/useFormatDate';
import { FormSkeleton } from '../directory/components/FormSkeleton';
import { useContactRoute } from '../hooks/useContactRoute';
import ContactInfoChannels from './tabs/ContactInfoChannels';
import ContactInfoDetails from './tabs/ContactInfoDetails';
import ContactInfoHistory from './tabs/ContactInfoHistory';
import { ContextualbarHeader, ContextualbarIcon, ContextualbarTitle, ContextualbarClose } from '../../../../components/Contextualbar';
import { useFormatDate } from '../../../../hooks/useFormatDate';
import { useContactRoute } from '../../hooks/useContactRoute';
import ContactInfoChannels from '../tabs/ContactInfoChannels';
import ContactInfoDetails from '../tabs/ContactInfoDetails';
import ContactInfoHistory from '../tabs/ContactInfoHistory';

type ContactInfoProps = {
id: string;
contact: Serialized<ILivechatContact>;
onClose: () => void;
rid?: string;
route?: RouteName;
};

const ContactInfo = ({ id: contactId, onClose }: ContactInfoProps) => {
const ContactInfo = ({ contact, onClose }: ContactInfoProps) => {
const t = useTranslation();

const { getRouteName } = useRouter();
Expand All @@ -36,42 +33,18 @@ const ContactInfo = ({ id: contactId, onClose }: ContactInfoProps) => {
const getCustomFields = useEndpoint('GET', '/v1/livechat/custom-fields');
const { data: { customFields } = {} } = useQuery(['/v1/livechat/custom-fields'], () => getCustomFields());

const getContact = useEndpoint('GET', '/v1/omnichannel/contact');
const {
data: { contact } = {},
isInitialLoading,
isError,
} = useQuery(['/v1/omnichannel/contact', contactId], () => getContact({ contactId }), {
enabled: canViewCustomFields && !!contactId,
});

if (isInitialLoading) {
return (
<Box pi={24}>
<FormSkeleton />
</Box>
);
}

if (isError || !contact) {
return <Box mbs={16}>{t('Contact_not_found')}</Box>;
}

const { username, visitorEmails, phone, ts, livechatData, lastChat, contactManager } = contact;
const { name, emails, phones, createdAt, lastChat, contactManager, customFields: userCustomFields } = contact;

const showContactHistory = (currentRouteName === 'live' || currentRouteName === 'omnichannel-directory') && lastChat;

const [{ phoneNumber = '' }] = phone ?? [{}];
const [{ address: email = '' }] = visitorEmails ?? [{}];

const checkIsVisibleAndScopeVisitor = (key: string) => {
const field = customFields?.find(({ _id }) => _id === key);
return field?.visibility === 'visible' && field?.scope === 'visitor';
};

// Serialized does not like unknown :(
const customFieldEntries = canViewCustomFields
? Object.entries((livechatData ?? {}) as unknown as Record<string, string>).filter(
? Object.entries((userCustomFields ?? {}) as unknown as Record<string, string>).filter(
([key, value]) => checkIsVisibleAndScopeVisitor(key) && value,
)
: [];
Expand All @@ -84,12 +57,12 @@ const ContactInfo = ({ id: contactId, onClose }: ContactInfoProps) => {
<ContextualbarClose onClick={onClose} />
</ContextualbarHeader>
<Box display='flex' pi={24}>
{username && (
{name && (
<Box width='100%' pb={16} display='flex' alignItems='center' justifyContent='space-between'>
<Box display='flex'>
<UserAvatar size='x40' title={username} username={username} />
<UserAvatar size='x40' title={name} username={name} />
<Box mis={16} display='flex' flexDirection='column'>
<Box fontScale='h4'>{username}</Box>
<Box fontScale='h4'>{name}</Box>
{lastChat && <Box fontScale='c1'>{`${t('Last_Chat')}: ${formatDate(lastChat.ts)}`}</Box>}
</Box>
</Box>
Expand Down Expand Up @@ -118,10 +91,10 @@ const ContactInfo = ({ id: contactId, onClose }: ContactInfoProps) => {
</Tabs>
{context === 'details' && (
<ContactInfoDetails
ts={ts}
createdAt={createdAt}
contactManager={contactManager}
phoneNumber={phoneNumber}
email={email}
phones={phones?.map(({ phoneNumber }) => phoneNumber)}
emails={emails?.map(({ address }) => address)}
customFieldEntries={customFieldEntries}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Box } from '@rocket.chat/fuselage';
import { useEndpoint, usePermission, useTranslation } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import React from 'react';

import { ContextualbarSkeleton } from '../../../../components/Contextualbar';
import ContactInfo from './ContactInfo';

type ContactInfoWithDataProps = {
id: string;
onClose: () => void;
};

const ContactInfoWithData = ({ id: contactId, onClose }: ContactInfoWithDataProps) => {
const t = useTranslation();
const canViewCustomFields = usePermission('view-livechat-room-customfields');

const getContact = useEndpoint('GET', '/v1/omnichannel/contacts.get');
const { data, isInitialLoading, isError } = useQuery(['getContactById', contactId], () => getContact({ contactId }), {
enabled: canViewCustomFields && !!contactId,
});

if (isInitialLoading) {
return <ContextualbarSkeleton />;
}

if (isError || !data?.contact) {
return <Box mbs={16}>{t('Contact_not_found')}</Box>;
}

return <ContactInfo contact={data?.contact} onClose={onClose} />;
};

export default ContactInfoWithData;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ContactInfoWithData';
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ const ContactInfoRouter = () => {
};

const {
v: { _id },
v: { contactId },
} = room;

if (context === 'edit') {
return <ContactEditWithData id={_id} onClose={closeTab} onCancel={handleCloseEdit} />;
return <ContactEditWithData id={contactId || ''} onClose={closeTab} onCancel={handleCloseEdit} />;
}

return <ContactInfo id={_id} onClose={closeTab} />;
return <ContactInfo id={contactId || ''} onClose={closeTab} />;
};

export default ContactInfoRouter;
Loading
Loading