Skip to content

Commit

Permalink
Merge pull request #31 from Josmar-jr/fix/implement-cost-center-autoc…
Browse files Browse the repository at this point in the history
…omplete

Fix/implement cost center autocomplete
  • Loading branch information
ataideverton authored Sep 26, 2024
2 parents cd1fa38 + baa375a commit b7b28d5
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 49 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Fixed

- Implement cost center autocomplete, lazyload in organization autocomplete and maxHeight prop

## [0.3.2] - 2024-08-22

### Fixed
- shows more options in the cost centers dropdown


### Removed
- [ENGINEERS-1247] - Disable cypress tests in PR level

Expand Down
90 changes: 90 additions & 0 deletions react/components/CostCentersAutocomplete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React, { useEffect, useState } from 'react'
import { useQuery } from 'react-apollo'
import { AutocompleteInput } from 'vtex.styleguide'
import { useIntl } from 'react-intl'

import { messages } from './customers-admin'
import GET_COST_CENTER_BY_ORG from '../queries/costCentersByOrg.gql'
import { SEARCH_TERM_DELAY_MS } from '../constants/debounceDelay'

interface Props {
onChange: (value: { value: string | null; label: string }) => void
organizationId?: string
}

const initialState = {
search: '',
page: 1,
pageSize: 25,
sortOrder: 'ASC',
sortedBy: 'name',
}

const CostCenterAutocomplete = ({ onChange, organizationId }: Props) => {
const { formatMessage } = useIntl()
const [costCenterTextInput, setCostCenterTextInput] = useState('')
const [debouncedSearchTerm, setDebouncedSearchTerm] =
useState(costCenterTextInput)

const { data, loading, refetch } = useQuery(GET_COST_CENTER_BY_ORG, {
variables: {
...initialState,
id: organizationId,
},
ssr: false,
notifyOnNetworkStatusChange: true,
skip: !organizationId,
})

const onClear = () => {
setCostCenterTextInput('')
onChange({ value: null, label: '' })
}

useEffect(() => {
const handler = setTimeout(() => {
setDebouncedSearchTerm(costCenterTextInput)
}, SEARCH_TERM_DELAY_MS) // 500ms delay

return () => {
clearTimeout(handler)
}
}, [costCenterTextInput])

useEffect(() => {
if (debouncedSearchTerm) {
refetch({
...initialState,
id: organizationId,
search: debouncedSearchTerm,
})
} else if (debouncedSearchTerm === '') {
onClear()
}
}, [debouncedSearchTerm])

const options = {
maxHeight: 200,
onSelect: onChange,
loading,
value: data?.getCostCentersByOrganizationId?.data?.map(
(costCenter: { id: string; name: string }) => ({
value: costCenter.id,
label: costCenter.name,
})
),
}

const input = {
onChange: (_term: string) => {
setCostCenterTextInput(_term)
},
onClear,
placeholder: formatMessage(messages.costCenter),
value: costCenterTextInput,
}

return <AutocompleteInput input={input} options={options} />
}

export default CostCenterAutocomplete
109 changes: 73 additions & 36 deletions react/components/OrganizationsAutocomplete.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useEffect, useState } from 'react'
import React, { useEffect, useState, useMemo, useCallback } from 'react'
import { useQuery } from 'react-apollo'
import { AutocompleteInput } from 'vtex.styleguide'
import { useIntl } from 'react-intl'

import { messages } from './customers-admin'
import GET_ORGANIZATIONS from '../queries/listOrganizations.gql'
import GET_ORGANIZATION_BY_ID from '../queries/getOrganization.graphql'
import { SEARCH_TERM_DELAY_MS } from '../constants/debounceDelay'

const initialState = {
status: ['active', 'on-hold', 'inactive'],
Expand All @@ -24,62 +25,97 @@ interface Props {
const OrganizationsAutocomplete = ({ onChange, organizationId }: Props) => {
const { formatMessage } = useIntl()
const [term, setTerm] = useState('')
const [hasChanged, setHasChanged] = useState(false)
const [values, setValues] = useState([] as any)
const [debouncedTerm, setDebouncedTerm] = useState(term)

const [values, setValues] = useState<Array<{ value: string; label: string }>>(
[]
)

const { data, loading, refetch } = useQuery(GET_ORGANIZATIONS, {
variables: initialState,
ssr: false,
notifyOnNetworkStatusChange: true,
})

const { data: organization } = useQuery(GET_ORGANIZATION_BY_ID, {
variables: { id: organizationId },
ssr: false,
fetchPolicy: 'network-only',
notifyOnNetworkStatusChange: true,
skip: !organizationId,
})
const { data: organization, loading: orgLoading } = useQuery(
GET_ORGANIZATION_BY_ID,
{
variables: { id: organizationId },
ssr: false,
fetchPolicy: 'network-only',
notifyOnNetworkStatusChange: true,
skip: !organizationId,
}
)

const options = {
onSelect: (value: any) => onChange(value),
loading,
value: values,
}
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedTerm(term)
}, SEARCH_TERM_DELAY_MS) // 500ms delay

const onClear = () => {
if (!hasChanged) return
setTerm('')
onChange({ value: null, label: '' })
}
return () => clearTimeout(handler)
}, [term])

useEffect(() => {
if (debouncedTerm.length > 2) {
refetch({
...initialState,
search: debouncedTerm,
})
} else if (debouncedTerm === '') {
refetch({
...initialState,
search: '',
})
}
}, [debouncedTerm, refetch])

useEffect(() => {
if (!organization) {
if (!organization?.getOrganizationById) {
return
}

const { name, id } = organization.getOrganizationById

setTerm(name)
setHasChanged(true)
onChange({ value: id, label: name })
}, [organization])
}, [organization, onChange])

useEffect(() => {
if (data?.getOrganizations?.data) {
setValues(
data.getOrganizations.data.map((item: any) => {
return {
data.getOrganizations.data.map(
(item: { id: string; name: string }) => ({
value: item.id,
label: item.name,
}
})
})
)
)
}
}, [data])

const onClear = useCallback(() => {
setTerm('')

refetch({
...initialState,
search: '',
})
onChange({ value: null, label: '' })
}, [onChange, refetch])

const options = useMemo(
() => ({
maxHeight: 250,
onSelect: onChange,
loading,
value: values,
}),
[loading, values, onChange, orgLoading]
)

useEffect(() => {
if (term && term.length > 1) {
setHasChanged(true)
refetch({
...initialState,
search: term,
Expand All @@ -89,14 +125,15 @@ const OrganizationsAutocomplete = ({ onChange, organizationId }: Props) => {
}
}, [term])

const input = {
onChange: (_term: string) => {
setTerm(_term)
},
onClear,
placeholder: formatMessage(messages.searchOrganizations),
value: term,
}
const input = useMemo(
() => ({
onChange: (_term: string) => setTerm(_term),
onClear,
placeholder: formatMessage(messages.searchOrganizations),
value: term,
}),
[term, onClear, formatMessage]
)

return <AutocompleteInput input={input} options={options} />
}
Expand Down
26 changes: 17 additions & 9 deletions react/components/customers-admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import GET_COST from '../queries/costCentersByOrg.gql'
import GET_ORGANIZATIONS from '../queries/getOrganizationsByEmail.graphql'
import ADD_USER from '../mutations/addUser.gql'
import DELETE_USER from '../mutations/deleteUser.gql'
import CostCenterAutocomplete from './CostCentersAutocomplete'

export const messages = defineMessages({
b2bInfo: {
Expand Down Expand Up @@ -455,15 +456,22 @@ const UserEdit: FC<any> = (props: any) => {
)}

{state.orgId && (
<div className="mb5">
<Dropdown
label={formatMessage(messages.costCenter)}
options={optionsCost}
value={state.costId}
onChange={(_: any, costId: string) => {
setState({ ...state, costId })
}}
/>
<div className="mb5 w-100">
<div className="flex">
<div className="w-100">
<label className="h-100">
<span className="db mt0 mb3 c-on-base t-small">
{formatMessage(messages.costCenter)}
</span>
<CostCenterAutocomplete
organizationId={state.orgId}
onChange={(event) => {
setState({ ...state, costId: event.value })
}}
/>
</label>
</div>
</div>
</div>
)}

Expand Down
1 change: 1 addition & 0 deletions react/constants/debounceDelay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const SEARCH_TERM_DELAY_MS = 500
2 changes: 1 addition & 1 deletion react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"vtex.render-runtime": "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected]/public/@types/vtex.render-runtime",
"vtex.storefront-permissions": "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected]/public/@types/vtex.storefront-permissions",
"vtex.storefront-permissions-components": "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected]/public/@types/vtex.storefront-permissions-components",
"vtex.styleguide": "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected].3/public/@types/vtex.styleguide"
"vtex.styleguide": "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected].13/public/@types/vtex.styleguide"
},
"dependencies": {
"faker": "^4.1.0",
Expand Down
6 changes: 3 additions & 3 deletions react/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -500,9 +500,9 @@ [email protected]:
version "1.23.0"
resolved "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected]/public/@types/vtex.storefront-permissions#2a12e48a1b630d6d9cd64115572bcae55a7aa756"

"vtex.styleguide@http://vtex.vtexassets.com/_v/public/typings/v1/[email protected].3/public/@types/vtex.styleguide":
version "9.146.3"
resolved "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected].3/public/@types/vtex.styleguide#05558160f29cd8f4aefe419844a4bd66e2b3fdbb"
"vtex.styleguide@http://vtex.vtexassets.com/_v/public/typings/v1/[email protected].13/public/@types/vtex.styleguide":
version "9.146.13"
resolved "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected].13/public/@types/vtex.styleguide#f4ccbc54621bf5114ddd115b6032ae320f2eba55"

zen-observable-ts@^0.8.21:
version "0.8.21"
Expand Down

0 comments on commit b7b28d5

Please sign in to comment.