Skip to content

Commit

Permalink
Added categories to tag list, add, and edit tags
Browse files Browse the repository at this point in the history
  • Loading branch information
devan-huapaya committed Aug 30, 2021
1 parent 1006061 commit 87ddf5a
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 39 deletions.
1 change: 1 addition & 0 deletions packages/schema/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ enum TagCategory {
PROGRAM
GRANT
OTHER
ALL
}

#
Expand Down
2 changes: 1 addition & 1 deletion packages/webapp/src/components/forms/AddTagForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const AddTagForm = memo(function AddTagForm({
const newTag: TagInput = {
label: values.label,
description: values.description,
category: values.category?.value || undefined
category: values.category || undefined
}

const response = await createTag(orgId, newTag)
Expand Down
65 changes: 48 additions & 17 deletions packages/webapp/src/components/lists/RequestTagsList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,21 @@ import { useRecoilValue } from 'recoil'
import { organizationState } from '~store'
import { Tag } from '@cbosuite/schema/lib/client-types'
import React, { memo, useCallback, useEffect, useRef, useState } from 'react'
import PaginatedList, { IPaginatedListColumn } from '~components/ui/PaginatedList'
import TagBadge from '~components/ui/TagBadge'
import ClientOnly from '~components/ui/ClientOnly'
import MultiActionButton, { IMultiActionButtons } from '~components/ui/MultiActionButton2'
import Panel from '~components/ui/Panel'
import PaginatedList, { IPaginatedListColumn } from '~ui/PaginatedList'
import TagBadge from '~ui/TagBadge'
import ClientOnly from '~ui/ClientOnly'
import MultiActionButton, { IMultiActionButtons } from '~ui/MultiActionButton2'
import Panel from '~ui/Panel'
import { useBoolean } from '@fluentui/react-hooks'
import AddTagForm from '~components/forms/AddTagForm'
import ShortString from '~components/ui/ShortString'
import AddTagForm from '~forms/AddTagForm'
import ShortString from '~ui/ShortString'
import useWindowSize from '~hooks/useWindowSize'
import EditTagForm from '~components/forms/EditTagForm'
import UserCardRow from '~components/ui/UserCardRow'
import EditTagForm from '~forms/EditTagForm'
import UserCardRow from '~ui/UserCardRow'
import { Col, Row } from 'react-bootstrap'
//import { Parser, FieldInfo } from 'json2csv'
// import { useReports } from '~hooks/api/useReports'
import { useTranslation } from '~hooks/useTranslation'

import TAG_CATEGORIES from '~utils/consts/TAG_CATEGORIES'
import { OptionType } from '~ui/ReactSelect'
interface RequestTagsListProps extends ComponentProps {
title?: string
}
Expand All @@ -34,7 +33,6 @@ const RequestTagsList = memo(function RequestTagsList({
}: RequestTagsListProps): JSX.Element {
const { t, c } = useTranslation('requestTags')
const org = useRecoilValue(organizationState)
// const { data: engagementExportData } = useReports()

const { isMD } = useWindowSize()
const [filteredList, setFilteredList] = useState<Tag[]>(org?.tags || [])
Expand All @@ -43,22 +41,48 @@ const RequestTagsList = memo(function RequestTagsList({
const [isEditFormOpen, { setTrue: openEditTagPanel, setFalse: dismissEditTagPanel }] =
useBoolean(false)
const [selectedTag, setSelectedTag] = useState<Tag>(null)

const searchText = useRef<string>('')

useEffect(() => {
setFilteredList(org?.tags)
}, [org?.tags])

/**
* Filter tag list
*/
const filterList = (filterOption: OptionType) => {
console.log('filterOption', filterOption)
if (!filterOption?.value) {
console.log('filterOption', filterOption)
}
const value = filterOption?.value
let filteredTags: Tag[]

if (!value || value === 'ALL' || value === '') {
// Show all org tags
filteredTags = org?.tags
} else if (value === 'OTHER') {
// Show tags without category or other
filteredTags = org?.tags.filter((tag: Tag) => !tag.category || tag.category === value)
} else {
// Filter on selected category
filteredTags = org?.tags.filter((tag: Tag) => tag.category === value)
}

setFilteredList(filteredTags)
}

const searchList = useCallback(
(searchStr: string) => {
if (searchStr === '') {
// Clear search
setFilteredList(org?.tags)
} else {
// Filter tags based on search term
const filteredTags = org?.tags.filter(
(tag: Tag) =>
tag?.label.toLowerCase().indexOf(searchStr) > -1 ||
tag?.description?.toLowerCase().indexOf(searchStr) > -1
tag?.label.toLowerCase().indexOf(searchStr.toLowerCase()) > -1 ||
tag?.description?.toLowerCase().indexOf(searchStr.toLowerCase()) > -1
)
setFilteredList(filteredTags)
}
Expand All @@ -68,6 +92,11 @@ const RequestTagsList = memo(function RequestTagsList({
[org?.tags, searchText]
)

const filterOptions = {
options: TAG_CATEGORIES.map((cat) => ({ label: c(`tagCategory.${cat}`), value: cat })),
onChange: filterList
}

const columnActionButtons: IMultiActionButtons<Tag>[] = [
{
name: t('requestTagListRowActions.edit'),
Expand Down Expand Up @@ -238,7 +267,8 @@ const RequestTagsList = memo(function RequestTagsList({
itemsPerPage={20}
columns={pageColumns}
rowClassName='align-items-center'
addButtonName={t('requestTagAddButton')}
addButtonName={t('requestdTagAddButton')}
filterOptions={filterOptions}
onSearchValueChange={(value) => searchList(value)}
onListAddButtonClick={() => openNewTagPanel()}
// exportButtonName={st('requestTagExportButton')}
Expand All @@ -251,6 +281,7 @@ const RequestTagsList = memo(function RequestTagsList({
columns={mobileColumn}
hideListHeaders={true}
addButtonName={t('requestTagAddButton')}
filterOptions={filterOptions}
onSearchValueChange={(value) => searchList(value)}
onListAddButtonClick={() => openNewTagPanel()}
/>
Expand Down
63 changes: 43 additions & 20 deletions packages/webapp/src/components/ui/PaginatedList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import ClientOnly from '~ui/ClientOnly'
import { useTranslation } from '~hooks/useTranslation'
import Icon from '../Icon'
import Collapsible from '~ui/Collapsible'
import ReactSelect, { OptionType } from '~ui/ReactSelect'

export interface IPaginatedListColumn {
key: string
Expand All @@ -28,6 +29,13 @@ export interface IPaginatedListColumn {
onRenderColumnItem?: (item: any, index: number) => JSX.Element | JSX.Element[] | string
}

export interface FilterOptions {
onChange?: (filterValue: OptionType) => void
options: OptionType[]
className?: string
fieldName?: string | Array<string>
}

interface PaginatedListProps<T> extends ComponentProps {
title?: string
list: T[]
Expand All @@ -42,6 +50,10 @@ interface PaginatedListProps<T> extends ComponentProps {
isLoading?: boolean
collapsible?: boolean
collapsibleStateName?: string
showSearch?: boolean
showFilter?: boolean
filterOptions?: FilterOptions
onFilterChange?: (value: string) => void
onSearchValueChange?: (value: string) => void
onListAddButtonClick?: () => void
onPageChange?: (items: T[], currentPage: number) => void
Expand All @@ -65,6 +77,8 @@ const PaginatedList = memo(function PaginatedList<T>({
onSearchValueChange,
onListAddButtonClick,
onPageChange,
showSearch = true,
filterOptions,
onExportDataButtonClick
}: PaginatedListProps<T>): JSX.Element {
const { c } = useTranslation()
Expand Down Expand Up @@ -149,31 +163,40 @@ const PaginatedList = memo(function PaginatedList<T>({
{!!title && <h2>{title}</h2>}
</div>
</Col>
<Col md={3} xs={7}>
<Col md={6} xs={12}>
<ClientOnly>
<Collapsible enabled={collapsible} in={isCollapsibleOpen}>
<TextField
placeholder={c('paginatedList.search')}
onChange={(_ev, searchVal) => {
setListSearching(searchVal.length > 0)
onSearchValueChange?.(searchVal)
}}
styles={{
fieldGroup: {
borderRadius: 4,
':after': {
borderRadius: 4
}
}
}}
iconProps={{
iconName: 'Search'
}}
/>
<Row className=''>
<Col md={6} xs={12} className='mb-3 mb-md-0'>
{filterOptions && <ReactSelect {...filterOptions} />}
</Col>
<Col md={6} xs={12}>
{showSearch && (
<TextField
placeholder={c('paginatedList.search')}
onChange={(_ev, searchVal) => {
setListSearching(searchVal.length > 0)
onSearchValueChange?.(searchVal)
}}
styles={{
fieldGroup: {
borderRadius: 4,
':after': {
borderRadius: 4
}
}
}}
iconProps={{
iconName: 'Search'
}}
/>
)}
</Col>
</Row>
</Collapsible>
</ClientOnly>
</Col>
<Col md={6} xs={5} className='d-flex justify-content-end'>
<Col xs={3} className='d-flex justify-content-end'>
<Collapsible enabled={collapsible} in={isCollapsibleOpen}>
<>
{exportButtonName && (
Expand Down
118 changes: 118 additions & 0 deletions packages/webapp/src/components/ui/ReactSelect/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { memo } from 'react'
import Select from 'react-select'
import type ComponentProps from '~types/ComponentProps'

// React select without Formik
// React select users js object style notation :(
export const reactSelectStyles = {
valueContainer: (base: Record<string, any>): Record<string, any> => ({
...base,
paddingTop: '1px',
paddingBottom: '1px'
}),
control: (base: Record<string, any>, state: { isFocused: boolean }): Record<string, any> => ({
...base,
border: state.isFocused ? '1px solid var(--bs-primary)' : '1px solid var(--bs-gray-4)',
fontSize: '14px ',
lineHeight: '21px',
minHeight: 36,
// This line disables the blue border
boxShadow: 'none',
'&:hover': {
boxShadow: 'none',
border: '1px solid var(--bs-primary)'
}
}),
clearIndicator: (base: Record<string, any>): Record<string, any> => ({
...base,
padding: 4
}),
indicatorContainer: (base: Record<string, any>): Record<string, any> => ({
...base,
padding: 4
}),
dropdownIndicator: (base: Record<string, any>): Record<string, any> => ({
...base,
padding: 4
}),
menu: (base: Record<string, any>): Record<string, any> => ({
...base,
borderRadius: 0,
padding: 0
}),
menuList: (base: Record<string, any>): Record<string, any> => ({
...base,
paddingTop: 0,
paddingBottom: 0
}),
multiValue: (base: Record<string, any>): Record<string, any> => ({
...base,
borderRadius: '3rem',
backgroundColor: 'var(--bs-dark)', // Taken from designs. would put in bootstrap styles if react select accepted css styles :(
color: 'var(--bs-white)',
paddingLeft: '4px',
paddingRight: '4px'
}),
multiValueLabel: (base: Record<string, any>): Record<string, any> => ({
...base,
color: 'var(--bs-white)'
}),
multiValueRemove: (base: Record<string, any>): Record<string, any> => ({
...base,
backgroundColor: 'var(--bs-white)', // Taken from designs. would put in bootstrap styles if react select accepted css styles :(
color: 'var(--bs-dark)',
borderRadius: '100%',
height: '18px',
width: '18px',
margin: 'auto'
}),
placeholder: (base: Record<string, any>): Record<string, any> => ({
...base,
color: 'var(--bs-text-muted)'
})
}

export interface OptionType {
label: string
value: any
}
export interface ReactSelectProps extends ComponentProps {
name?: string
placeholder?: string
error?: string
options?: any[]
defaultValue?: any
onChange?: (filterOption: OptionType) => void
isMulti?: boolean
}

const ReactSelect = memo(function ReactSelect({
onChange,
placeholder,
defaultValue,
isMulti,
options
}: ReactSelectProps): JSX.Element {
return (
<>
<Select
isClearable
styles={reactSelectStyles}
onChange={onChange}
options={options}
placeholder={placeholder}
defaultValue={defaultValue}
isMulti={isMulti}
components={{
IndicatorSeparator: () => null
}}
/>
</>
)
})

export default ReactSelect
6 changes: 5 additions & 1 deletion packages/webapp/src/locales/en-US/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
"paginatedList": {
"search": "Search",
"_search.comment": "Search text placeholder",
"filter": "Filter",
"_filter.comment": "filter text placeholder",
"noResults": "No results found.",
"_noResults.comment": "No results text in list",
"loading": "loading...",
Expand Down Expand Up @@ -219,6 +221,8 @@
"GRANT": "Grant",
"_GRANT.comment": "Tag group for grants that a community based organization have recieved or are trying to receieve funding for",
"OTHER": "Other",
"_OTHER.comment": "Tag group for tags that do not fall into the other groups"
"_OTHER.comment": "Tag group for tags that do not fall into the other groups",
"ALL": "All",
"_ALL.comment": "Used when referring to all tag groups"
}
}
8 changes: 8 additions & 0 deletions packages/webapp/src/utils/consts/TAG_CATEGORIES.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { TagCategory } from '@cbosuite/schema/lib/client-types'
const TAG_CATEGORIES: TagCategory[] = ['SDOH', 'PROGRAM', 'GRANT', 'OTHER', 'ALL']

export default TAG_CATEGORIES

0 comments on commit 87ddf5a

Please sign in to comment.