Skip to content

Commit 2b8c5da

Browse files
authored
Merge pull request #681 from codeforpdx/663/add-mobile-contacts-search
663/add mobile contacts sort
2 parents 6505239 + ed386a3 commit 2b8c5da

File tree

6 files changed

+129
-27
lines changed

6 files changed

+129
-27
lines changed

src/components/Contacts/ContactListTable.jsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ import ContactListTableMobile from './ContactListTableMobile';
2626
* @param {Function} Props.addContact - from Contacts page
2727
* @returns {React.JSX.Element} The ContactListTable Component
2828
*/
29-
const ContactListTable = ({ contacts, deleteContact, handleDeleteContact, addContact }) => {
29+
const ContactListTable = ({ contacts = [], deleteContact, handleDeleteContact, addContact }) => {
3030
const [showMessageModal, setShowMessageModal] = useState(false);
3131
const [messageToField, setMessageToField] = useState('');
32-
const theme = useTheme();
33-
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
34-
3532
const [showAddContactModal, setShowAddContactModal] = useState(false);
3633
const [contactToEdit, setContactToEdit] = useState(null);
3734
const [isEditing, setIsEditing] = useState(false);
35+
const contactWebIds = contacts.map(({ webId }) => webId);
36+
const theme = useTheme();
37+
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
3838

3939
const handleSendMessage = (contactId) => {
4040
setShowMessageModal(!showMessageModal);
@@ -47,14 +47,12 @@ const ContactListTable = ({ contacts, deleteContact, handleDeleteContact, addCon
4747
setIsEditing(true);
4848
};
4949

50-
const contactWebIds = contacts.map(({ webId }) => webId);
51-
5250
return (
5351
<Box
5452
sx={{
5553
margin: '20px 0',
5654
width: '95vw',
57-
height: '500px'
55+
minHeight: '500px'
5856
}}
5957
>
6058
{isSmallScreen ? (

src/hooks/useTableSort.js

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,36 @@
1-
// hooks/useSortedData.js
2-
import { useState, useEffect } from 'react';
1+
import { useEffect, useState } from 'react';
32

4-
const useSortedData = (initialData, getFieldValue, initialFieldType) => {
5-
const [fieldType, setFieldType] = useState(initialFieldType);
3+
const useTableSort = (initialData, initialSortValue) => {
64
const [sortedData, setSortedData] = useState([]);
5+
const [sortValue, setSortValue] = useState(initialSortValue);
6+
7+
const sortData = (value) => {
8+
const sorted = [...initialData].sort((a, b) => {
9+
if (value === 'First Name') {
10+
return a.givenName.localeCompare(b.givenName);
11+
}
12+
if (value === 'Last Name') {
13+
return a.familyName.localeCompare(b.familyName);
14+
}
15+
if (value === 'Web ID') {
16+
return a.webId.localeCompare(b.webId);
17+
}
18+
return 0;
19+
});
20+
setSortedData(sorted);
21+
};
722

823
useEffect(() => {
9-
const sortData = (dataToSort, field) =>
10-
[...dataToSort].sort((a, b) => {
11-
const fieldA = getFieldValue(a, field);
12-
const fieldB = getFieldValue(b, field);
13-
return fieldA.localeCompare(fieldB);
14-
});
24+
setSortedData([...initialData]);
25+
}, [initialData]);
1526

16-
setSortedData(sortData(initialData, fieldType));
17-
}, [fieldType, initialData, getFieldValue]);
27+
useEffect(() => {
28+
if (initialData.length > 0) {
29+
sortData(sortValue, initialData);
30+
}
31+
}, [sortValue, initialData]);
1832

19-
return { fieldType, setFieldType, sortedData };
33+
return { sortedData, sortValue, setSortValue };
2034
};
2135

22-
export default useSortedData;
36+
export default useTableSort;

src/pages/Contacts.jsx

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ const Contacts = () => {
3737
const [processing, setProcessing] = useState(false);
3838
const [selectedContactToDelete, setSelectedContactToDelete] = useState(null);
3939
const [deleteViaEdit, setDeleteViaEdit] = useState(false);
40+
const [sortValue, setSortValue] = useState('Sort by:');
41+
const [sortedData, setSortedData] = useState([]);
4042
const {
41-
data,
43+
data = [],
4244
isLoading,
4345
isError,
4446
error,
@@ -47,12 +49,42 @@ const Contacts = () => {
4749
delete: deleteContact
4850
} = useContactsList();
4951
const { addNotification } = useNotification();
50-
const [fieldType, setFieldType] = useState('First Name');
52+
53+
const sortData = (value) => {
54+
const sorted = [...data].sort((a, b) => {
55+
if (value === 'Default') {
56+
return data;
57+
}
58+
if (value === 'First Name') {
59+
return a.givenName.localeCompare(b.givenName);
60+
}
61+
if (value === 'Last Name') {
62+
return a.familyName.localeCompare(b.familyName);
63+
}
64+
if (value === 'Web ID') {
65+
return a.webId.localeCompare(b.webId);
66+
}
67+
return 0;
68+
});
69+
setSortedData(sorted);
70+
};
71+
72+
const handleSortChange = (event) => {
73+
const { value } = event.target;
74+
setSortValue(value);
75+
sortData(value);
76+
};
5177

5278
useEffect(() => {
5379
refetch();
5480
}, []);
5581

82+
useEffect(() => {
83+
if (data.length > 0) {
84+
setSortedData(data);
85+
}
86+
}, [data]);
87+
5688
const getContactDisplayName = (contact) => {
5789
if (!contact) {
5890
return 'Unknown Contact';
@@ -104,14 +136,13 @@ const Contacts = () => {
104136
>
105137
<Box>
106138
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
107-
{/* TODO: Make sorting options functional */}
108139
{isSmallScreen && (
109140
<FormControl sx={{ minWidth: 120 }} size="small">
110141
<Select
111142
id="contact-select-field-small"
112-
value={fieldType}
113143
defaultValue="First Name"
114-
onChange={(e) => setFieldType(e.target.value)}
144+
value={sortValue}
145+
onChange={handleSortChange}
115146
sx={{
116147
borderRadius: '8px',
117148
color: 'primary.main',
@@ -127,6 +158,11 @@ const Contacts = () => {
127158
}}
128159
IconComponent={KeyboardArrowDownIcon}
129160
>
161+
{' '}
162+
<MenuItem value="Sort by:" disabled>
163+
Sort by:
164+
</MenuItem>
165+
<MenuItem value="Default">Default</MenuItem>
130166
<MenuItem value="First Name">First Name</MenuItem>
131167
<MenuItem value="Last Name">Last Name</MenuItem>
132168
<MenuItem value="Web ID">Web ID</MenuItem>
@@ -145,7 +181,7 @@ const Contacts = () => {
145181
</Box>
146182
{data.length > 0 ? (
147183
<ContactListTable
148-
contacts={data}
184+
contacts={isSmallScreen ? sortedData : data}
149185
deleteContact={(contact) => handleSelectDeleteContact(contact)}
150186
handleDeleteContact={handleDeleteContact}
151187
addContact={addContact}

test/components/Contacts/ContactListTableDesktop.test.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ const contacts = [
4848
givenName: 'Bob',
4949
person: 'Bob Builder',
5050
webId: 'https://example.com/Builder'
51+
},
52+
{
53+
familyName: 'Carl',
54+
givenName: 'Carson',
55+
person: 'Carl Carson',
56+
webId: 'https://example.com/Carson'
5157
}
5258
];
5359

test/components/Contacts/ContactListTableMobile.test.jsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@ afterEach(() => {
1313

1414
const queryClient = new QueryClient();
1515

16+
const sortData = (data, key) =>
17+
[...data].sort((a, b) => {
18+
if (key === 'First Name') {
19+
return a.givenName.localeCompare(b.givenName);
20+
}
21+
if (key === 'Last Name') {
22+
return a.familyName.localeCompare(b.familyName);
23+
}
24+
if (key === 'Web ID') {
25+
return a.webId.localeCompare(b.webId);
26+
}
27+
return 0;
28+
});
29+
1630
const MockTableComponent = ({
1731
contacts,
1832
deleteContact = vi.fn(),
@@ -48,6 +62,12 @@ const contacts = [
4862
givenName: 'Bob',
4963
person: 'Bob Builder',
5064
webId: 'https://example.com/Builder'
65+
},
66+
{
67+
familyName: 'Carson',
68+
givenName: 'Carl',
69+
person: 'Carl Carson',
70+
webId: 'https://example.com/Carson'
5171
}
5272
];
5373

@@ -82,4 +102,31 @@ describe('contacts list table mobile tests', () => {
82102
expect(webIdElement).not.toBeNull();
83103
});
84104
});
105+
106+
it('sorts by first name correctly', () => {
107+
render(<MockTableComponent contacts={contacts} session={sessionObj} />);
108+
109+
const sortedData = sortData(contacts, 'First Name');
110+
expect(sortedData[0].givenName).toBe('Aaron');
111+
expect(sortedData[1].givenName).toBe('Bob');
112+
expect(sortedData[2].givenName).toBe('Carl');
113+
});
114+
115+
it('sorts by last name correctly', () => {
116+
render(<MockTableComponent contacts={contacts} session={sessionObj} />);
117+
118+
const sortedData = sortData(contacts, 'Last Name');
119+
expect(sortedData[0].familyName).toBe('Abby');
120+
expect(sortedData[1].familyName).toBe('Builder');
121+
expect(sortedData[2].familyName).toBe('Carson');
122+
});
123+
124+
it('sorts by web ID correctly', () => {
125+
render(<MockTableComponent contacts={contacts} session={sessionObj} />);
126+
127+
const sortedData = sortData(contacts, 'Web ID');
128+
expect(sortedData[0].webId).toBe('https://example.com/Abby');
129+
expect(sortedData[1].webId).toBe('https://example.com/Builder');
130+
expect(sortedData[2].webId).toBe('https://example.com/Carson');
131+
});
85132
});

test/pages/Contacts.test.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ describe('Contacts Page', () => {
7373
const contacts = getByRole('grid');
7474
expect(contacts).not.toBeNull();
7575
});
76+
7677
it('displays empty list message when there are no contacts', () => {
7778
useContactsList.mockReturnValue({ data: [], refetch: vi.fn() });
7879
const { getByLabelText } = render(

0 commit comments

Comments
 (0)