From bee8e120b49bf4083de3d82a30f5c92cec4dc554 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Thu, 30 Apr 2026 17:07:14 +0530 Subject: [PATCH] PM-4992 Update talent search view --- .../TalentSearchPage.module.scss | 34 +-- .../TalentSearchPage/TalentSearchPage.tsx | 231 +++++------------- 2 files changed, 81 insertions(+), 184 deletions(-) diff --git a/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.module.scss b/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.module.scss index 5a6dd85e6..22116c969 100644 --- a/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.module.scss +++ b/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.module.scss @@ -3,17 +3,20 @@ .container { display: flex; flex-direction: column; - gap: $sp-4; } :global([class*='ContentLayout-module_content-outer']) { - margin: 24px auto 0 !important; + margin: 0 auto 0 !important; } :global([class*='ContentLayout-module_content__']) { padding-bottom: 0 !important; } +:global([class*='BreadCrumb-module_breadcrumb']) { + display: none; +} + .pageArea { position: relative; @include substractPagePaddings; @@ -32,9 +35,9 @@ .pageBody { display: grid; grid-template-columns: 443px 1fr; - gap: 40px; + gap: 24px; margin-top: -232px; - padding: $sp-4 $sp-12 $sp-14 $sp-12; + padding: $sp-2 $sp-8 $sp-10 $sp-8; position: relative; z-index: 1; font-family: $font-roboto; @@ -62,17 +65,17 @@ background: $tc-white; border: 0; border-radius: 16px; - padding: 32px; + padding: 20px; display: flex; flex-direction: column; - gap: $sp-3; + gap: $sp-2; } .sidebar .panel + .panel { border-top-left-radius: 0; border-top-right-radius: 0; border-top: 0; - padding-top: 20px; + padding-top: 14px; } .panelTitle { @@ -107,9 +110,9 @@ } .jobDescriptionField { - margin-top: 0.5rem; + margin-top: 0; :global(textarea) { - min-height: 442px; + min-height: 170px; resize: vertical; color: $black-100; font-size: 14px; @@ -119,7 +122,7 @@ .aiActions { display: flex; - gap: $sp-2; + gap: $sp-1; :global(button) { font-size: 14px; @@ -134,6 +137,7 @@ } .filterBlock { + margin-bottom: 2px; :global([class*='__value-container']) { min-height: 18px; } @@ -175,8 +179,8 @@ align-items: center; gap: $sp-2; color: $black-100; - font-size: 16px; - line-height: 24px; + font-size: 15px; + line-height: 21px; font-weight: 400; position: relative; cursor: pointer; @@ -242,8 +246,10 @@ } .clearFiltersWrap { - margin-top: 10px; + margin-top: 6px; align-self: flex-start; + display: flex; + gap: $sp-2; :global(button) { font-size: 14px; @@ -280,7 +286,7 @@ align-items: center; justify-content: space-between; gap: $sp-3; - padding-top: 8px; + padding-top: 0; @include ltemd { flex-direction: column; diff --git a/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.tsx b/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.tsx index d8f052b7b..b3b4fee54 100644 --- a/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.tsx +++ b/src/apps/customer-portal/src/pages/talent-search/TalentSearchPage/TalentSearchPage.tsx @@ -1,6 +1,6 @@ /* eslint-disable complexity */ /* eslint-disable react/jsx-no-bind */ -import { ChangeEvent, FC, FocusEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { ChangeEvent, FC, FocusEvent, useCallback, useMemo, useRef, useState } from 'react' import classNames from 'classnames' import { CountryLookup, useCountryLookup } from '~/libs/core' @@ -9,8 +9,6 @@ import { IconOutline, InputMultiselect, InputMultiselectOption, - InputSelect, - InputSelectOption, InputTextarea, Tooltip, } from '~/libs/ui' @@ -24,37 +22,30 @@ import { searchMembers, SearchTalent, } from '../../../lib' -import personSearchImage from '../../../lib/assets/person-search.png' import styles from './TalentSearchPage.module.scss' -type TalentSearchSortOption = 'alphabetical' | 'matching-index' - export const TalentSearchPage: FC = () => { - const skipNextAutoSearchRef = useRef(false) const searchGenerationRef = useRef(0) - const toggleDebounceTimerRef = useRef | undefined>(undefined) - const pendingToggleAutoSearchRef = useRef(false) - const hasMountedRef = useRef(false) const [lastSearchedDescription, setLastSearchedDescription] = useState('') const countryLookup: CountryLookup[] | undefined = useCountryLookup() const [jobDescription, setJobDescription] = useState('') const [isExtractingSkills, setIsExtractingSkills] = useState(false) const [errorMessage, setErrorMessage] = useState('') - const [hasSearched, setHasSearched] = useState(true) + const [hasSearched, setHasSearched] = useState(false) const [skillOptionsLoading, setSkillOptionsLoading] = useState(false) const [selectedSkills, setSelectedSkills] = useState([]) - const [sortBy, setSortBy] = useState('alphabetical') const [selectedCountries, setSelectedCountries] = useState([]) - const [onlyProfileComplete, setOnlyProfileComplete] = useState(true) + const [onlyProfileComplete, setOnlyProfileComplete] = useState(false) const [onlyOpenToWork, setOnlyOpenToWork] = useState(true) const [onlyActive, setOnlyActive] = useState(true) - const [isSearchingMembers, setIsSearchingMembers] = useState(true) + const [isSearchingMembers, setIsSearchingMembers] = useState(false) const [isLoadingMore, setIsLoadingMore] = useState(false) const [results, setResults] = useState([]) const [totalResults, setTotalResults] = useState(0) const [currentPage, setCurrentPage] = useState(1) + const [lastAppliedSearchSignature, setLastAppliedSearchSignature] = useState('') const countryNameByCode = useMemo((): Map => new Map( (countryLookup || []) .filter(country => country.countryCode && country.country) @@ -81,17 +72,22 @@ export const TalentSearchPage: FC = () => { ) const hasSkillSearch = selectedSkills.length > 0 - const hasActiveFilters = hasSkillSearch - || selectedCountries.length > 0 - || onlyOpenToWork - || onlyActive - const shouldShowIntroState = !hasSearched && !hasActiveFilters - const activeSort: TalentSearchSortOption = hasSkillSearch ? 'matching-index' : sortBy - const sortOptions = useMemo( - (): InputSelectOption[] => (hasSkillSearch - ? [{ label: 'Matching Index', value: 'matching-index' }] - : [{ label: 'Alphabetical', value: 'alphabetical' }]), - [hasSkillSearch], + const shouldShowIntroState = !hasSearched + const currentSearchSignature = useMemo( + (): string => JSON.stringify({ + countries: selectedCountryCodesList + .slice() + .sort(), + openToWork: onlyOpenToWork, + profileComplete: onlyProfileComplete, + recentlyActive: onlyActive, + skills: selectedSkills + .map(skill => String(skill.value || '') + .trim()) + .filter(Boolean) + .sort(), + }), + [onlyActive, onlyOpenToWork, onlyProfileComplete, selectedCountryCodesList, selectedSkills], ) // Order comes from reports-api (sortBy/sortOrder on each request) so pagination stays globally consistent. @@ -247,26 +243,14 @@ export const TalentSearchPage: FC = () => { }, [onlyActive, onlyOpenToWork, onlyProfileComplete, selectedCountryCodesList]) const clearAllFilters = useCallback((): void => { - searchGenerationRef.current += 1 setSelectedCountries([]) - setOnlyProfileComplete(true) + setOnlyProfileComplete(false) setOnlyOpenToWork(true) setOnlyActive(true) - setSortBy('alphabetical') setSelectedSkills([]) - setHasSearched(true) setErrorMessage('') - skipNextAutoSearchRef.current = true setLastSearchedDescription('') - runMemberSearch([], { - countries: [], - generation: searchGenerationRef.current, - openToWork: true, - page: 1, - profileComplete: true, - recentlyActive: true, - }) - }, [runMemberSearch]) + }, []) const handleAiSearch = useCallback(async (): Promise => { const normalizedDescription = jobDescription.trim() @@ -308,121 +292,47 @@ export const TalentSearchPage: FC = () => { setSelectedSkills(extractedOptions) if (extractedOptions.length === 0) { - setResults([]) - setTotalResults(0) - setHasSearched(true) setErrorMessage('No skills were extracted from the job description.') - skipNextAutoSearchRef.current = true return } - setHasSearched(true) - skipNextAutoSearchRef.current = true - const searchSucceeded = await runMemberSearch(extractedOptions, { generation, page: 1 }) - if (searchGenerationRef.current !== generation) return - - if (searchSucceeded) { - setLastSearchedDescription(normalizedDescription) - } + setLastSearchedDescription(normalizedDescription) } catch { - skipNextAutoSearchRef.current = true if (searchGenerationRef.current !== generation) return setErrorMessage('Failed to extract skills. Please try again.') - setHasSearched(true) } finally { setIsExtractingSkills(false) } - }, [isExtractingSkills, jobDescription, runMemberSearch]) - - useEffect(() => { - if ((shouldShowIntroState) || isExtractingSkills) { - if (toggleDebounceTimerRef.current) { - clearTimeout(toggleDebounceTimerRef.current) - toggleDebounceTimerRef.current = undefined - } - - pendingToggleAutoSearchRef.current = false - - return - } - - if (!hasMountedRef.current) { - hasMountedRef.current = true - - if (skipNextAutoSearchRef.current) { - skipNextAutoSearchRef.current = false - pendingToggleAutoSearchRef.current = false - if (toggleDebounceTimerRef.current) { - clearTimeout(toggleDebounceTimerRef.current) - toggleDebounceTimerRef.current = undefined - } - - return - } - - runMemberSearch(selectedSkills, { generation: searchGenerationRef.current, page: 1 }) - return - } - - if (skipNextAutoSearchRef.current) { - skipNextAutoSearchRef.current = false - if (toggleDebounceTimerRef.current) { - clearTimeout(toggleDebounceTimerRef.current) - toggleDebounceTimerRef.current = undefined - } - - pendingToggleAutoSearchRef.current = false + }, [isExtractingSkills, jobDescription]) + const handleSearch = useCallback(async (): Promise => { + if (isSearchingMembers || selectedSkills.length === 0) { return } - const runSearch = (): void => { - runMemberSearch(selectedSkills, { - generation: searchGenerationRef.current, - page: 1, - }) - } - - const shouldDebounce = pendingToggleAutoSearchRef.current || Boolean(toggleDebounceTimerRef.current) - - if (shouldDebounce) { - pendingToggleAutoSearchRef.current = false - if (toggleDebounceTimerRef.current) { - clearTimeout(toggleDebounceTimerRef.current) - toggleDebounceTimerRef.current = undefined - } - - toggleDebounceTimerRef.current = setTimeout(() => { - toggleDebounceTimerRef.current = undefined - runSearch() - }, 800) - return + setHasSearched(true) + const searchSucceeded = await runMemberSearch(selectedSkills, { + countries: selectedCountryCodesList, + openToWork: onlyOpenToWork, + page: 1, + profileComplete: onlyProfileComplete, + recentlyActive: onlyActive, + }) + if (searchSucceeded) { + setLastAppliedSearchSignature(currentSearchSignature) } - - // No debounce requested and no pending timer: execute immediately. - pendingToggleAutoSearchRef.current = false - runSearch() }, [ - hasSearched, - hasActiveFilters, - isExtractingSkills, + currentSearchSignature, + isSearchingMembers, onlyActive, onlyOpenToWork, + onlyProfileComplete, runMemberSearch, - selectedCountries, + selectedCountryCodesList, selectedSkills, - shouldShowIntroState, ]) - // Cleanup any pending debounced request on unmount. - useEffect(() => (): void => { - if (toggleDebounceTimerRef.current) { - clearTimeout(toggleDebounceTimerRef.current) - toggleDebounceTimerRef.current = undefined - } - }, []) - const handleLoadMore = useCallback((): void => { if (isLoadingMore || isSearchingMembers || !hasMoreResults) { return @@ -433,7 +343,7 @@ export const TalentSearchPage: FC = () => { page: currentPage + 1, }) }, [currentPage, hasMoreResults, isLoadingMore, isSearchingMembers, runMemberSearch, selectedSkills]) - const isSearchButtonDisabled = useMemo( + const isAiExtractButtonDisabled = useMemo( () => isExtractingSkills || !jobDescription.trim() || jobDescription.trim() === lastSearchedDescription, @@ -441,7 +351,7 @@ export const TalentSearchPage: FC = () => { ) return ( @@ -450,20 +360,12 @@ export const TalentSearchPage: FC = () => {
{errorMessage && ( @@ -495,7 +397,6 @@ export const TalentSearchPage: FC = () => {
-

Filter

{ onChange={(event: ChangeEvent) => { const value = (event.target.value || []) as InputMultiselectOption[] setSelectedSkills(value) - setHasSearched(true) if (value.length === 0) { setLastSearchedDescription('') } @@ -535,7 +435,6 @@ export const TalentSearchPage: FC = () => { checked={onlyOpenToWork} className={styles.checkboxInput} onChange={(event: ChangeEvent) => { - pendingToggleAutoSearchRef.current = true setOnlyOpenToWork(event.target.checked) }} /> @@ -548,7 +447,6 @@ export const TalentSearchPage: FC = () => { checked={onlyActive} className={styles.checkboxInput} onChange={(event: ChangeEvent) => { - pendingToggleAutoSearchRef.current = true setOnlyActive(event.target.checked) }} /> @@ -578,7 +476,6 @@ export const TalentSearchPage: FC = () => { checked={onlyProfileComplete} className={styles.checkboxInput} onChange={(event: ChangeEvent) => { - pendingToggleAutoSearchRef.current = true setOnlyProfileComplete(event.target.checked) }} /> @@ -589,6 +486,17 @@ export const TalentSearchPage: FC = () => { +
@@ -601,12 +509,10 @@ export const TalentSearchPage: FC = () => { > {shouldShowIntroState && (
- Person search -

Find the right talent

+

+ Paste a job description to AI-extract skills, or enter skills manually + to find talents +

)} @@ -621,21 +527,6 @@ export const TalentSearchPage: FC = () => {  that match your search.

-
- Sort by - ) => { - const nextSort = event.target.value || 'alphabetical' - setSortBy( - nextSort as TalentSearchSortOption, - ) - }} - /> -
)} {isSearchingMembers && (