Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export async function fetchCompletedProfiles(
page: number,
perPage: number,
openToWorkFilter?: OpenToWorkFilter,
skillIds?: string[],
): Promise<CompletedProfilesResponse> {
const queryParams = new URLSearchParams({
page: String(page),
Expand All @@ -109,6 +110,14 @@ export async function fetchCompletedProfiles(
queryParams.set('openToWork', 'false')
}

if (Array.isArray(skillIds) && skillIds.length > 0) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 maintainability]
Consider using skillIds.filter(Boolean) to remove falsy values before iterating. This simplifies the code by eliminating the need for the if (id) check inside the loop.

skillIds.forEach(id => {
if (id) {
queryParams.append('skillId', String(id))
}
})
}

const response = await xhrGetAsync<any>(
`${EnvironmentConfig.REPORTS_API}/topcoder/completed-profiles?${queryParams.toString()}`,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
display: flex;
gap: $sp-4;

:global([class*='__value-container']) {
min-height: 18px;
}

@include ltemd {
flex-direction: column;
align-items: stretch;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
/* eslint-disable react/jsx-no-bind */
/* eslint-disable no-await-in-loop */
/* eslint-disable complexity */
import { ChangeEvent, FC, useEffect, useMemo, useState } from 'react'
import { ChangeEvent, FC, useCallback, useEffect, useMemo, useState } from 'react'
import useSWR, { SWRResponse } from 'swr'

import { EnvironmentConfig } from '~/config'
import { CountryLookup, useCountryLookup, UserSkill, UserSkillDisplayModes } from '~/libs/core'
import { Button, InputSelect, InputSelectOption, LoadingSpinner, Tooltip } from '~/libs/ui'
import { CountryLookup, useCountryLookup, UserSkill, UserSkillDisplayModes, xhrGetAsync } from '~/libs/core'
import {
Button,
InputMultiselect,
InputMultiselectOption,
InputSelect,
InputSelectOption,
LoadingSpinner,
Tooltip,
} from '~/libs/ui'
import { getPreferredRoleLabelByValue } from '~/libs/shared/lib/utils/roles'

import { PageWrapper } from '../../../lib'
Expand All @@ -26,15 +34,54 @@ export const ProfileCompletionPage: FC = () => {
const [selectedCountry, setSelectedCountry] = useState<string>('all')
const [currentPage, setCurrentPage] = useState<number>(1)
const [selectedOpenToWork, setSelectedOpenToWork] = useState<OpenToWorkFilter>('all')
const [selectedSkills, setSelectedSkills] = useState<InputMultiselectOption[]>([])
const [memberSkills, setMemberSkills] = useState<Map<string | number, UserSkill[]>>(new Map())
const [skillOptionsLoading, setSkillOptionsLoading] = useState<boolean>(false)
const countryLookup: CountryLookup[] | undefined = useCountryLookup()

const countryCodeFilter = selectedCountry === 'all' ? undefined : selectedCountry

const loadSkillOptions = useCallback(async (query: string): Promise<InputMultiselectOption[]> => {
Comment thread
hentrymartin marked this conversation as resolved.
setSkillOptionsLoading(true)
try {
const baseUrl = `${EnvironmentConfig.API.V5}/standardized-skills`
const params = new URLSearchParams({
size: '25',
})
if (query && query.trim().length > 0) {
params.append('term', query.trim())
}

const url = `${baseUrl}/skills/autocomplete?${params.toString()}`
const response: any = await xhrGetAsync(url)
Comment thread
hentrymartin marked this conversation as resolved.

const skills = Array.isArray(response) ? response : []

return skills
.map((skill: any) => ({
label: skill.name,
value: String(skill.id),
}))
.filter((option: InputMultiselectOption) => !!option.value)
} catch {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ correctness]
Filtering options with !!option.value might not be sufficient if value can be a falsy but valid value (e.g., 0). Ensure that this filtering logic aligns with the expected data structure.

return []
} finally {
setSkillOptionsLoading(false)
}
}, [])

const { data, error, isValidating }: SWRResponse<CompletedProfilesResponse> = useSWR(
// eslint-disable-next-line max-len
`customer-portal-completed-profiles:${countryCodeFilter || 'all'}:${selectedOpenToWork}:${currentPage}:${DEFAULT_PAGE_SIZE}`,
() => fetchCompletedProfiles(countryCodeFilter, currentPage, DEFAULT_PAGE_SIZE, selectedOpenToWork),
`customer-portal-completed-profiles:${countryCodeFilter || 'all'}:${selectedOpenToWork}:${currentPage}:${DEFAULT_PAGE_SIZE}:${selectedSkills.map(skill => skill.value)
.sort()
.join(',')}`,
() => fetchCompletedProfiles(
countryCodeFilter,
currentPage,
DEFAULT_PAGE_SIZE,
selectedOpenToWork,
selectedSkills.map(skill => skill.value),
),
{
revalidateOnFocus: false,
},
Expand Down Expand Up @@ -203,6 +250,21 @@ export const ProfileCompletionPage: FC = () => {
placeholder='Select'
/>
</div>
<div className={styles.filterWrap}>
<InputMultiselect
name='skills'
label='Skills'
placeholder='Filter by skills'
value={selectedSkills}
onFetchOptions={loadSkillOptions}
loading={skillOptionsLoading}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
Comment thread
hentrymartin marked this conversation as resolved.
const value = (event.target.value || []) as InputMultiselectOption[]
setSelectedSkills(value)
setCurrentPage(1)
}}
/>
</div>
</div>
<div className={styles.counterCard}>
<span className={styles.counterLabel}>Fully Completed Profiles</span>
Expand Down
Loading