Skip to content

Commit

Permalink
Merge pull request #617 from glific/feature/add-contacts-to-group
Browse files Browse the repository at this point in the history
Support for adding bulk contacts to group
  • Loading branch information
kurund authored Oct 22, 2020
2 parents 85a5498 + b400ba8 commit 994503e
Show file tree
Hide file tree
Showing 11 changed files with 332 additions and 92 deletions.
89 changes: 68 additions & 21 deletions src/components/UI/Form/AutoComplete/AutoComplete.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';
import { Chip, FormHelperText, FormControl, Checkbox, Paper } from '@material-ui/core';
Expand All @@ -19,7 +19,10 @@ export interface AutocompleteProps {
chipIcon?: any;
getOptions?: any;
validate?: any;
asyncValues?: any;
noOptionsText?: any;
onChange?: any;
asyncSearch?: boolean;
}

export const AutoComplete: React.SFC<AutocompleteProps> = ({
Expand All @@ -34,13 +37,17 @@ export const AutoComplete: React.SFC<AutocompleteProps> = ({
multiple = true,
disabled = false,
getOptions,
asyncValues,
onChange,
asyncSearch = false,
noOptionsText = 'No options available',
}) => {
const errorText = getIn(errors, field.name);
const touchedVal = getIn(touched, field.name);
const hasError = dirty && touchedVal && errorText !== undefined;
const [optionValue, setOptionValue] = React.useState([]);
const [open, setOpen] = React.useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [optionValue, setOptionValue] = useState([]);
const [open, setOpen] = useState(false);

useEffect(() => {
if (options.length > 0) {
Expand All @@ -67,13 +74,28 @@ export const AutoComplete: React.SFC<AutocompleteProps> = ({
options={optionValue}
getOptionLabel={(option: any) => (option[optionLabel] ? option[optionLabel] : '')}
onChange={(event, value: any) => {
if (asyncSearch) {
const filterValues = asyncValues.value.filter(
(val: any) => val.id !== value[value.length - 1].id
);
if (filterValues.length === value.length - 2) {
asyncValues.setValue(filterValues);
} else {
asyncValues.setValue([...value]);
}
setSearchTerm('');
onChange('');
}
setFieldValue(field.name, value);
}}
inputValue={asyncSearch ? searchTerm : undefined}
value={
multiple
? optionValue.filter((option: any) =>
field.value.map((value: any) => value.id).includes(option.id)
)
? asyncSearch
? asyncValues.value
: optionValue.filter((option: any) =>
field.value.map((value: any) => value.id).includes(option.id)
)
: field.value
}
disabled={disabled}
Expand All @@ -91,21 +113,46 @@ export const AutoComplete: React.SFC<AutocompleteProps> = ({
/>
))
}
renderOption={(option, { selected }) => (
<React.Fragment>
{multiple ? <Checkbox icon={icon} checked={selected} color="primary" /> : ''}
{option[optionLabel]}
</React.Fragment>
)}
renderInput={(params) => (
<TextField
{...params}
error={hasError}
helperText={hasError ? errorText : ''}
{...textFieldProps}
data-testid="AutocompleteInput"
/>
)}
renderOption={(option, { selected }) => {
return (
<React.Fragment>
{multiple ? (
<Checkbox
icon={icon}
checked={
asyncSearch
? asyncValues.value.map((value: any) => value.id).includes(option.id)
: selected
}
color="primary"
/>
) : (
''
)}
{option[optionLabel]}
</React.Fragment>
);
}}
renderInput={(params: any) => {
const asyncChange = asyncSearch
? {
onChange: (event: any) => {
setSearchTerm(event.target.value);
return onChange(event.target.value);
},
}
: null;
return (
<TextField
{...params}
{...asyncChange}
error={hasError}
helperText={hasError ? errorText : ''}
{...textFieldProps}
data-testid="AutocompleteInput"
/>
);
}}
open={open}
onOpen={() => {
setOpen(true);
Expand Down
33 changes: 27 additions & 6 deletions src/components/UI/SearchDialogBox/SearchDialogBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,56 @@ export interface SearchDialogBoxProps {
options: any;
selectedOptions: any;
icon?: any;
optionLabel?: string;
onChange?: any;
asyncSearch?: boolean;
}

export const SearchDialogBox = (props: any) => {
export const SearchDialogBox = (props: SearchDialogBoxProps) => {
const [selectedOptions, setSelectedOptions] = useState<Array<string>>([]);
const [asyncSelectedOptions, setAsyncSelectedOptions] = useState<Array<any>>([]);

useEffect(() => {
setSelectedOptions(
props.options.filter((option: any) => props.selectedOptions.includes(option.id))
);
if (!props.asyncSearch) {
setSelectedOptions(
props.options.filter((option: any) => props.selectedOptions.includes(option.id))
);
}
}, [props.selectedOptions, props.options]);

useEffect(() => {
if (props.asyncSearch === true) {
setAsyncSelectedOptions(props.selectedOptions);
}
}, [props.selectedOptions]);

const changeValue = (event: any, value: any) => {
setSelectedOptions(value);
};

return (
<DialogBox
title={props.title}
handleOk={() => props.handleOk(selectedOptions.map((option: any) => option.id))}
handleOk={() =>
props.handleOk(
props.asyncSearch
? asyncSelectedOptions.map((option: any) => option.id)
: selectedOptions.map((option: any) => option.id)
)
}
handleCancel={props.handleCancel}
titleAlign="left"
buttonOk="Save"
>
<div className={styles.DialogBox}>
<FormControl fullWidth>
<AutoComplete
asyncSearch={props.asyncSearch}
asyncValues={{ value: asyncSelectedOptions, setValue: setAsyncSelectedOptions }}
options={props.options}
optionLabel="label"
optionLabel={props.optionLabel ? props.optionLabel : 'label'}
field={{ value: selectedOptions }}
onChange={(value: any) => props.onChange(value)}
form={{ setFieldValue: changeValue }}
textFieldProps={{
label: 'Search',
Expand Down
14 changes: 8 additions & 6 deletions src/containers/Automation/AutomationList/AutomationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ const columnAttributes = {
};
const configureIcon = <ConfigureIcon></ConfigureIcon>;

const additionalAction = {
label: 'Configure',
icon: configureIcon,
parameter: 'uuid',
link: '/automation/configure',
};
const additionalAction = [
{
label: 'Configure',
icon: configureIcon,
parameter: 'uuid',
link: '/automation/configure',
},
];

export const AutomationList: React.SFC<AutomationListProps> = (props) => (
<List
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,13 @@ export const BlockContactList: React.SFC<BlockContactListProps> = (props) => {
);
}

const additionalAction = {
icon: unblockIcon,
parameter: 'id',
dialog: setDialog,
};
const additionalAction = [
{
icon: unblockIcon,
parameter: 'id',
dialog: setDialog,
},
];
return (
<>
<List
Expand Down
41 changes: 33 additions & 8 deletions src/containers/Group/GroupList/GroupList.test.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import React from 'react';
import { render, screen, wait } from '@testing-library/react';
import { render, screen, wait, fireEvent } from '@testing-library/react';
import UserEvent from '@testing-library/user-event';
import { MockedProvider } from '@apollo/client/testing';

import { setUserRole } from '../../../context/role';
import { GroupList } from './GroupList';
import { countGroupQuery, filterGroupQuery } from '../../../mocks/Group';

const mocks = [countGroupQuery, filterGroupQuery];
import { countGroupQuery, filterGroupQuery, getGroupContactsQuery } from '../../../mocks/Group';
import { MemoryRouter } from 'react-router';
import { getContactsQuery } from '../../../mocks/Contact';

const mocks = [
countGroupQuery,
filterGroupQuery,
filterGroupQuery,
getGroupContactsQuery,
getContactsQuery,
];

const wrapper = (
<MockedProvider mocks={mocks} addTypename={false}>
<GroupList />
</MockedProvider>
<MemoryRouter>
<MockedProvider mocks={mocks} addTypename={false}>
<GroupList />
</MockedProvider>
</MemoryRouter>
);

describe('<GroupList />', () => {
Expand All @@ -28,4 +38,19 @@ describe('<GroupList />', () => {

// TODO: test delete
});

test('it should have add contact to group dialog box ', async () => {
setUserRole(['Admin']);
const { getByText, getAllByTestId } = render(wrapper);

// loading is show initially
expect(getByText('Loading...')).toBeInTheDocument();
await wait();
expect(getAllByTestId('additionalButton')[0]).toBeInTheDocument();
fireEvent.click(getAllByTestId('additionalButton')[0]);

await wait();

expect(getByText('Add contacts to the group')).toBeInTheDocument();
});
});
Loading

0 comments on commit 994503e

Please sign in to comment.