Skip to content

Commit

Permalink
Feature manage group (#365)
Browse files Browse the repository at this point in the history
* Update Group Single Screen (#339)

* Updates default messaging for Group Cards (#338)

* Tests written

Co-authored-by: Caleb Panza <caleb.panza@icloud.org>

* Add new "pen" icon

* Update a handful of styles on buttons

* Linter
* Border Width now set to 2px for legibility

* Updated button style on Group Single screen

* Fixes Group Member avatar layout issues

* Removed console.log

Co-authored-by: Caleb Panza <caleb.panza@icloud.org>
Co-authored-by: dzwood <46049974+dzwood@users.noreply.github.com>

* Edit group screen (#341)

* Update Resources and set up Group Members card

* Components for displaying a Group Member

* Stories written for all components
* Component for GroupMember
* UI element for GroupMemberStatusBadge

* Check in

* Resolves error where Modal wasn't working

* Finalize the Edit Group Member

* Add a new group member modal

Co-authored-by: Caleb Panza <caleb.panza@icloud.org>

* Quick Updates

* Caches Group Member Search Results
* Manually update cache for Group Member _updates_
* Couple of SquareAvatar missing props

* Updated the caching logic

@see apollographql/apollo-client#6760

* New API updates

* Update icons.js

Co-authored-by: Caleb Panza <caleb.panza@icloud.org>
Co-authored-by: dzwood <46049974+dzwood@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 7, 2021
1 parent c06dd44 commit ed990d8
Show file tree
Hide file tree
Showing 53 changed files with 1,962 additions and 183 deletions.
35 changes: 35 additions & 0 deletions components/GroupManage/GroupManage.components.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* GroupManage.components.js
*
* Author: Caleb Panza
* Created: Sep 03, 2021
*
* Convenience file for some UIKit configurations that are only relevant to the Manage Group component.
*/

import React from 'react';
import PropTypes from 'prop-types';
import isEmpty from 'lodash/isEmpty';
import { Box, Button, Icon } from 'ui-kit';

export const CardTitle = ({ title }) => (
<Box as="h3" flexGrow="1" mb="0" color="neutrals.700">
{title}
</Box>
);

CardTitle.propTypes = {
title: PropTypes.string.isRequired,
};

export const SmallPillButton = ({ title, icon, ...props }) => (
<Button size="s" rounded variant="secondary" py="5px" {...props}>
{!isEmpty(icon) && <Icon name={icon} size="18" mb="2px" mr="2px" />}
{title}
</Button>
);

SmallPillButton.propTypes = {
title: PropTypes.string.isRequired,
icon: PropTypes.string,
};
110 changes: 21 additions & 89 deletions components/GroupManage/GroupManage.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,110 +4,42 @@ import capitalize from 'lodash/capitalize';

import { slugify } from 'utils';
import { GroupManageProvider } from 'providers';
import { initialState } from 'providers/GroupManageProvider';
import { Box, Card } from 'ui-kit';
import { CustomLink } from 'components';

// todo : figure out where/how to support this
import GroupManagePhoto from './GroupManagePhoto';
import GroupManageResources from './GroupManageResources';

import GroupManageMembers from './GroupManageMembers';
function GroupManage(props = {}) {
const [section, setSection] = useState(initialState.sections.resources);

const handleSectionClick = section => event => {
event.preventDefault();
setSection(section);
};

function render() {
switch (section) {
case initialState.sections.photo: {
return <GroupManagePhoto />;
}
case initialState.sections.resources: {
return <GroupManageResources />;
}
default: {
return null;
}
}
}

return (
<GroupManageProvider groupData={props.data}>
<Box
display={{ lg: 'grid' }}
gridTemplateColumns="25% 1fr"
gridColumnGap="xl"
>
<Box mb={{ _: 'l', lg: '0' }}>
<Box>
<Box mb="l">
<CustomLink href={`/group/${slugify(props?.data?.title)}`}>
&larr; Back
</CustomLink>
<Box my="base">
{/* TODO: Make this a `<Label>` ui-kit component. */}
<Box
as="b"
color="subdued"
fontSize="xs"
fontWeight="bold"
letterSpacing="1px"
textTransform="uppercase"
>
Edit
</Box>
<Box as="h1" fontSize="h3">
{props?.data?.title}
</Box>
</Box>
<Card boxShadow="base">
<Box display="flex" flexDirection="column">
{Object.values(initialState.sections).map(
(_section, idx, arr) => {
let borderRadius = {};

if (idx === 0) {
borderRadius = {
borderTopLeftRadius: '0.375rem',
};
}
<Box as="h1">{props?.data?.title}</Box>
</Box>

if (idx === arr.length - 1) {
borderRadius = {
borderBottomLeftRadius: '0.375rem',
};
}
<Box
display="grid"
gridTemplateColumns={{ _: '1fr', md: '40% 1fr' }}
gridColumnGap="base"
columnGap="15px"
rowGap="15px"
// ! : keep for backwards css compatibility
gridColumnGap="15px"
gridRowGap="15px"
>
<Card p="base">
<GroupManageResources />
</Card>

return (
/**
* todo : Hiding 'Update' section for now TEMPORARILY per request.
*/
// <Box
// display="none"
// key={idx}
// width="100%"
// p="s"
// px="base"
// onClick={handleSectionClick(_section)}
// backgroundColor={
// section === _section ? 'primarySubduedHover' : ''
// }
// borderLeft={section === _section ? '5px solid' : ''}
// {...borderRadius}
// color={section === _section ? 'primary' : 'neutrals.700'}
// cursor="pointer"
// fontWeight="bold"
// >
// Update {capitalize(_section)}
// </Box>
<></>
);
}
)}
</Box>
<Card p="base">
<GroupManageMembers />
</Card>
</Box>
<Box>{render()}</Box>
</Box>
</GroupManageProvider>
);
Expand Down
207 changes: 207 additions & 0 deletions components/GroupManage/GroupManageMembers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
/**
* GroupManageMembers.js
*
* Author: Caleb Panza
* Created: Sep 03, 2021
*
* List of Group Members that can be searched and filtered. When the current person is a leader of the group, they will also be able to see management tools.
*/

import React, { useState, useEffect } from 'react';

import { useModalDispatch, showModal } from 'providers/ModalProvider';
import { useGroupManage } from 'providers/GroupManageProvider';
import { GroupMember, SearchField } from 'components';
import { Box, Button, Loader } from 'ui-kit';
import { CardTitle, SmallPillButton } from './GroupManage.components';
import { useSearchGroupMembers } from 'hooks';

function GroupManageMembers(props = {}) {
// MARK : Hooks
const modalDispatch = useModalDispatch();
const [{ groupData }] = useGroupManage();
const [searchGroupMembers, { groupMembers, facets, loading }] =
useSearchGroupMembers();

// MARK : State
const [searchText, setSearchText] = useState('');
const [searchArgs, setSearchArgs] = useState([
{ key: 'text', values: [''] },
{ key: 'groupId', values: [groupData?.id] },
{ key: 'status', values: [] },
]);

// MARK : Variables
const groupId = groupData?.id;
const hasMembers = Array.isArray(groupMembers) && groupMembers.length > 0;
const statusFacet = facets.find(({ key }) => key === 'status');

// MARK : Handlers
const searchFieldHandleChange = event => {
setSearchText(event?.target?.value || '');
};
const searchFieldHandleClear = event => {
event.preventDefault();
const searchWithoutText = searchArgs.filter(({ key }) => key !== 'text');
setSearchText('');
setSearchArgs([...searchWithoutText, { key: 'text', values: [''] }]);
};
const searchFieldHandleSubmit = event => {
event.preventDefault();

const searchWithoutText = searchArgs.filter(({ key }) => key !== 'text');
setSearchArgs([
...searchWithoutText,
{ key: 'text', values: [searchText] },
]);
};

const handleAddNewMember = () => {
modalDispatch(
showModal('AddGroupMember', {
groupId,
})
);
};

// MARK : Render
const renderStatusFacets = () => {
const hasFacets = statusFacet?.values.length > 0;
const selectedStatuses =
searchArgs.find(({ key }) => key === 'status') || [];

if (!hasFacets) return null;

return (
<Box my="base" mx={'-3px'}>
{statusFacet?.values.map(value => (
<Button
key={value}
mx="3px"
px="10px"
py="4px"
rounded
size="s"
variant="chip"
status={
selectedStatuses.values.includes(value) ? 'SELECTED' : 'IDLE'
}
onClick={() => {
let newStatuses = selectedStatuses.values;
if (selectedStatuses.values.includes(value)) {
newStatuses = newStatuses.filter(
existingValue => existingValue !== value
);
} else {
newStatuses = [...newStatuses, value];
}

const statuses = { key: 'status', values: newStatuses };
setSearchArgs([
...searchArgs.filter(({ key }) => key !== 'status'),
statuses,
]);
}}
>
{value}
</Button>
))}
</Box>
);
};

// MARK : Effects
useEffect(() => {
searchGroupMembers({
variables: {
groupId: groupData?.id,
query: {
attributes: searchArgs,
},
},
});
}, [searchArgs]);

useEffect(() => {
searchGroupMembers({
variables: {
groupId: groupData?.id,
query: {
attributes: [...searchArgs],
},
},
});
}, []);

console.log({ groupMembers })

return (
<>
<Box alignItems="center" display="flex">
<CardTitle title="Group Members" />

<SmallPillButton onClick={handleAddNewMember} icon="plus" title="Add" />
</Box>

<Box mt="base" mb="l">
<SearchField
handleChange={searchFieldHandleChange}
handleClear={searchFieldHandleClear}
handleSubmit={searchFieldHandleSubmit}
placeholder="Search..."
value={searchText || ''}
>
Search
</SearchField>
</Box>

{renderStatusFacets()}

{loading && (
<Box display="flex" justifyContent="center" alignItems="center" p="s">
<Loader />
</Box>
)}

{hasMembers && (
<Box
display="grid"
gridTemplateColumns={{ _: '1fr', md: '1fr 1fr' }}
columnGap="25px"
rowGap="15px"
// ! : keep for backwards css compatibility
gridColumnGap="15px"
gridRowGap="15px"
>
{groupMembers.map(
({ id, person, role, status }) => (
<GroupMember
id={id}
key={id}
status={status}
role={role}
person={person}
/>
)
)}
</Box>
)}

{!hasMembers && !loading && (
<Box
display="flex"
flexDirection="column"
justifyContent="center"
alignItems="center"
color={'neutrals.400'}
p="base"
>
<Box as="i">We couldn't find anyone that matches your criteria.</Box>
<Box as="i">Try refining your search a little bit</Box>
</Box>
)}
</>
);
}

export default GroupManageMembers;
Loading

0 comments on commit ed990d8

Please sign in to comment.