RocketChat/Rocket.Chat

View on GitHub
apps/meteor/client/omnichannel/monitors/MonitorsTable.tsx

Summary

Maintainability
D
2 days
Test Coverage
import {
    IconButton,
    Pagination,
    Button,
    Field,
    FieldLabel,
    FieldRow,
    Box,
    States,
    StatesIcon,
    StatesTitle,
    StatesActions,
    StatesAction,
} from '@rocket.chat/fuselage';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import { useTranslation, useToastMessageDispatch, useMethod, useEndpoint, useSetModal } from '@rocket.chat/ui-contexts';
import { useMutation, useQuery, hashQueryKey } from '@tanstack/react-query';
import React, { useMemo, useState } from 'react';

import FilterByText from '../../components/FilterByText';
import GenericModal from '../../components/GenericModal';
import GenericNoResults from '../../components/GenericNoResults';
import {
    GenericTable,
    GenericTableBody,
    GenericTableCell,
    GenericTableHeader,
    GenericTableHeaderCell,
    GenericTableLoadingTable,
    GenericTableRow,
} from '../../components/GenericTable';
import { usePagination } from '../../components/GenericTable/hooks/usePagination';
import { useSort } from '../../components/GenericTable/hooks/useSort';
import UserAutoComplete from '../../components/UserAutoComplete';
import { queryClient } from '../../lib/queryClient';

const MonitorsTable = () => {
    const t = useTranslation();
    const setModal = useSetModal();

    const [text, setText] = useState('');
    const [username, setUsername] = useState('');
    const debouncedText = useDebouncedValue(text, 500);

    const dispatchToastMessage = useToastMessageDispatch();

    const pagination = usePagination();
    const sort = useSort<'name' | 'username' | 'email'>('name');

    const getMonitors = useEndpoint('GET', '/v1/livechat/monitors');

    // TODO: implement endpoints for monitors add/remove
    const removeMonitor = useMethod('livechat:removeMonitor');
    const addMonitor = useMethod('livechat:addMonitor');

    const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = pagination;
    const { sortBy, sortDirection, setSort } = sort;

    const query = useMemo(
        () => ({
            text: debouncedText,
            sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`,
            ...(itemsPerPage && { count: itemsPerPage }),
            ...(current && { offset: current }),
        }),
        [debouncedText, itemsPerPage, current, sortBy, sortDirection],
    );

    const { data, refetch, isLoading, isSuccess, isError } = useQuery(['omnichannel', 'monitors', debouncedText, pagination, sort], () =>
        getMonitors(query),
    );

    const [defaultQuery] = useState(hashQueryKey([query]));
    const queryHasChanged = defaultQuery !== hashQueryKey([query]);

    const addMutation = useMutation({
        mutationFn: async (username: string) => {
            await addMonitor(username);

            await queryClient.invalidateQueries(['omnichannel', 'monitors']);
        },
        onSuccess: () => {
            setUsername('');
            dispatchToastMessage({ type: 'success', message: t('Monitor_added') });
        },
        onError: (error) => {
            dispatchToastMessage({ type: 'error', message: error });
        },
    });

    const handleAdd = () => {
        addMutation.mutate(username);
    };

    const handleRemove = (username: string) => {
        const onDeleteMonitor = async () => {
            try {
                await removeMonitor(username);
                dispatchToastMessage({ type: 'success', message: t('Monitor_removed') });
            } catch (error) {
                dispatchToastMessage({ type: 'error', message: error });
            }
            queryClient.invalidateQueries(['omnichannel', 'monitors']);
            setModal();
        };

        setModal(
            <GenericModal
                variant='danger'
                data-qa-id='manage-monitors-confirm-remove'
                onConfirm={onDeleteMonitor}
                onCancel={() => setModal()}
                confirmText={t('Delete')}
            />,
        );
    };

    const headers = useMemo(
        () => [
            <GenericTableHeaderCell key='name' direction={sortDirection} active={sortBy === 'name'} onClick={setSort}>
                {t('Name')}
            </GenericTableHeaderCell>,
            <GenericTableHeaderCell key='username' direction={sortDirection} active={sortBy === 'username'} onClick={setSort}>
                {t('Username')}
            </GenericTableHeaderCell>,
            <GenericTableHeaderCell key='email' direction={sortDirection} active={sortBy === 'email'} onClick={setSort}>
                {t('Email')}
            </GenericTableHeaderCell>,
            <GenericTableHeaderCell key='spacer' w={40} />,
        ],
        [setSort, sortBy, sortDirection, t],
    );

    return (
        <>
            <Box display='flex' flexDirection='column'>
                <Field>
                    <FieldLabel>{t('Username')}</FieldLabel>
                    <FieldRow>
                        <UserAutoComplete name='monitor' value={username} onChange={setUsername as () => void} />
                        <Button primary disabled={!username} loading={addMutation.isLoading} onClick={() => handleAdd()} mis={8}>
                            {t('Add_monitor')}
                        </Button>
                    </FieldRow>
                </Field>
            </Box>
            {((isSuccess && data?.monitors.length > 0) || queryHasChanged) && <FilterByText onChange={setText} />}
            {isLoading && (
                <GenericTable>
                    <GenericTableHeader>{headers}</GenericTableHeader>
                    <GenericTableBody>
                        <GenericTableLoadingTable headerCells={4} />
                    </GenericTableBody>
                </GenericTable>
            )}
            {isSuccess && data.monitors.length === 0 && queryHasChanged && <GenericNoResults />}
            {isSuccess && data.monitors.length === 0 && !queryHasChanged && (
                <GenericNoResults
                    icon='shield-blank'
                    title={t('No_monitors_yet')}
                    description={t('No_monitors_yet_description')}
                    linkHref='https://go.rocket.chat/i/omnichannel-docs'
                    linkText={t('Learn_more_about_monitors')}
                />
            )}
            {isSuccess && data.monitors.length > 0 && (
                <>
                    <GenericTable aria-busy={text !== debouncedText} aria-live='assertive' data-qa-id='manage-monitors-table'>
                        <GenericTableHeader>{headers}</GenericTableHeader>
                        <GenericTableBody>
                            {data.monitors?.map((monitor) => (
                                <GenericTableRow key={monitor._id} tabIndex={0} width='full' data-qa-id={monitor.name}>
                                    <GenericTableCell withTruncatedText>{monitor.name}</GenericTableCell>
                                    <GenericTableCell withTruncatedText>{monitor.username}</GenericTableCell>
                                    <GenericTableCell withTruncatedText>{monitor.email}</GenericTableCell>
                                    <GenericTableCell withTruncatedText>
                                        <IconButton icon='trash' small title={t('Remove')} onClick={() => handleRemove(monitor.username)} />
                                    </GenericTableCell>
                                </GenericTableRow>
                            ))}
                        </GenericTableBody>
                    </GenericTable>
                    <Pagination
                        divider
                        current={current}
                        itemsPerPage={itemsPerPage}
                        count={data?.total || 0}
                        onSetItemsPerPage={onSetItemsPerPage}
                        onSetCurrent={onSetCurrent}
                        {...paginationProps}
                    />
                </>
            )}
            {isError && (
                <States>
                    <StatesIcon name='warning' variation='danger' />
                    <StatesTitle>{t('Something_went_wrong')}</StatesTitle>
                    <StatesActions>
                        <StatesAction onClick={() => refetch()}>{t('Reload_page')}</StatesAction>
                    </StatesActions>
                </States>
            )}
        </>
    );
};

export default MonitorsTable;