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

Chore: Convert to TS omnichannel/agent #25511

Merged
merged 10 commits into from
Jun 1, 2022
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
2 changes: 1 addition & 1 deletion apps/meteor/client/components/GenericModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type GenericModalProps = RequiredModalProps & {
icon?: ComponentProps<typeof Icon>['name'] | ReactElement | null;
confirmDisabled?: boolean;
onCancel?: () => void;
onClose: () => void;
onClose?: () => void;
onConfirm: () => void;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ const query = (
selector: string;
} => ({ selector: JSON.stringify({ name: term }) });

type RoomAutoCompleteProps = Omit<ComponentProps<typeof AutoComplete>, 'value' | 'filter'> & {
value: any;
type RoomAutoCompleteProps<T> = Omit<ComponentProps<typeof AutoComplete>, 'value' | 'filter' | 'onChange'> & {
value: T;
onChange: (value: TemplateStringsArray) => void;
};

/* @deprecated */
const RoomAutoComplete = (props: RoomAutoCompleteProps): ReactElement => {
const RoomAutoComplete = <T,>(props: RoomAutoCompleteProps<T>): ReactElement => {
const [filter, setFilter] = useState('');
const { value: data } = useEndpointData(
'rooms.autocomplete.channelAndPrivate',
Expand All @@ -33,7 +34,8 @@ const RoomAutoComplete = (props: RoomAutoCompleteProps): ReactElement => {

return (
<AutoComplete
{...props}
value={props.value as any}
onChange={props.onChange as any}
filter={filter}
setFilter={setFilter}
renderSelected={({ value, label }): ReactElement => (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AutoComplete, Option, Box, Chip, Options } from '@rocket.chat/fuselage';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import React, { ComponentProps, memo, MouseEventHandler, ReactElement, useMemo, useState } from 'react';
import React, { ComponentProps, memo, ReactElement, useMemo, useState } from 'react';

import { useEndpointData } from '../../hooks/useEndpointData';
import UserAvatar from '../avatar/UserAvatar';
Expand All @@ -12,12 +12,12 @@ const query = (
selector: string;
} => ({ selector: JSON.stringify({ term, conditions }) });

type UserAutoCompleteProps = Omit<ComponentProps<typeof AutoComplete>, 'value' | 'filter'> &
Omit<ComponentProps<typeof Option>, 'value'> & {
type UserAutoCompleteProps = Omit<ComponentProps<typeof AutoComplete>, 'value' | 'filter' | 'onChange'> &
Omit<ComponentProps<typeof Option>, 'value' | 'onChange'> & {
conditions?: { [key: string]: unknown };
onChange?: MouseEventHandler<HTMLButtonElement>;
value: any;
filter?: string;
value: string;
onChange?: (value: string) => void;
};

const UserAutoComplete = ({ value, ...props }: UserAutoCompleteProps): ReactElement => {
Expand All @@ -34,8 +34,8 @@ const UserAutoComplete = ({ value, ...props }: UserAutoCompleteProps): ReactElem

return (
<AutoComplete
{...props}
value={value}
value={value as any}
onChange={props.onChange as any}
filter={filter}
setFilter={setFilter}
renderSelected={({ value, label }): ReactElement => {
Expand All @@ -44,7 +44,7 @@ const UserAutoComplete = ({ value, ...props }: UserAutoCompleteProps): ReactElem
}

return (
<Chip height='x20' value={value} onClick={props.onChange} mie='x4'>
<Chip height='x20' value={value} onClick={(_e: any): void => props.onChange?.(value)} mie='x4'>
<UserAvatar size='x20' username={value} />
<Box verticalAlign='middle' is='span' margin='none' mi='x4'>
{label}
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/client/components/avatar/UserAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type UserAvatarProps = Omit<BaseAvatarProps, 'url' | 'title'> & {
username: string;
etag?: string;
url?: string;
title?: string;
};

const UserAvatar: FC<UserAvatarProps> = ({ username, etag, ...rest }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import UsersInRoleTable from './UsersInRoleTable';
const UsersInRolePage = ({ role }: { role: IRole }): ReactElement => {
const t = useTranslation();
const reload = useRef<() => void>(() => undefined);
const [user, setUser] = useState<string | undefined>('');
const [rid, setRid] = useState<string>();
const [user, setUser] = useState<string>('');
const [rid, setRid] = useState<string>('');
const [userError, setUserError] = useState<string>();
const dispatchToastMessage = useToastMessageDispatch();

Expand All @@ -36,10 +36,10 @@ const UsersInRolePage = ({ role }: { role: IRole }): ReactElement => {
try {
await addUser({ roleId: _id, username: user, roomId: rid });
dispatchToastMessage({ type: 'success', message: t('User_added') });
setUser(undefined);
setUser('');
reload.current?.();
} catch (error) {
dispatchToastMessage({ type: 'error', message: String(error) });
dispatchToastMessage({ type: 'error', message: error });
}
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { Button, Box, Field } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { useState } from 'react';
import React, { useState, FC } from 'react';

import UserAutoComplete from '../../../components/UserAutoComplete';
import { useEndpointAction } from '../../../hooks/useEndpointAction';

function AddAgent({ reload, ...props }) {
type AddAgentProps = {
reload: () => void;
pi?: 'x24';
};

const AddAgent: FC<AddAgentProps> = ({ reload, ...props }) => {
const t = useTranslation();
const [username, setUsername] = useState();
const [username, setUsername] = useState('');

const saveAction = useEndpointAction('POST', 'livechat/users/agent', { username });

Expand All @@ -21,21 +26,21 @@ function AddAgent({ reload, ...props }) {
return;
}
reload();
setUsername();
setUsername(username);
});
return (
<Box display='flex' alignItems='center' {...props}>
<Field>
<Field.Label>{t('Username')}</Field.Label>
<Field.Row>
<UserAutoComplete value={username} onChange={setUsername} />
<UserAutoComplete value={username} onChange={(username: string): void => setUsername(username)} />
<Button disabled={!username} onClick={handleSave} mis='x8' primary>
{t('Add')}
</Button>
</Field.Row>
</Field>
</Box>
);
}
};

export default AddAgent;
11 changes: 5 additions & 6 deletions apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IUser } from '@rocket.chat/core-typings';
import type { ILivechatAgent, ILivechatDepartment, ILivechatDepartmentAgents } from '@rocket.chat/core-typings';
import { Field, TextInput, Button, Margins, Box, MultiSelect, Icon, Select } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useRoute, useSetting, useMethod, useTranslation } from '@rocket.chat/ui-contexts';
Expand All @@ -15,14 +15,13 @@ import { formsSubscription } from '../additionalForms';
// Department

type dataType = {
status: string;
user: IUser;
user: Pick<ILivechatAgent, '_id' | 'username' | 'name' | 'status' | 'statusLivechat' | 'emails' | 'livechat'>;
};

type AgentEditProps = {
data: dataType;
userDepartments: { departments: Array<{ departmentId: string }> };
availableDepartments: { departments: Array<{ _id: string; name?: string }> };
userDepartments: { departments: Pick<ILivechatDepartmentAgents, 'departmentId'>[] };
availableDepartments: { departments: Pick<ILivechatDepartment, '_id' | 'name'>[] };
uid: string;
reset: () => void;
};
Expand All @@ -44,7 +43,7 @@ const AgentEdit: FC<AgentEditProps> = ({ data, userDepartments, availableDepartm
[availableDepartments],
);
const initialDepartmentValue = useMemo(
() => (userDepartments?.departments ? userDepartments.departments.map(({ departmentId }) => departmentId) : []),
() => (userDepartments.departments ? userDepartments.departments.map(({ departmentId }) => departmentId) : []),
[userDepartments],
);
const eeForms = useSubscription(formsSubscription);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { Box } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React from 'react';
import React, { FC } from 'react';

import { FormSkeleton } from '../../../components/Skeleton';
import { AsyncStatePhase } from '../../../hooks/useAsyncState';
import { useEndpointData } from '../../../hooks/useEndpointData';
import AgentEdit from './AgentEdit';

function AgentEditWithData({ uid, reload }) {
type AgentEditWithDataProps = {
uid: string;
reload: () => void;
};

const AgentEditWithData: FC<AgentEditWithDataProps> = ({ uid, reload }) => {
const t = useTranslation();
const { value: data, phase: state, error } = useEndpointData(`livechat/users/agent/${uid}`);
const {
Expand All @@ -21,7 +26,11 @@ function AgentEditWithData({ uid, reload }) {
error: availableDepartmentsError,
} = useEndpointData('livechat/department');

if ([state, availableDepartmentsState, userDepartmentsState].includes(AsyncStatePhase.LOADING)) {
if (
[state, availableDepartmentsState, userDepartmentsState].includes(AsyncStatePhase.LOADING) ||
!userDepartments ||
!availableDepartments
) {
return <FormSkeleton />;
}

Expand All @@ -30,6 +39,6 @@ function AgentEditWithData({ uid, reload }) {
}

return <AgentEdit uid={uid} data={data} userDepartments={userDepartments} availableDepartments={availableDepartments} reset={reload} />;
}
};

export default AgentEditWithData;
Original file line number Diff line number Diff line change
@@ -1,50 +1,52 @@
import { Box, Margins, ButtonGroup } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { memo } from 'react';
import { useSubscription } from 'use-subscription';
import React, { HTMLAttributes, memo } from 'react';

import { FormSkeleton } from '../../../components/Skeleton';
import { UserStatus } from '../../../components/UserStatus';
import VerticalBar from '../../../components/VerticalBar';
import { AsyncStatePhase } from '../../../hooks/useAsyncState';
import { useEndpointData } from '../../../hooks/useEndpointData';
import UserInfo from '../../room/contextualBar/UserInfo';
import { formsSubscription } from '../additionalForms';
import { useFormsSubscription } from '../additionalForms';
import AgentInfoAction from './AgentInfoAction';

export const AgentInfo = memo(function AgentInfo({ uid, children, ...props }) {
type AgentInfoProps = {
uid: string;
} & Omit<HTMLAttributes<HTMLElement>, 'is'>;

export const AgentInfo = memo<AgentInfoProps>(function AgentInfo({ uid, children, ...props }) {
const t = useTranslation();
const { value: data, phase: state, error } = useEndpointData(`livechat/users/agent/${uid}`);
const eeForms = useSubscription(formsSubscription);
const result = useEndpointData(`livechat/users/agent/${uid}`);

const { useMaxChatsPerAgentDisplay = () => {} } = eeForms;
const { useMaxChatsPerAgentDisplay } = useFormsSubscription();

const MaxChats = useMaxChatsPerAgentDisplay();
const MaxChats = useMaxChatsPerAgentDisplay?.();

if (state === AsyncStatePhase.LOADING) {
if (result.phase === AsyncStatePhase.LOADING) {
return <FormSkeleton />;
}

if (error || !data || !data.user) {
if (result.phase === AsyncStatePhase.REJECTED) {
return <Box mbs='x16'>{t('User_not_found')}</Box>;
}

const { user } = data;
const { user } = result.value;
const { username, statusLivechat, status: userStatus } = user;

return (
<VerticalBar.ScrollableContent p='x24' {...props}>
<Box alignSelf='center'>
<UserInfo.Avatar size={'x332'} username={username} />
<UserInfo.Avatar size='x332' username={username} />
</Box>

<ButtonGroup mi='neg-x4' flexShrink={0} flexWrap='nowrap' withTruncatedText justifyContent='center' flexShrink={0}>
<ButtonGroup mi='neg-x4' flexShrink={0} flexWrap='nowrap' withTruncatedText justifyContent='center'>
{children}
</ButtonGroup>

<Margins block='x4'>
<Box mb='x2'>
<UserInfo.Username name={username} status={<UserStatus status={userStatus} />} />
<UserInfo.Username username={username} status={<UserStatus status={userStatus} />} />
</Box>

{statusLivechat && (
Expand Down
11 changes: 0 additions & 11 deletions apps/meteor/client/views/omnichannel/agents/AgentInfoAction.js

This file was deleted.

17 changes: 17 additions & 0 deletions apps/meteor/client/views/omnichannel/agents/AgentInfoAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Button, Icon, IconProps } from '@rocket.chat/fuselage';
import React, { FC, HtmlHTMLAttributes } from 'react';

type AgentInfoActionProps = {
icon: IconProps['name'];
label?: string;
title?: string;
} & Omit<HtmlHTMLAttributes<HTMLElement>, 'is'>;

const AgentInfoAction: FC<AgentInfoActionProps> = ({ icon, label, ...props }) => (
<Button title={label} {...props} mi='x4'>
<Icon name={icon} size='x20' mie='x4' />
{label}
</Button>
);

export default AgentInfoAction;
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useToastMessageDispatch, useRouteParameter, useRoute, useTranslation } from '@rocket.chat/ui-contexts';
import React from 'react';
import React, { VFC } from 'react';

import GenericModal from '../../../components/GenericModal';
import { useEndpointAction } from '../../../hooks/useEndpointAction';
import AgentInfo from './AgentInfo';

function AgentInfoActions({ reload }) {
const AgentInfoActions: VFC<{
reload: () => void;
}> = ({ reload }) => {
const t = useTranslation();
const _id = useRouteParameter('id');
const agentsRoute = useRoute('omnichannel-agents');
Expand All @@ -24,30 +26,32 @@ function AgentInfoActions({ reload }) {

const handleDelete = useMutableCallback((e) => {
e.stopPropagation();
const onDeleteAgent = async () => {
const onDeleteAgent = async (): Promise<void> => {
try {
await handleRemoveClick();
dispatchToastMessage({ type: 'success', message: t('Agent_removed') });
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
dispatchToastMessage({ type: 'error', message: String(error) });
}
setModal();
};

setModal(<GenericModal variant='danger' onConfirm={onDeleteAgent} onCancel={() => setModal()} confirmText={t('Delete')} />);
setModal(<GenericModal variant='danger' onConfirm={onDeleteAgent} onCancel={(): void => setModal()} confirmText={t('Delete')} />);
});

const handleEditClick = useMutableCallback(() =>
agentsRoute.push({
context: 'edit',
id: _id,
id: String(_id),
}),
);

return [
<AgentInfo.Action key={t('Remove')} title={t('Remove')} label={t('Remove')} onClick={handleDelete} icon={'trash'} />,
<AgentInfo.Action key={t('Edit')} title={t('Edit')} label={t('Edit')} onClick={handleEditClick} icon={'edit'} />,
];
}
return (
<>
<AgentInfo.Action key={t('Remove')} title={t('Remove')} label={t('Remove')} onClick={handleDelete} icon={'trash'} />,
<AgentInfo.Action key={t('Edit')} title={t('Edit')} label={t('Edit')} onClick={handleEditClick} icon={'edit'} />,
</>
);
};

export default AgentInfoActions;
Loading