Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/implement cost center autocomplete #31

Merged
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Fixed
enzomerca marked this conversation as resolved.
Show resolved Hide resolved

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

### Fixed

- Run schedule job only on saturday

## [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 = {
Josmar-jr marked this conversation as resolved.
Show resolved Hide resolved
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
Loading