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
@@ -1,17 +1,18 @@
/* eslint-disable complexity */
import { Dispatch, FC, MutableRefObject, SetStateAction, useRef, useState } from 'react'
import { ChangeEvent, Dispatch, FC, MutableRefObject, SetStateAction, useEffect, useRef, useState } from 'react'
import { bind, sortBy, trim } from 'lodash'
import { toast } from 'react-toastify'
import classNames from 'classnames'

import { BaseModal, Button, IconOutline, InputDatePicker, InputSelect, InputText } from '~/libs/ui'
import { BaseModal, Button, IconOutline, InputDatePicker, InputSelect, InputText, InputTextarea } from '~/libs/ui'
import {
updateDeleteOrCreateMemberTraitAsync,
UserProfile, UserTrait,
UserTraitCategoryNames,
UserTraitIds,
} from '~/libs/core'
import { getIndustryOptionLabel, getIndustryOptionValue, INDUSTRIES_OPTIONS } from '~/libs/shared'
import { getIndustryOptionLabel, getIndustryOptionValue, INDUSTRIES_OPTIONS, InputSkillSelector } from '~/libs/shared'
import { fetchSkillsByIds } from '~/libs/shared/lib/services/standard-skills'

import { WorkExpirenceCard } from '../WorkExpirenceCard'

Expand All @@ -32,10 +33,10 @@ const ModifyWorkExpirenceModal: FC<ModifyWorkExpirenceModalProps> = (props: Modi
= useState<boolean>(props.workExpirence?.length === 0 || false)

const [formValues, setFormValues]: [
{ [key: string]: string | boolean | Date | undefined },
Dispatch<SetStateAction<{ [key: string]: string | boolean | Date | undefined }>>
{ [key: string]: string | boolean | Date | any[] | undefined },
Dispatch<SetStateAction<{ [key: string]: string | boolean | Date | any[] | undefined }>>
]
= useState<{ [key: string]: string | boolean | Date | undefined }>({})
= useState<{ [key: string]: string | boolean | Date | any[] | undefined }>({})

const [formErrors, setFormErrors]: [
{ [key: string]: string },
Expand All @@ -56,6 +57,86 @@ const ModifyWorkExpirenceModal: FC<ModifyWorkExpirenceModalProps> = (props: Modi
]
= useState<UserTrait[] | undefined>(props.workExpirence)

const [skillNamesMap, setSkillNamesMap] = useState<Record<string, string>>({})
const [loadingSkills, setLoadingSkills] = useState<boolean>(false)
const fetchedSkillIdsRef = useRef<Set<string>>(new Set())

useEffect(() => {
if (!workExpirence) {
setLoadingSkills(false)
return
}

const allSkillIds = new Set<string>()
workExpirence.forEach((work: UserTrait) => {
if (work.associatedSkills && Array.isArray(work.associatedSkills)) {
work.associatedSkills.forEach((skillId: string) => {
if (skillId && typeof skillId === 'string') {
allSkillIds.add(skillId)
}
})
}
})

if (allSkillIds.size > 0) {
const skillIdsToFetch = Array.from(allSkillIds)
.filter(id => !fetchedSkillIdsRef.current.has(id))

if (skillIdsToFetch.length > 0) {
setLoadingSkills(true)
skillIdsToFetch.forEach(id => fetchedSkillIdsRef.current.add(id))

fetchSkillsByIds(skillIdsToFetch)
.then(skills => {
setSkillNamesMap(prevMap => {
const newMap: Record<string, string> = { ...prevMap }
skills.forEach(skill => {
if (skill.id && skill.name) {
newMap[skill.id] = skill.name
}
})
skillIdsToFetch.forEach(skillId => {
if (!newMap[skillId]) {
newMap[skillId] = skillId
}
})
return newMap
})
})
.catch(() => {
setSkillNamesMap(prevMap => {
const fallbackMap: Record<string, string> = { ...prevMap }
skillIdsToFetch.forEach(skillId => {
if (!fallbackMap[skillId]) {
fallbackMap[skillId] = skillId
}
})
return fallbackMap
})
})
.finally(() => {
setLoadingSkills(false)
})
} else {
setLoadingSkills(false)
}
} else {
// No skills to fetch
setLoadingSkills(false)
}
}, [workExpirence])

const areSkillsLoaded = (work: UserTrait): boolean => {
if (!work.associatedSkills || !Array.isArray(work.associatedSkills) || work.associatedSkills.length === 0) {
return true
}

return work.associatedSkills.every((skillId: string) => {
const skillName = skillNamesMap[skillId]
return skillName && skillName !== skillId
})
}

const industryOptions: any = sortBy(INDUSTRIES_OPTIONS)
.map(v => ({
label: getIndustryOptionLabel(v),
Expand Down Expand Up @@ -89,13 +170,16 @@ const ModifyWorkExpirenceModal: FC<ModifyWorkExpirenceModalProps> = (props: Modi
})
}

function handleFormValueChange(key: string, event: React.ChangeEvent<HTMLInputElement>): void {
function handleFormValueChange(
key: string,
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
): void {
let value: string | boolean | Date | undefined
const oldFormValues = { ...formValues }

switch (key) {
case 'currentlyWorking':
value = event.target.checked
value = (event.target as HTMLInputElement).checked
if (value) {
oldFormValues.endDate = undefined
}
Expand All @@ -116,6 +200,17 @@ const ModifyWorkExpirenceModal: FC<ModifyWorkExpirenceModalProps> = (props: Modi
})
}

function handleSkillsChange(event: ChangeEvent<HTMLInputElement>): void {
const selectedSkills = (event.target as any).value || []
setFormValues({
...formValues,
associatedSkills: selectedSkills.map((skill: any) => ({
id: skill.value || skill.id,
name: skill.label || skill.name,
})),
})
}

function resetForm(): void {
setFormValues({})
setFormErrors({})
Expand Down Expand Up @@ -173,9 +268,10 @@ const ModifyWorkExpirenceModal: FC<ModifyWorkExpirenceModalProps> = (props: Modi
: undefined

const updatedWorkExpirence: UserTrait = {
cityTown: formValues.city,
associatedSkills: (formValues.associatedSkills as any[])?.map((s: any) => s.id || s) || [],
company: companyName,
companyName,
description: (formValues.description as string) || undefined,
endDate: endDateIso,
industry: formValues.industry,
position: formValues.position,
Expand All @@ -200,20 +296,41 @@ const ModifyWorkExpirenceModal: FC<ModifyWorkExpirenceModalProps> = (props: Modi
resetForm()
}

function handleWorkExpirenceEdit(indx: number): void {
async function handleWorkExpirenceEdit(indx: number): Promise<void> {
const work: UserTrait = workExpirence ? workExpirence[indx] : {}

setEditedItemIndex(indx)

let associatedSkills: any[] = []
if (work.associatedSkills && Array.isArray(work.associatedSkills) && work.associatedSkills.length > 0) {
try {
const skills = await fetchSkillsByIds(
work.associatedSkills.filter((id): id is string => typeof id === 'string'),
)
const skillsMap = new Map(skills.map(s => [s.id, s.name]))

associatedSkills = work.associatedSkills.map((skillId: string) => ({
id: skillId,
name: skillsMap.get(skillId) || '',
}))
} catch {
associatedSkills = work.associatedSkills.map((skillId: string) => ({
id: skillId,
name: skillNamesMap[skillId] || '',
}))
}
}

setFormValues({
city: work.cityTown || work.city,
company: work.company || work.companyName,
currentlyWorking: work.working,
associatedSkills,
company: (work.company || work.companyName || '') as string,
currentlyWorking: work.working || false,
description: work.description || '',
endDate: work.timePeriodTo
? new Date(work.timePeriodTo)
: (work.endDate ? new Date(work.endDate) : undefined),
industry: work.industry,
position: work.position,
industry: work.industry || '',
position: (work.position || '') as string,
startDate: work.timePeriodFrom
? new Date(work.timePeriodFrom)
: (work.startDate ? new Date(work.startDate) : undefined),
Expand Down Expand Up @@ -286,7 +403,12 @@ const ModifyWorkExpirenceModal: FC<ModifyWorkExpirenceModalProps> = (props: Modi
className={styles.workExpirenceCardWrap}
key={uniqueKey || `${work.position}-${indx}`}
>
<WorkExpirenceCard work={work} isModalView />
<WorkExpirenceCard
work={work}
isModalView
skillNamesMap={skillNamesMap}
showSkills={!loadingSkills && areSkillsLoaded(work)}
/>
<div className={styles.actionElements}>
<Button
className={styles.ctaBtn}
Expand Down Expand Up @@ -320,6 +442,7 @@ const ModifyWorkExpirenceModal: FC<ModifyWorkExpirenceModalProps> = (props: Modi
placeholder='Enter a company'
dirty
tabIndex={0}
forceUpdateValue
type='text'
onChange={bind(handleFormValueChange, this, 'company')}
value={formValues.company as string}
Expand All @@ -332,6 +455,7 @@ const ModifyWorkExpirenceModal: FC<ModifyWorkExpirenceModalProps> = (props: Modi
dirty
tabIndex={0}
type='text'
forceUpdateValue
onChange={bind(handleFormValueChange, this, 'position')}
value={formValues.position as string}
/>
Expand Down Expand Up @@ -376,6 +500,23 @@ const ModifyWorkExpirenceModal: FC<ModifyWorkExpirenceModalProps> = (props: Modi
onChange={bind(handleFormValueChange, this, 'currentlyWorking')}
checked={formValues.currentlyWorking as boolean}
/>
<InputTextarea
name='description'
label='Description'
placeholder='Describe your role and achievements at this company'
dirty
tabIndex={0}
onChange={bind(handleFormValueChange, this, 'description')}
value={formValues.description as string}
rows={4}
/>
<InputSkillSelector
label='Associated Skills'
placeholder='Type to search and add skills...'
value={formValues.associatedSkills as any[]}
onChange={handleSkillsChange}
loading={false}
/>
</form>
) : (
<Button
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Dispatch, FC, SetStateAction, useEffect, useMemo, useState } from 'react'
import { Dispatch, FC, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react'

Choose a reason for hiding this comment

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

[❗❗ correctness]
The useRef import has been removed but is still used in the code. Ensure that fetchedSkillIdsRef is no longer needed or re-import useRef if it is still required.

import { useSearchParams } from 'react-router-dom'
import { SWRResponse } from 'swr'

import { MemberTraitsAPI, useMemberTraits, UserProfile, UserTrait, UserTraitIds } from '~/libs/core'
import { MemberTraitsAPI, useMemberTraits, UserProfile, UserSkill, UserTrait, UserTraitIds } from '~/libs/core'
import { useSkillsByIds } from '~/libs/shared/lib/services/standard-skills'

import { EDIT_MODE_QUERY_PARAM, profileEditModes } from '../../config'
import { AddButton, EditMemberPropertyBtn, EmptySection } from '../../components'
Expand Down Expand Up @@ -31,6 +33,66 @@ const WorkExpirence: FC<WorkExpirenceProps> = (props: WorkExpirenceProps) => {
const workExpirence: UserTrait[] | undefined
= useMemo(() => memberWorkExpirenceTraits?.[0]?.traits?.data, [memberWorkExpirenceTraits])

// Collect all unique skill IDs from work experience entries

Choose a reason for hiding this comment

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

[⚠️ performance]
The useMemo hook is used to collect skill IDs, which is appropriate for performance optimization. However, ensure that the dependencies are correctly set to avoid unnecessary recalculations.

const allSkillIds = useMemo(() => {
if (!workExpirence) {
return []
}

const skillIdsSet = new Set<string>()
workExpirence.forEach((work: UserTrait) => {
if (work.associatedSkills && Array.isArray(work.associatedSkills)) {
work.associatedSkills.forEach((skillId: string) => {
if (skillId && typeof skillId === 'string') {
skillIdsSet.add(skillId)
}
})
}
})

return Array.from(skillIdsSet)
}, [workExpirence])

const { data: fetchedSkills, error: skillsError }: SWRResponse<UserSkill[], Error> = useSkillsByIds(
allSkillIds.length > 0 ? allSkillIds : undefined,
)

// Determine loading state: data is undefined and no error yet
const loadingSkills = fetchedSkills === undefined && !skillsError

// Build skill names map from fetched skills
const skillNamesMap = useMemo(() => {
const map: Record<string, string> = {}

if (fetchedSkills) {
fetchedSkills.forEach(skill => {
if (skill.id && skill.name) {
map[skill.id] = skill.name
}
})
}

// For skills that weren't found, use ID as fallback
allSkillIds.forEach(skillId => {
if (!map[skillId]) {
map[skillId] = skillId
}
})

return map
}, [fetchedSkills, allSkillIds])

const areSkillsLoaded = useCallback((work: UserTrait): boolean => {

Choose a reason for hiding this comment

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

[⚠️ performance]
The useCallback hook is used for areSkillsLoaded, which is good for preventing unnecessary re-creations of the function. Ensure that the dependencies are correctly set to avoid stale closures.

if (!work.associatedSkills || !Array.isArray(work.associatedSkills) || work.associatedSkills.length === 0) {
return true
}

return work.associatedSkills.every((skillId: string) => {
const skillName = skillNamesMap[skillId]
return skillName && skillName !== skillId
})
}, [skillNamesMap])

useEffect(() => {
if (props.authProfile && editMode === profileEditModes.workExperience) {
setIsEditMode(true)
Expand Down Expand Up @@ -83,6 +145,8 @@ const WorkExpirence: FC<WorkExpirenceProps> = (props: WorkExpirenceProps) => {
<WorkExpirenceCard
key={uniqueKey || `${work.position || 'experience'}-${index}`}
work={work}
skillNamesMap={skillNamesMap}
showSkills={!loadingSkills && areSkillsLoaded(work)}
/>
)
})
Expand Down
Loading
Loading