diff --git a/apps/portal/src/components/offers/constants.ts b/apps/portal/src/components/offers/constants.ts index 63a57d0e..10e3b0b1 100644 --- a/apps/portal/src/components/offers/constants.ts +++ b/apps/portal/src/components/offers/constants.ts @@ -110,9 +110,30 @@ export const educationFieldOptions = [ ]; export enum FieldError { - NonNegativeNumber = 'Please fill in a non-negative number in this field.', - Number = 'Please fill in a number in this field.', - Required = 'Please fill in this field.', + NON_NEGATIVE_NUMBER = 'Please fill in a non-negative number in this field.', + NUMBER = 'Please fill in a number in this field.', + REQUIRED = 'Please fill in this field.', } export const OVERALL_TAB = 'Overall'; + +export enum ProfileDetailTab { + ANALYSIS = 'Offer Engine Analysis', + BACKGROUND = 'Background', + OFFERS = 'Offers', +} + +export const profileDetailTabs = [ + { + label: ProfileDetailTab.OFFERS, + value: ProfileDetailTab.OFFERS, + }, + { + label: ProfileDetailTab.BACKGROUND, + value: ProfileDetailTab.BACKGROUND, + }, + { + label: ProfileDetailTab.ANALYSIS, + value: ProfileDetailTab.ANALYSIS, + }, +]; diff --git a/apps/portal/src/components/offers/offersSubmission/analysis/OfferAnalysis.tsx b/apps/portal/src/components/offers/offerAnalysis/OfferAnalysis.tsx similarity index 71% rename from apps/portal/src/components/offers/offersSubmission/analysis/OfferAnalysis.tsx rename to apps/portal/src/components/offers/offerAnalysis/OfferAnalysis.tsx index 19d944d1..67c9c9e1 100644 --- a/apps/portal/src/components/offers/offersSubmission/analysis/OfferAnalysis.tsx +++ b/apps/portal/src/components/offers/offerAnalysis/OfferAnalysis.tsx @@ -2,11 +2,9 @@ import { useEffect } from 'react'; import { useState } from 'react'; import { HorizontalDivider, Spinner, Tabs } from '@tih/ui'; -import { trpc } from '~/utils/trpc'; - import OfferPercentileAnalysisText from './OfferPercentileAnalysisText'; import OfferProfileCard from './OfferProfileCard'; -import { OVERALL_TAB } from '../../constants'; +import { OVERALL_TAB } from '../constants'; import type { Analysis, @@ -29,10 +27,18 @@ function OfferAnalysisContent({ tab, }: OfferAnalysisContentProps) { if (!offerAnalysis || !offer || offerAnalysis.noOfOffers === 0) { + if (tab === OVERALL_TAB) { + return ( +

+ You are the first to submit an offer for your job title and YOE! Check + back later when there are more submissions. +

+ ); + } return (

- You are the first to submit an offer for these companies! Check back - later when there are more submissions. + You are the first to submit an offer for this company, job title and + YOE! Check back later when there are more submissions.

); } @@ -55,12 +61,17 @@ function OfferAnalysisContent({ } type OfferAnalysisProps = Readonly<{ - profileId?: string; + allAnalysis?: ProfileAnalysis | null; + isError: boolean; + isLoading: boolean; }>; -export default function OfferAnalysis({ profileId }: OfferAnalysisProps) { +export default function OfferAnalysis({ + allAnalysis, + isError, + isLoading, +}: OfferAnalysisProps) { const [tab, setTab] = useState(OVERALL_TAB); - const [allAnalysis, setAllAnalysis] = useState(null); const [analysis, setAnalysis] = useState(null); useEffect(() => { @@ -77,22 +88,6 @@ export default function OfferAnalysis({ profileId }: OfferAnalysisProps) { } }, [tab, allAnalysis]); - if (!profileId) { - return null; - } - - const getAnalysisResult = trpc.useQuery( - ['offers.analysis.get', { profileId }], - { - onError(error) { - console.error(error.message); - }, - onSuccess(data) { - setAllAnalysis(data); - }, - }, - ); - const tabOptions = [ { label: OVERALL_TAB, @@ -107,18 +102,13 @@ export default function OfferAnalysis({ profileId }: OfferAnalysisProps) { return ( analysis && (
-
- Result -
- {getAnalysisResult.isError && ( + {isError && (

An error occurred while generating profile analysis.

)} - {getAnalysisResult.isLoading && ( - - )} - {!getAnalysisResult.isError && !getAnalysisResult.isLoading && ( + {isLoading && } + {!isError && !isLoading && (
; + +export default function OfferPercentileAnalysisText({ + tab, + companyName, + offerAnalysis: { noOfOffers, percentile }, +}: OfferPercentileAnalysisTextProps) { + return tab === OVERALL_TAB ? ( +

+ Your highest offer is from {companyName}, which is{' '} + {percentile.toFixed(1)} percentile out of {noOfOffers}{' '} + offers received for the same job title and YOE(±1) in the last year. +

+ ) : ( +

+ Your offer from {companyName} is {percentile.toFixed(1)}{' '} + percentile out of {noOfOffers} offers received in {companyName} for + the same job title and YOE(±1) in the last year. +

+ ); +} diff --git a/apps/portal/src/components/offers/offersSubmission/analysis/OfferProfileCard.tsx b/apps/portal/src/components/offers/offerAnalysis/OfferProfileCard.tsx similarity index 54% rename from apps/portal/src/components/offers/offersSubmission/analysis/OfferProfileCard.tsx rename to apps/portal/src/components/offers/offerAnalysis/OfferProfileCard.tsx index 50bbcd06..af786c4b 100644 --- a/apps/portal/src/components/offers/offersSubmission/analysis/OfferProfileCard.tsx +++ b/apps/portal/src/components/offers/offerAnalysis/OfferProfileCard.tsx @@ -1,10 +1,14 @@ +import { + BuildingOffice2Icon, + CalendarDaysIcon, +} from '@heroicons/react/24/outline'; import { JobType } from '@prisma/client'; import { HorizontalDivider } from '~/../../../packages/ui/dist'; import { convertMoneyToString } from '~/utils/offers/currency'; import { formatDate } from '~/utils/offers/time'; -import ProfilePhotoHolder from '../../profile/ProfilePhotoHolder'; +import ProfilePhotoHolder from '../profile/ProfilePhotoHolder'; import type { AnalysisOffer } from '~/types/offers'; @@ -27,29 +31,37 @@ export default function OfferProfileCard({ }, }: OfferProfileCardProps) { return ( -
-
-
+
+
+
-

{profileName}

-

Previous company: {previousCompanies[0]}

-

YOE: {totalYoe} year(s)

+

{profileName}

+
+ + Current: + {previousCompanies[0]} +
+
+ + YOE: + {totalYoe} +
-
+
-

{title}

-

+

{title}

+

Company: {company.name}, {location}

-

Level: {level}

+

Level: {level}

-

{formatDate(monthYearReceived)}

+

{formatDate(monthYearReceived)}

{jobType === JobType.FULLTIME ? `${convertMoneyToString(income)} / year` diff --git a/apps/portal/src/components/offers/offersSubmission/OfferProfileSave.tsx b/apps/portal/src/components/offers/offersSubmission/OffersProfileSave.tsx similarity index 98% rename from apps/portal/src/components/offers/offersSubmission/OfferProfileSave.tsx rename to apps/portal/src/components/offers/offersSubmission/OffersProfileSave.tsx index 9ba39af3..f113ffdb 100644 --- a/apps/portal/src/components/offers/offersSubmission/OfferProfileSave.tsx +++ b/apps/portal/src/components/offers/offersSubmission/OffersProfileSave.tsx @@ -16,7 +16,7 @@ type OfferProfileSaveProps = Readonly<{ token?: string; }>; -export default function OfferProfileSave({ +export default function OffersProfileSave({ profileId, token, }: OfferProfileSaveProps) { diff --git a/apps/portal/src/components/offers/offersSubmission/OffersSubmissionForm.tsx b/apps/portal/src/components/offers/offersSubmission/OffersSubmissionForm.tsx index 931e94f3..95d43be8 100644 --- a/apps/portal/src/components/offers/offersSubmission/OffersSubmissionForm.tsx +++ b/apps/portal/src/components/offers/offersSubmission/OffersSubmissionForm.tsx @@ -6,8 +6,7 @@ import { JobType } from '@prisma/client'; import { Button } from '@tih/ui'; import { Breadcrumbs } from '~/components/offers/Breadcrumb'; -import OfferAnalysis from '~/components/offers/offersSubmission/analysis/OfferAnalysis'; -import OfferProfileSave from '~/components/offers/offersSubmission/OfferProfileSave'; +import OffersProfileSave from '~/components/offers/offersSubmission/OffersProfileSave'; import BackgroundForm from '~/components/offers/offersSubmission/submissionForm/BackgroundForm'; import OfferDetailsForm from '~/components/offers/offersSubmission/submissionForm/OfferDetailsForm'; import type { @@ -20,7 +19,12 @@ import { cleanObject, removeInvalidMoneyData } from '~/utils/offers/form'; import { getCurrentMonth, getCurrentYear } from '~/utils/offers/time'; import { trpc } from '~/utils/trpc'; -import type { CreateOfferProfileResponse } from '~/types/offers'; +import OfferAnalysis from '../offerAnalysis/OfferAnalysis'; + +import type { + CreateOfferProfileResponse, + ProfileAnalysis, +} from '~/types/offers'; const defaultOfferValues = { comments: '', @@ -78,6 +82,7 @@ export default function OffersSubmissionForm({ id: profileId || '', token: token || '', }); + const [analysis, setAnalysis] = useState(null); const pageRef = useRef(null); const scrollToTop = () => @@ -88,6 +93,18 @@ export default function OffersSubmissionForm({ }); const { handleSubmit, trigger } = formMethods; + const generateAnalysisMutation = trpc.useMutation( + ['offers.analysis.generate'], + { + onError(error) { + console.error(error.message); + }, + onSuccess(data) { + setAnalysis(data); + }, + }, + ); + const formSteps: Array = [ { component: ( @@ -107,14 +124,21 @@ export default function OffersSubmissionForm({ label: 'Background', }, { - component: , + component: ( + + ), hasNext: true, hasPrevious: false, label: 'Analysis', }, { component: ( - ; - -export default function OfferPercentileAnalysisText({ - tab, - companyName, - offerAnalysis: { noOfOffers, percentile }, -}: OfferPercentileAnalysisTextProps) { - return tab === 'Overall' ? ( -

- Your highest offer is from {companyName}, which is{' '} - {percentile} percentile out of {noOfOffers} offers received - for the same job title and YOE(+/-1) in the last year. -

- ) : ( -

- Your offer from {companyName} is {percentile} percentile out - of {noOfOffers} offers received in {companyName} for the same job - title and YOE(+/-1) in the last year. -

- ); -} diff --git a/apps/portal/src/components/offers/offersSubmission/submissionForm/BackgroundForm.tsx b/apps/portal/src/components/offers/offersSubmission/submissionForm/BackgroundForm.tsx index 43acb49f..aaa25e54 100644 --- a/apps/portal/src/components/offers/offersSubmission/submissionForm/BackgroundForm.tsx +++ b/apps/portal/src/components/offers/offersSubmission/submissionForm/BackgroundForm.tsx @@ -39,8 +39,8 @@ function YoeSection() { required={true} type="number" {...register(`background.totalYoe`, { - min: { message: FieldError.NonNegativeNumber, value: 0 }, - required: FieldError.Required, + min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, + required: FieldError.REQUIRED, valueAsNumber: true, })} /> @@ -52,7 +52,7 @@ function YoeSection() { label="Specific YOE 1" type="number" {...register(`background.specificYoes.0.yoe`, { - min: { message: FieldError.NonNegativeNumber, value: 0 }, + min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, valueAsNumber: true, })} /> @@ -68,7 +68,7 @@ function YoeSection() { label="Specific YOE 2" type="number" {...register(`background.specificYoes.1.yoe`, { - min: { message: FieldError.NonNegativeNumber, value: 0 }, + min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, valueAsNumber: true, })} /> @@ -128,7 +128,7 @@ function FullTimeJobFields() { startAddOnType="label" type="number" {...register(`background.experiences.0.totalCompensation.value`, { - min: { message: FieldError.NonNegativeNumber, value: 0 }, + min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, valueAsNumber: true, })} /> @@ -158,7 +158,7 @@ function FullTimeJobFields() { label="Duration (months)" type="number" {...register(`background.experiences.0.durationInMonths`, { - min: { message: FieldError.NonNegativeNumber, value: 0 }, + min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, valueAsNumber: true, })} /> @@ -211,7 +211,7 @@ function InternshipJobFields() { startAddOnType="label" type="number" {...register(`background.experiences.0.monthlySalary.value`, { - min: { message: FieldError.NonNegativeNumber, value: 0 }, + min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, valueAsNumber: true, })} /> diff --git a/apps/portal/src/components/offers/offersSubmission/submissionForm/OfferDetailsForm.tsx b/apps/portal/src/components/offers/offersSubmission/submissionForm/OfferDetailsForm.tsx index 474fd0b0..e7529c42 100644 --- a/apps/portal/src/components/offers/offersSubmission/submissionForm/OfferDetailsForm.tsx +++ b/apps/portal/src/components/offers/offersSubmission/submissionForm/OfferDetailsForm.tsx @@ -72,7 +72,7 @@ function FullTimeOfferDetailsForm({ placeholder={emptyOption} required={true} {...register(`offers.${index}.offersFullTime.title`, { - required: FieldError.Required, + required: FieldError.REQUIRED, })} />
@@ -99,7 +99,7 @@ function FullTimeOfferDetailsForm({ placeholder="e.g. L4, Junior" required={true} {...register(`offers.${index}.offersFullTime.level`, { - required: FieldError.Required, + required: FieldError.REQUIRED, })} />
@@ -112,7 +112,7 @@ function FullTimeOfferDetailsForm({ placeholder={emptyOption} required={true} {...register(`offers.${index}.location`, { - required: FieldError.Required, + required: FieldError.REQUIRED, })} />
@@ -135,7 +135,7 @@ function FullTimeOfferDetailsForm({ {...register( `offers.${index}.offersFullTime.totalCompensation.currency`, { - required: FieldError.Required, + required: FieldError.REQUIRED, }, )} /> @@ -153,8 +153,8 @@ function FullTimeOfferDetailsForm({ {...register( `offers.${index}.offersFullTime.totalCompensation.value`, { - min: { message: FieldError.NonNegativeNumber, value: 0 }, - required: FieldError.Required, + min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, + required: FieldError.REQUIRED, valueAsNumber: true, }, )} @@ -171,7 +171,7 @@ function FullTimeOfferDetailsForm({ {...register( `offers.${index}.offersFullTime.baseSalary.currency`, { - required: FieldError.Required, + required: FieldError.REQUIRED, }, )} /> @@ -185,8 +185,8 @@ function FullTimeOfferDetailsForm({ startAddOnType="label" type="number" {...register(`offers.${index}.offersFullTime.baseSalary.value`, { - min: { message: FieldError.NonNegativeNumber, value: 0 }, - required: FieldError.Required, + min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, + required: FieldError.REQUIRED, valueAsNumber: true, })} /> @@ -198,7 +198,7 @@ function FullTimeOfferDetailsForm({ label="Currency" options={CURRENCY_OPTIONS} {...register(`offers.${index}.offersFullTime.bonus.currency`, { - required: FieldError.Required, + required: FieldError.REQUIRED, })} /> } @@ -211,8 +211,8 @@ function FullTimeOfferDetailsForm({ startAddOnType="label" type="number" {...register(`offers.${index}.offersFullTime.bonus.value`, { - min: { message: FieldError.NonNegativeNumber, value: 0 }, - required: FieldError.Required, + min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, + required: FieldError.REQUIRED, valueAsNumber: true, })} /> @@ -226,7 +226,7 @@ function FullTimeOfferDetailsForm({ label="Currency" options={CURRENCY_OPTIONS} {...register(`offers.${index}.offersFullTime.stocks.currency`, { - required: FieldError.Required, + required: FieldError.REQUIRED, })} /> } @@ -239,8 +239,8 @@ function FullTimeOfferDetailsForm({ startAddOnType="label" type="number" {...register(`offers.${index}.offersFullTime.stocks.value`, { - min: { message: FieldError.NonNegativeNumber, value: 0 }, - required: FieldError.Required, + min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, + required: FieldError.REQUIRED, valueAsNumber: true, })} /> @@ -300,7 +300,7 @@ function InternshipOfferDetailsForm({ required={true} {...register(`offers.${index}.offersIntern.title`, { minLength: 1, - required: FieldError.Required, + required: FieldError.REQUIRED, })} />
@@ -330,7 +330,7 @@ function InternshipOfferDetailsForm({ placeholder={emptyOption} required={true} {...register(`offers.${index}.location`, { - required: FieldError.Required, + required: FieldError.REQUIRED, })} />
@@ -343,7 +343,7 @@ function InternshipOfferDetailsForm({ placeholder={emptyOption} required={true} {...register(`offers.${index}.offersIntern.internshipCycle`, { - required: FieldError.Required, + required: FieldError.REQUIRED, })} /> @@ -365,7 +365,7 @@ function InternshipOfferDetailsForm({ monthRequired={true} yearLabel="" {...register(`offers.${index}.monthYearReceived`, { - required: FieldError.Required, + required: FieldError.REQUIRED, })} />
@@ -380,7 +380,7 @@ function InternshipOfferDetailsForm({ {...register( `offers.${index}.offersIntern.monthlySalary.currency`, { - required: FieldError.Required, + required: FieldError.REQUIRED, }, )} /> @@ -396,8 +396,8 @@ function InternshipOfferDetailsForm({ startAddOnType="label" type="number" {...register(`offers.${index}.offersIntern.monthlySalary.value`, { - min: { message: FieldError.NonNegativeNumber, value: 0 }, - required: FieldError.Required, + min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, + required: FieldError.REQUIRED, valueAsNumber: true, })} /> diff --git a/apps/portal/src/components/offers/profile/OfferCard.tsx b/apps/portal/src/components/offers/profile/OfferCard.tsx index 2e779a42..e9d2d7f2 100644 --- a/apps/portal/src/components/offers/profile/OfferCard.tsx +++ b/apps/portal/src/components/offers/profile/OfferCard.tsx @@ -58,52 +58,64 @@ export default function OfferCard({ } function BottomSection() { + if ( + !totalCompensation && + !monthlySalary && + !negotiationStrategy && + !otherComment + ) { + return null; + } + return ( -
-
-
- -

- {totalCompensation - ? `TC: ${totalCompensation}` - : `Monthly Salary: ${monthlySalary}`} -

+ <> + +
+
+ {totalCompensation || + (monthlySalary && ( +
+ +

+ {totalCompensation && `TC: ${totalCompensation}`} + {monthlySalary && `Monthly Salary: ${monthlySalary}`} +

+
+ ))} + {totalCompensation && ( +
+

+ Base / year: {base} ⋅ Stocks / year: {stocks} ⋅ Bonus / year:{' '} + {bonus} +

+
+ )}
- - {totalCompensation && ( -
-

- Base / year: {base} ⋅ Stocks / year: {stocks} ⋅ Bonus / year:{' '} - {bonus} -

+ {negotiationStrategy && ( +
+
+ + + "{negotiationStrategy}" + +
)} -
- {negotiationStrategy && ( -
-
- - - "{negotiationStrategy}" - + {otherComment && ( +
+
+ + "{otherComment}" +
-
- )} - {otherComment && ( -
-
- - "{otherComment}" -
-
- )} -
+ )} +
+ ); } return (
-
); diff --git a/apps/portal/src/components/offers/profile/ProfileDetails.tsx b/apps/portal/src/components/offers/profile/ProfileDetails.tsx index bf3dd6b2..38eecd5c 100644 --- a/apps/portal/src/components/offers/profile/ProfileDetails.tsx +++ b/apps/portal/src/components/offers/profile/ProfileDetails.tsx @@ -1,5 +1,10 @@ -import { AcademicCapIcon, BriefcaseIcon } from '@heroicons/react/24/outline'; -import { Spinner } from '@tih/ui'; +import { useState } from 'react'; +import { + AcademicCapIcon, + ArrowPathIcon, + BriefcaseIcon, +} from '@heroicons/react/24/outline'; +import { Button, Spinner } from '@tih/ui'; import EducationCard from '~/components/offers/profile/EducationCard'; import OfferCard from '~/components/offers/profile/OfferCard'; @@ -8,22 +13,143 @@ import type { OfferDisplayData, } from '~/components/offers/types'; -import type { ProfileAnalysis } from '~/types/offers'; +import { trpc } from '~/utils/trpc'; -type ProfileHeaderProps = Readonly<{ +import { ProfileDetailTab } from '../constants'; +import OfferAnalysis from '../offerAnalysis/OfferAnalysis'; + +import { ProfileAnalysis } from '~/types/offers'; + +type ProfileOffersProps = Readonly<{ + offers: Array; +}>; + +function ProfileOffers({ offers }: ProfileOffersProps) { + if (offers.length !== 0) { + return ( + <> + {offers.map((offer) => ( + + ))} + + ); + } + return ( +
+ + No offer is attached. +
+ ); +} + +type ProfileBackgroundProps = Readonly<{ + background?: BackgroundDisplayData; +}>; + +function ProfileBackground({ background }: ProfileBackgroundProps) { + if (!background?.experiences?.length && !background?.educations?.length) { + return ( +
+

No background information available.

+
+ ); + } + + return ( + <> + {background?.experiences?.length > 0 && ( + <> +
+ + Work Experience +
+ + + )} + {background?.educations?.length > 0 && ( + <> +
+ + Education +
+ + + )} + + ); +} + +type ProfileAnalysisProps = Readonly<{ + analysis?: ProfileAnalysis; + isEditable: boolean; + profileId: string; +}>; + +function ProfileAnalysis({ + analysis: profileAnalysis, + profileId, + isEditable, +}: ProfileAnalysisProps) { + const [analysis, setAnalysis] = useState(profileAnalysis); + const generateAnalysisMutation = trpc.useMutation( + ['offers.analysis.generate'], + { + onError(error) { + console.error(error.message); + }, + onSuccess(data) { + if (data) { + setAnalysis(data); + } + }, + }, + ); + + if (generateAnalysisMutation.isLoading) { + return ( +
+ +
+ ); + } + + return ( +
+ + {isEditable && ( +
+
+ )} +
+ ); +} + +type ProfileDetailsProps = Readonly<{ analysis?: ProfileAnalysis; background?: BackgroundDisplayData; + isEditable: boolean; isLoading: boolean; offers: Array; - selectedTab: string; + profileId: string; + selectedTab: ProfileDetailTab; }>; export default function ProfileDetails({ + analysis, background, isLoading, offers, selectedTab, -}: ProfileHeaderProps) { + profileId, + isEditable, +}: ProfileDetailsProps) { if (isLoading) { return (
@@ -31,46 +157,20 @@ export default function ProfileDetails({
); } - if (selectedTab === 'offers') { - if (offers.length !== 0) { - return ( - <> - {offers.map((offer) => ( - - ))} - - ); - } - return ( -
- - No offer is attached. -
- ); + if (selectedTab === ProfileDetailTab.OFFERS) { + return ; + } + if (selectedTab === ProfileDetailTab.BACKGROUND) { + return ; } - if (selectedTab === 'background') { + if (selectedTab === ProfileDetailTab.ANALYSIS) { return ( - <> - {background?.experiences && background?.experiences.length > 0 && ( - <> -
- - Work Experience -
- - - )} - {background?.educations && background?.educations.length > 0 && ( - <> -
- - Education -
- - - )} - + ); } - return
Detail page for {selectedTab}
; + return null; } diff --git a/apps/portal/src/components/offers/profile/ProfileHeader.tsx b/apps/portal/src/components/offers/profile/ProfileHeader.tsx index 4ef3c581..4a0d944b 100644 --- a/apps/portal/src/components/offers/profile/ProfileHeader.tsx +++ b/apps/portal/src/components/offers/profile/ProfileHeader.tsx @@ -13,13 +13,16 @@ import type { BackgroundDisplayData } from '~/components/offers/types'; import { getProfileEditPath } from '~/utils/offers/link'; +import type { ProfileDetailTab } from '../constants'; +import { profileDetailTabs } from '../constants'; + type ProfileHeaderProps = Readonly<{ background?: BackgroundDisplayData; handleDelete: () => void; isEditable: boolean; isLoading: boolean; - selectedTab: string; - setSelectedTab: (tab: string) => void; + selectedTab: ProfileDetailTab; + setSelectedTab: (tab: ProfileDetailTab) => void; }>; export default function ProfileHeader({ @@ -139,9 +142,9 @@ export default function ProfileHeader({ Current: - {`${experiences[0]?.companyName || ''} ${ - experiences[0]?.jobLevel || '' - } ${experiences[0]?.jobTitle || ''}`} + {`${experiences[0].companyName || ''} ${ + experiences[0].jobLevel || '' + } ${experiences[0].jobTitle || ''}`}
)} @@ -165,20 +168,7 @@ export default function ProfileHeader({
setSelectedTab(value)} /> diff --git a/apps/portal/src/pages/offers/profile/[offerProfileId].tsx b/apps/portal/src/pages/offers/profile/[offerProfileId].tsx index 6d6c0173..4ac1d3ba 100644 --- a/apps/portal/src/pages/offers/profile/[offerProfileId].tsx +++ b/apps/portal/src/pages/offers/profile/[offerProfileId].tsx @@ -2,6 +2,7 @@ import Error from 'next/error'; import { useRouter } from 'next/router'; import { useState } from 'react'; +import { ProfileDetailTab } from '~/components/offers/constants'; import ProfileComments from '~/components/offers/profile/ProfileComments'; import ProfileDetails from '~/components/offers/profile/ProfileDetails'; import ProfileHeader from '~/components/offers/profile/ProfileHeader'; @@ -27,7 +28,9 @@ export default function OfferProfile() { const [background, setBackground] = useState(); const [offers, setOffers] = useState>([]); - const [selectedTab, setSelectedTab] = useState('offers'); + const [selectedTab, setSelectedTab] = useState( + ProfileDetailTab.OFFERS, + ); const [analysis, setAnalysis] = useState(); const getProfileQuery = trpc.useQuery( @@ -163,8 +166,10 @@ export default function OfferProfile() {
diff --git a/apps/portal/src/server/router/offers/offers-analysis-router.ts b/apps/portal/src/server/router/offers/offers-analysis-router.ts index 2772dd6a..751e6cd2 100644 --- a/apps/portal/src/server/router/offers/offers-analysis-router.ts +++ b/apps/portal/src/server/router/offers/offers-analysis-router.ts @@ -321,18 +321,18 @@ export const offersAnalysisRouter = createRouter() similarOffers, ); const overallPercentile = - similarOffers.length === 0 + similarOffers.length <= 1 ? 100 - : (100 * overallIndex) / similarOffers.length; + : 100 - (100 * overallIndex) / (similarOffers.length - 1); const companyIndex = searchOfferPercentile( overallHighestOffer, similarCompanyOffers, ); const companyPercentile = - similarCompanyOffers.length === 0 + similarCompanyOffers.length <= 1 ? 100 - : (100 * companyIndex) / similarCompanyOffers.length; + : 100 - (100 * companyIndex) / (similarCompanyOffers.length - 1); // FIND TOP >=90 PERCENTILE OFFERS, DOESN'T GIVE 100th PERCENTILE // e.g. If there only 4 offers, it gives the 2nd and 3rd offer