mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-28 12:43:12 +08:00
[offers][fix] Refactor and fix offer analysis (#413)
This commit is contained in:
@ -84,7 +84,7 @@ export default function OfferProfileSave({
|
|||||||
onClick={saveProfile}
|
onClick={saveProfile}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-10">
|
<div>
|
||||||
<Button
|
<Button
|
||||||
icon={EyeIcon}
|
icon={EyeIcon}
|
||||||
label="View your profile"
|
label="View your profile"
|
||||||
|
@ -2,6 +2,7 @@ import { useRef, useState } from 'react';
|
|||||||
import type { SubmitHandler } from 'react-hook-form';
|
import type { SubmitHandler } from 'react-hook-form';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/20/solid';
|
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/20/solid';
|
||||||
|
import { JobType } from '@prisma/client';
|
||||||
import { Button } from '@tih/ui';
|
import { Button } from '@tih/ui';
|
||||||
|
|
||||||
import { Breadcrumbs } from '~/components/offers/Breadcrumb';
|
import { Breadcrumbs } from '~/components/offers/Breadcrumb';
|
||||||
@ -13,7 +14,6 @@ import type {
|
|||||||
OfferFormData,
|
OfferFormData,
|
||||||
OffersProfileFormData,
|
OffersProfileFormData,
|
||||||
} from '~/components/offers/types';
|
} from '~/components/offers/types';
|
||||||
import { JobType } from '~/components/offers/types';
|
|
||||||
import type { Month } from '~/components/shared/MonthYearPicker';
|
import type { Month } from '~/components/shared/MonthYearPicker';
|
||||||
|
|
||||||
import { cleanObject, removeInvalidMoneyData } from '~/utils/offers/form';
|
import { cleanObject, removeInvalidMoneyData } from '~/utils/offers/form';
|
||||||
@ -25,7 +25,7 @@ import type { CreateOfferProfileResponse } from '~/types/offers';
|
|||||||
const defaultOfferValues = {
|
const defaultOfferValues = {
|
||||||
comments: '',
|
comments: '',
|
||||||
companyId: '',
|
companyId: '',
|
||||||
jobType: JobType.FullTime,
|
jobType: JobType.FULLTIME,
|
||||||
location: '',
|
location: '',
|
||||||
monthYearReceived: {
|
monthYearReceived: {
|
||||||
month: getCurrentMonth() as Month,
|
month: getCurrentMonth() as Month,
|
||||||
@ -36,18 +36,18 @@ const defaultOfferValues = {
|
|||||||
|
|
||||||
export const defaultFullTimeOfferValues = {
|
export const defaultFullTimeOfferValues = {
|
||||||
...defaultOfferValues,
|
...defaultOfferValues,
|
||||||
jobType: JobType.FullTime,
|
jobType: JobType.FULLTIME,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultInternshipOfferValues = {
|
export const defaultInternshipOfferValues = {
|
||||||
...defaultOfferValues,
|
...defaultOfferValues,
|
||||||
jobType: JobType.Intern,
|
jobType: JobType.INTERN,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultOfferProfileValues = {
|
const defaultOfferProfileValues = {
|
||||||
background: {
|
background: {
|
||||||
educations: [],
|
educations: [],
|
||||||
experiences: [{ jobType: JobType.FullTime }],
|
experiences: [{ jobType: JobType.FULLTIME }],
|
||||||
specificYoes: [],
|
specificYoes: [],
|
||||||
totalYoe: 0,
|
totalYoe: 0,
|
||||||
},
|
},
|
||||||
@ -90,7 +90,12 @@ export default function OffersSubmissionForm({
|
|||||||
|
|
||||||
const formSteps: Array<FormStep> = [
|
const formSteps: Array<FormStep> = [
|
||||||
{
|
{
|
||||||
component: <OfferDetailsForm key={0} />,
|
component: (
|
||||||
|
<OfferDetailsForm
|
||||||
|
key={0}
|
||||||
|
defaultJobType={initialOfferProfileValues.offers[0].jobType}
|
||||||
|
/>
|
||||||
|
),
|
||||||
hasNext: true,
|
hasNext: true,
|
||||||
hasPrevious: false,
|
hasPrevious: false,
|
||||||
label: 'Offer details',
|
label: 'Offer details',
|
||||||
|
@ -4,7 +4,7 @@ import { HorizontalDivider, Spinner, Tabs } from '@tih/ui';
|
|||||||
|
|
||||||
import { trpc } from '~/utils/trpc';
|
import { trpc } from '~/utils/trpc';
|
||||||
|
|
||||||
import OfferPercentileAnalysis from './OfferPercentileAnalysis';
|
import OfferPercentileAnalysisText from './OfferPercentileAnalysisText';
|
||||||
import OfferProfileCard from './OfferProfileCard';
|
import OfferProfileCard from './OfferProfileCard';
|
||||||
import { OVERALL_TAB } from '../../constants';
|
import { OVERALL_TAB } from '../../constants';
|
||||||
|
|
||||||
@ -38,11 +38,12 @@ function OfferAnalysisContent({
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<OfferPercentileAnalysis
|
<OfferPercentileAnalysisText
|
||||||
companyName={offer.company.name}
|
companyName={offer.company.name}
|
||||||
offerAnalysis={offerAnalysis}
|
offerAnalysis={offerAnalysis}
|
||||||
tab={tab}
|
tab={tab}
|
||||||
/>
|
/>
|
||||||
|
<p className="mt-5">Here are some of the top offers relevant to you:</p>
|
||||||
{offerAnalysis.topPercentileOffers.map((topPercentileOffer) => (
|
{offerAnalysis.topPercentileOffers.map((topPercentileOffer) => (
|
||||||
<OfferProfileCard
|
<OfferProfileCard
|
||||||
key={topPercentileOffer.id}
|
key={topPercentileOffer.id}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import type { Analysis } from '~/types/offers';
|
|
||||||
|
|
||||||
type OfferPercentileAnalysisProps = Readonly<{
|
|
||||||
companyName: string;
|
|
||||||
offerAnalysis: Analysis;
|
|
||||||
tab: string;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export default function OfferPercentileAnalysis({
|
|
||||||
tab,
|
|
||||||
companyName,
|
|
||||||
offerAnalysis: { noOfOffers, percentile },
|
|
||||||
}: OfferPercentileAnalysisProps) {
|
|
||||||
return tab === 'Overall' ? (
|
|
||||||
<p>
|
|
||||||
Your highest offer is from {companyName}, which is {percentile} percentile
|
|
||||||
out of {noOfOffers} offers received for the same job type, same level, and
|
|
||||||
same YOE(+/-1) in the last year.
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Your offer from {companyName} is {percentile} percentile out of{' '}
|
|
||||||
{noOfOffers} offers received in {companyName} for the same job type, same
|
|
||||||
level, and same YOE(+/-1) in the last year.
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
@ -0,0 +1,27 @@
|
|||||||
|
import type { Analysis } from '~/types/offers';
|
||||||
|
|
||||||
|
type OfferPercentileAnalysisTextProps = Readonly<{
|
||||||
|
companyName: string;
|
||||||
|
offerAnalysis: Analysis;
|
||||||
|
tab: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default function OfferPercentileAnalysisText({
|
||||||
|
tab,
|
||||||
|
companyName,
|
||||||
|
offerAnalysis: { noOfOffers, percentile },
|
||||||
|
}: OfferPercentileAnalysisTextProps) {
|
||||||
|
return tab === 'Overall' ? (
|
||||||
|
<p>
|
||||||
|
Your highest offer is from <b>{companyName}</b>, which is{' '}
|
||||||
|
<b>{percentile}</b> percentile out of <b>{noOfOffers}</b> offers received
|
||||||
|
for the same job title and YOE(+/-1) in the last year.
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p>
|
||||||
|
Your offer from <b>{companyName}</b> is <b>{percentile}</b> percentile out
|
||||||
|
of <b>{noOfOffers}</b> offers received in {companyName} for the same job
|
||||||
|
title and YOE(+/-1) in the last year.
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
@ -1,9 +1,10 @@
|
|||||||
import { UserCircleIcon } from '@heroicons/react/24/outline';
|
import { JobType } from '@prisma/client';
|
||||||
|
|
||||||
import { HorizontalDivider } from '~/../../../packages/ui/dist';
|
import { HorizontalDivider } from '~/../../../packages/ui/dist';
|
||||||
|
import { convertMoneyToString } from '~/utils/offers/currency';
|
||||||
import { formatDate } from '~/utils/offers/time';
|
import { formatDate } from '~/utils/offers/time';
|
||||||
|
|
||||||
import { JobType } from '../../types';
|
import ProfilePhotoHolder from '../../profile/ProfilePhotoHolder';
|
||||||
|
|
||||||
import type { AnalysisOffer } from '~/types/offers';
|
import type { AnalysisOffer } from '~/types/offers';
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ export default function OfferProfileCard({
|
|||||||
<div className="my-5 block rounded-lg border p-4">
|
<div className="my-5 block rounded-lg border p-4">
|
||||||
<div className="grid grid-flow-col grid-cols-12 gap-x-10">
|
<div className="grid grid-flow-col grid-cols-12 gap-x-10">
|
||||||
<div className="col-span-1">
|
<div className="col-span-1">
|
||||||
<UserCircleIcon width={50} />
|
<ProfilePhotoHolder size="sm" />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-10">
|
<div className="col-span-10">
|
||||||
<p className="text-sm font-semibold">{profileName}</p>
|
<p className="text-sm font-semibold">{profileName}</p>
|
||||||
@ -50,9 +51,9 @@ export default function OfferProfileCard({
|
|||||||
<div className="col-span-1 row-span-3">
|
<div className="col-span-1 row-span-3">
|
||||||
<p className="text-end text-sm">{formatDate(monthYearReceived)}</p>
|
<p className="text-end text-sm">{formatDate(monthYearReceived)}</p>
|
||||||
<p className="text-end text-xl">
|
<p className="text-end text-xl">
|
||||||
{jobType === JobType.FullTime
|
{jobType === JobType.FULLTIME
|
||||||
? `$${income} / year`
|
? `${convertMoneyToString(income)} / year`
|
||||||
: `$${income} / month`}
|
: `${convertMoneyToString(income)} / month`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useFormContext, useWatch } from 'react-hook-form';
|
import { useFormContext, useWatch } from 'react-hook-form';
|
||||||
|
import { JobType } from '@prisma/client';
|
||||||
import { Collapsible, RadioList } from '@tih/ui';
|
import { Collapsible, RadioList } from '@tih/ui';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -10,7 +11,6 @@ import {
|
|||||||
titleOptions,
|
titleOptions,
|
||||||
} from '~/components/offers/constants';
|
} from '~/components/offers/constants';
|
||||||
import type { BackgroundPostData } from '~/components/offers/types';
|
import type { BackgroundPostData } from '~/components/offers/types';
|
||||||
import { JobType } from '~/components/offers/types';
|
|
||||||
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
|
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
|
||||||
|
|
||||||
import { CURRENCY_OPTIONS } from '~/utils/offers/currency/CurrencyEnum';
|
import { CURRENCY_OPTIONS } from '~/utils/offers/currency/CurrencyEnum';
|
||||||
@ -239,7 +239,7 @@ function InternshipJobFields() {
|
|||||||
function CurrentJobSection() {
|
function CurrentJobSection() {
|
||||||
const { register } = useFormContext();
|
const { register } = useFormContext();
|
||||||
const watchJobType = useWatch({
|
const watchJobType = useWatch({
|
||||||
defaultValue: JobType.FullTime,
|
defaultValue: JobType.FULLTIME,
|
||||||
name: 'background.experiences.0.jobType',
|
name: 'background.experiences.0.jobType',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -251,7 +251,7 @@ function CurrentJobSection() {
|
|||||||
<div className="mb-5 rounded-lg border border-gray-200 px-10 py-5">
|
<div className="mb-5 rounded-lg border border-gray-200 px-10 py-5">
|
||||||
<div className="mb-5">
|
<div className="mb-5">
|
||||||
<FormRadioList
|
<FormRadioList
|
||||||
defaultValue={JobType.FullTime}
|
defaultValue={JobType.FULLTIME}
|
||||||
isLabelHidden={true}
|
isLabelHidden={true}
|
||||||
label="Job Type"
|
label="Job Type"
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
@ -259,16 +259,16 @@ function CurrentJobSection() {
|
|||||||
<RadioList.Item
|
<RadioList.Item
|
||||||
key="Full-time"
|
key="Full-time"
|
||||||
label="Full-time"
|
label="Full-time"
|
||||||
value={JobType.FullTime}
|
value={JobType.FULLTIME}
|
||||||
/>
|
/>
|
||||||
<RadioList.Item
|
<RadioList.Item
|
||||||
key="Internship"
|
key="Internship"
|
||||||
label="Internship"
|
label="Internship"
|
||||||
value={JobType.Intern}
|
value={JobType.INTERN}
|
||||||
/>
|
/>
|
||||||
</FormRadioList>
|
</FormRadioList>
|
||||||
</div>
|
</div>
|
||||||
{watchJobType === JobType.FullTime ? (
|
{watchJobType === JobType.FULLTIME ? (
|
||||||
<FullTimeJobFields />
|
<FullTimeJobFields />
|
||||||
) : (
|
) : (
|
||||||
<InternshipJobFields />
|
<InternshipJobFields />
|
||||||
|
@ -9,6 +9,7 @@ import { useFormContext } from 'react-hook-form';
|
|||||||
import { useFieldArray } from 'react-hook-form';
|
import { useFieldArray } from 'react-hook-form';
|
||||||
import { PlusIcon } from '@heroicons/react/20/solid';
|
import { PlusIcon } from '@heroicons/react/20/solid';
|
||||||
import { TrashIcon } from '@heroicons/react/24/outline';
|
import { TrashIcon } from '@heroicons/react/24/outline';
|
||||||
|
import { JobType } from '@prisma/client';
|
||||||
import { Button, Dialog } from '@tih/ui';
|
import { Button, Dialog } from '@tih/ui';
|
||||||
|
|
||||||
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
|
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
|
||||||
@ -31,7 +32,6 @@ import FormTextArea from '../../forms/FormTextArea';
|
|||||||
import FormTextInput from '../../forms/FormTextInput';
|
import FormTextInput from '../../forms/FormTextInput';
|
||||||
import type { OfferFormData } from '../../types';
|
import type { OfferFormData } from '../../types';
|
||||||
import { JobTypeLabel } from '../../types';
|
import { JobTypeLabel } from '../../types';
|
||||||
import { JobType } from '../../types';
|
|
||||||
import { CURRENCY_OPTIONS } from '../../../../utils/offers/currency/CurrencyEnum';
|
import { CURRENCY_OPTIONS } from '../../../../utils/offers/currency/CurrencyEnum';
|
||||||
|
|
||||||
type FullTimeOfferDetailsFormProps = Readonly<{
|
type FullTimeOfferDetailsFormProps = Readonly<{
|
||||||
@ -448,7 +448,7 @@ function OfferDetailsFormArray({
|
|||||||
{fields.map((item, index) => {
|
{fields.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<div key={item.id}>
|
<div key={item.id}>
|
||||||
{jobType === JobType.FullTime ? (
|
{jobType === JobType.FULLTIME ? (
|
||||||
<FullTimeOfferDetailsForm index={index} remove={remove} />
|
<FullTimeOfferDetailsForm index={index} remove={remove} />
|
||||||
) : (
|
) : (
|
||||||
<InternshipOfferDetailsForm index={index} remove={remove} />
|
<InternshipOfferDetailsForm index={index} remove={remove} />
|
||||||
@ -464,7 +464,7 @@ function OfferDetailsFormArray({
|
|||||||
variant="tertiary"
|
variant="tertiary"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
append(
|
append(
|
||||||
jobType === JobType.FullTime
|
jobType === JobType.FULLTIME
|
||||||
? defaultFullTimeOfferValues
|
? defaultFullTimeOfferValues
|
||||||
: defaultInternshipOfferValues,
|
: defaultInternshipOfferValues,
|
||||||
)
|
)
|
||||||
@ -474,8 +474,14 @@ function OfferDetailsFormArray({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function OfferDetailsForm() {
|
type OfferDetailsFormProps = Readonly<{
|
||||||
const [jobType, setJobType] = useState(JobType.FullTime);
|
defaultJobType?: JobType;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default function OfferDetailsForm({
|
||||||
|
defaultJobType = JobType.FULLTIME,
|
||||||
|
}: OfferDetailsFormProps) {
|
||||||
|
const [jobType, setJobType] = useState(defaultJobType);
|
||||||
const [isDialogOpen, setDialogOpen] = useState(false);
|
const [isDialogOpen, setDialogOpen] = useState(false);
|
||||||
const { control } = useFormContext();
|
const { control } = useFormContext();
|
||||||
const fieldArrayValues = useFieldArray({ control, name: 'offers' });
|
const fieldArrayValues = useFieldArray({ control, name: 'offers' });
|
||||||
@ -483,17 +489,17 @@ export default function OfferDetailsForm() {
|
|||||||
|
|
||||||
const toggleJobType = () => {
|
const toggleJobType = () => {
|
||||||
remove();
|
remove();
|
||||||
if (jobType === JobType.FullTime) {
|
if (jobType === JobType.FULLTIME) {
|
||||||
setJobType(JobType.Intern);
|
setJobType(JobType.INTERN);
|
||||||
append(defaultInternshipOfferValues);
|
append(defaultInternshipOfferValues);
|
||||||
} else {
|
} else {
|
||||||
setJobType(JobType.FullTime);
|
setJobType(JobType.FULLTIME);
|
||||||
append(defaultFullTimeOfferValues);
|
append(defaultFullTimeOfferValues);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const switchJobTypeLabel = () =>
|
const switchJobTypeLabel = () =>
|
||||||
jobType === JobType.FullTime ? JobTypeLabel.INTERN : JobTypeLabel.FULLTIME;
|
jobType === JobType.FULLTIME ? JobTypeLabel.INTERN : JobTypeLabel.FULLTIME;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-5">
|
<div className="mb-5">
|
||||||
@ -506,9 +512,9 @@ export default function OfferDetailsForm() {
|
|||||||
display="block"
|
display="block"
|
||||||
label={JobTypeLabel.FULLTIME}
|
label={JobTypeLabel.FULLTIME}
|
||||||
size="md"
|
size="md"
|
||||||
variant={jobType === JobType.FullTime ? 'secondary' : 'tertiary'}
|
variant={jobType === JobType.FULLTIME ? 'secondary' : 'tertiary'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (jobType === JobType.FullTime) {
|
if (jobType === JobType.FULLTIME) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setDialogOpen(true);
|
setDialogOpen(true);
|
||||||
@ -520,9 +526,9 @@ export default function OfferDetailsForm() {
|
|||||||
display="block"
|
display="block"
|
||||||
label={JobTypeLabel.INTERN}
|
label={JobTypeLabel.INTERN}
|
||||||
size="md"
|
size="md"
|
||||||
variant={jobType === JobType.Intern ? 'secondary' : 'tertiary'}
|
variant={jobType === JobType.INTERN ? 'secondary' : 'tertiary'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (jobType === JobType.Intern) {
|
if (jobType === JobType.INTERN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setDialogOpen(true);
|
setDialogOpen(true);
|
||||||
|
@ -3,18 +3,10 @@ import {
|
|||||||
LightBulbIcon,
|
LightBulbIcon,
|
||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
import type { EducationBackgroundType } from '~/components/offers/types';
|
import type { EducationDisplayData } from '~/components/offers/types';
|
||||||
|
|
||||||
type EducationEntity = {
|
|
||||||
endDate?: string;
|
|
||||||
field?: string;
|
|
||||||
school?: string;
|
|
||||||
startDate?: string;
|
|
||||||
type?: EducationBackgroundType;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = Readonly<{
|
type Props = Readonly<{
|
||||||
education: EducationEntity;
|
education: EducationDisplayData;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export default function EducationCard({
|
export default function EducationCard({
|
||||||
@ -39,9 +31,7 @@ export default function EducationCard({
|
|||||||
</div>
|
</div>
|
||||||
{(startDate || endDate) && (
|
{(startDate || endDate) && (
|
||||||
<div className="font-light text-gray-400">
|
<div className="font-light text-gray-400">
|
||||||
<p>{`${startDate ? startDate : 'N/A'} - ${
|
<p>{`${startDate || 'N/A'} - ${endDate || 'N/A'}`}</p>
|
||||||
endDate ? endDate : 'N/A'
|
|
||||||
}`}</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,10 +6,10 @@ import {
|
|||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import { HorizontalDivider } from '@tih/ui';
|
import { HorizontalDivider } from '@tih/ui';
|
||||||
|
|
||||||
import type { OfferEntity } from '~/components/offers/types';
|
import type { OfferDisplayData } from '~/components/offers/types';
|
||||||
|
|
||||||
type Props = Readonly<{
|
type Props = Readonly<{
|
||||||
offer: OfferEntity;
|
offer: OfferDisplayData;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export default function OfferCard({
|
export default function OfferCard({
|
||||||
|
@ -3,13 +3,18 @@ import { Spinner } from '@tih/ui';
|
|||||||
|
|
||||||
import EducationCard from '~/components/offers/profile/EducationCard';
|
import EducationCard from '~/components/offers/profile/EducationCard';
|
||||||
import OfferCard from '~/components/offers/profile/OfferCard';
|
import OfferCard from '~/components/offers/profile/OfferCard';
|
||||||
import type { BackgroundCard, OfferEntity } from '~/components/offers/types';
|
import type {
|
||||||
import { EducationBackgroundType } from '~/components/offers/types';
|
BackgroundDisplayData,
|
||||||
|
OfferDisplayData,
|
||||||
|
} from '~/components/offers/types';
|
||||||
|
|
||||||
|
import type { ProfileAnalysis } from '~/types/offers';
|
||||||
|
|
||||||
type ProfileHeaderProps = Readonly<{
|
type ProfileHeaderProps = Readonly<{
|
||||||
background?: BackgroundCard;
|
analysis?: ProfileAnalysis;
|
||||||
|
background?: BackgroundDisplayData;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
offers: Array<OfferEntity>;
|
offers: Array<OfferDisplayData>;
|
||||||
selectedTab: string;
|
selectedTab: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@ -52,7 +57,7 @@ export default function ProfileDetails({
|
|||||||
<BriefcaseIcon className="mr-1 h-5" />
|
<BriefcaseIcon className="mr-1 h-5" />
|
||||||
<span className="font-bold">Work Experience</span>
|
<span className="font-bold">Work Experience</span>
|
||||||
</div>
|
</div>
|
||||||
<OfferCard offer={background?.experiences[0]} />
|
<OfferCard offer={background.experiences[0]} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{background?.educations && background?.educations.length > 0 && (
|
{background?.educations && background?.educations.length > 0 && (
|
||||||
@ -61,15 +66,7 @@ export default function ProfileDetails({
|
|||||||
<AcademicCapIcon className="mr-1 h-5" />
|
<AcademicCapIcon className="mr-1 h-5" />
|
||||||
<span className="font-bold">Education</span>
|
<span className="font-bold">Education</span>
|
||||||
</div>
|
</div>
|
||||||
<EducationCard
|
<EducationCard education={background.educations[0]} />
|
||||||
education={{
|
|
||||||
endDate: background.educations[0].endDate,
|
|
||||||
field: background.educations[0].field,
|
|
||||||
school: background.educations[0].school,
|
|
||||||
startDate: background.educations[0].startDate,
|
|
||||||
type: EducationBackgroundType.Bachelor,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
BookmarkSquareIcon,
|
|
||||||
BuildingOffice2Icon,
|
BuildingOffice2Icon,
|
||||||
CalendarDaysIcon,
|
CalendarDaysIcon,
|
||||||
PencilSquareIcon,
|
PencilSquareIcon,
|
||||||
@ -10,12 +9,12 @@ import {
|
|||||||
import { Button, Dialog, Spinner, Tabs } from '@tih/ui';
|
import { Button, Dialog, Spinner, Tabs } from '@tih/ui';
|
||||||
|
|
||||||
import ProfilePhotoHolder from '~/components/offers/profile/ProfilePhotoHolder';
|
import ProfilePhotoHolder from '~/components/offers/profile/ProfilePhotoHolder';
|
||||||
import type { BackgroundCard } from '~/components/offers/types';
|
import type { BackgroundDisplayData } from '~/components/offers/types';
|
||||||
|
|
||||||
import { getProfileEditPath } from '~/utils/offers/link';
|
import { getProfileEditPath } from '~/utils/offers/link';
|
||||||
|
|
||||||
type ProfileHeaderProps = Readonly<{
|
type ProfileHeaderProps = Readonly<{
|
||||||
background?: BackgroundCard;
|
background?: BackgroundDisplayData;
|
||||||
handleDelete: () => void;
|
handleDelete: () => void;
|
||||||
isEditable: boolean;
|
isEditable: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
@ -42,14 +41,14 @@ export default function ProfileHeader({
|
|||||||
function renderActionList() {
|
function renderActionList() {
|
||||||
return (
|
return (
|
||||||
<div className="space-x-2">
|
<div className="space-x-2">
|
||||||
<Button
|
{/* <Button
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
icon={BookmarkSquareIcon}
|
icon={BookmarkSquareIcon}
|
||||||
isLabelHidden={true}
|
isLabelHidden={true}
|
||||||
label="Save to user account"
|
label="Save to user account"
|
||||||
size="md"
|
size="md"
|
||||||
variant="tertiary"
|
variant="tertiary"
|
||||||
/>
|
/> */}
|
||||||
<Button
|
<Button
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
icon={PencilSquareIcon}
|
icon={PencilSquareIcon}
|
||||||
@ -109,6 +108,13 @@ export default function ProfileHeader({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!background) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { experiences, totalYoe, specificYoes, profileName } = background;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-40 bg-white p-4">
|
<div className="h-40 bg-white p-4">
|
||||||
<div className="justify-left flex h-1/2">
|
<div className="justify-left flex h-1/2">
|
||||||
@ -118,7 +124,7 @@ export default function ProfileHeader({
|
|||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="justify-left flex flex-1">
|
<div className="justify-left flex flex-1">
|
||||||
<h2 className="flex w-4/5 text-2xl font-bold">
|
<h2 className="flex w-4/5 text-2xl font-bold">
|
||||||
{background?.profileName ?? 'anonymous'}
|
{profileName ?? 'anonymous'}
|
||||||
</h2>
|
</h2>
|
||||||
{isEditable && (
|
{isEditable && (
|
||||||
<div className="flex h-8 w-1/5 justify-end">
|
<div className="flex h-8 w-1/5 justify-end">
|
||||||
@ -126,22 +132,26 @@ export default function ProfileHeader({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row">
|
{(experiences[0]?.companyName ||
|
||||||
<BuildingOffice2Icon className="mr-2.5 h-5" />
|
experiences[0]?.jobLevel ||
|
||||||
<span className="mr-2 font-bold">Current:</span>
|
experiences[0]?.jobTitle) && (
|
||||||
<span>
|
<div className="flex flex-row">
|
||||||
{`${background?.experiences[0]?.companyName ?? '-'} ${
|
<BuildingOffice2Icon className="mr-2.5 h-5" />
|
||||||
background?.experiences[0]?.jobLevel || ''
|
<span className="mr-2 font-bold">Current:</span>
|
||||||
} ${background?.experiences[0]?.jobTitle || ''}`}
|
<span>
|
||||||
</span>
|
{`${experiences[0]?.companyName || ''} ${
|
||||||
</div>
|
experiences[0]?.jobLevel || ''
|
||||||
|
} ${experiences[0]?.jobTitle || ''}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<CalendarDaysIcon className="mr-2.5 h-5" />
|
<CalendarDaysIcon className="mr-2.5 h-5" />
|
||||||
<span className="mr-2 font-bold">YOE:</span>
|
<span className="mr-2 font-bold">YOE:</span>
|
||||||
<span className="mr-4">{background?.totalYoe}</span>
|
<span className="mr-4">{totalYoe}</span>
|
||||||
{background?.specificYoes &&
|
{specificYoes &&
|
||||||
background?.specificYoes.length > 0 &&
|
specificYoes.length > 0 &&
|
||||||
background?.specificYoes.map(({ domain, yoe }) => {
|
specificYoes.map(({ domain, yoe }) => {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
key={domain}
|
key={domain}
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
export default function ProfilePhotoHolder() {
|
type ProfilePhotoHolderProps = {
|
||||||
|
size?: 'lg' | 'sm';
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ProfilePhotoHolder({
|
||||||
|
size = 'lg',
|
||||||
|
}: ProfilePhotoHolderProps) {
|
||||||
|
const sizeMap = { lg: '16', sm: '12' };
|
||||||
return (
|
return (
|
||||||
<span className="inline-block h-16 w-16 overflow-hidden rounded-full bg-gray-100">
|
<span
|
||||||
|
className={`inline-block h-${sizeMap[size]} w-${sizeMap[size]} overflow-hidden rounded-full bg-gray-100`}>
|
||||||
<svg
|
<svg
|
||||||
className="h-full w-full text-gray-300"
|
className="h-full w-full text-gray-300"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
|
import type { JobType } from '@prisma/client';
|
||||||
|
|
||||||
import type { MonthYear } from '~/components/shared/MonthYearPicker';
|
import type { MonthYear } from '~/components/shared/MonthYearPicker';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Offer Profile
|
* Offer Profile
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export enum JobType {
|
|
||||||
FullTime = 'FULLTIME',
|
|
||||||
Intern = 'INTERN',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const JobTypeLabel = {
|
export const JobTypeLabel = {
|
||||||
FULLTIME: 'Full-time',
|
FULLTIME: 'Full-time',
|
||||||
INTERN: 'Internship',
|
INTERN: 'Internship',
|
||||||
@ -26,17 +23,20 @@ export enum EducationBackgroundType {
|
|||||||
|
|
||||||
export type OffersProfilePostData = {
|
export type OffersProfilePostData = {
|
||||||
background: BackgroundPostData;
|
background: BackgroundPostData;
|
||||||
|
id?: string;
|
||||||
offers: Array<OfferPostData>;
|
offers: Array<OfferPostData>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OffersProfileFormData = {
|
export type OffersProfileFormData = {
|
||||||
background: BackgroundPostData;
|
background: BackgroundPostData;
|
||||||
|
id?: string;
|
||||||
offers: Array<OfferFormData>;
|
offers: Array<OfferFormData>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BackgroundPostData = {
|
export type BackgroundPostData = {
|
||||||
educations: Array<EducationPostData>;
|
educations: Array<EducationPostData>;
|
||||||
experiences: Array<ExperiencePostData>;
|
experiences: Array<ExperiencePostData>;
|
||||||
|
id?: string;
|
||||||
specificYoes: Array<SpecificYoePostData>;
|
specificYoes: Array<SpecificYoePostData>;
|
||||||
totalYoe: number;
|
totalYoe: number;
|
||||||
};
|
};
|
||||||
@ -44,6 +44,7 @@ export type BackgroundPostData = {
|
|||||||
type ExperiencePostData = {
|
type ExperiencePostData = {
|
||||||
companyId?: string | null;
|
companyId?: string | null;
|
||||||
durationInMonths?: number | null;
|
durationInMonths?: number | null;
|
||||||
|
id?: string;
|
||||||
jobType?: string | null;
|
jobType?: string | null;
|
||||||
level?: string | null;
|
level?: string | null;
|
||||||
location?: string | null;
|
location?: string | null;
|
||||||
@ -57,6 +58,7 @@ type ExperiencePostData = {
|
|||||||
type EducationPostData = {
|
type EducationPostData = {
|
||||||
endDate?: Date | null;
|
endDate?: Date | null;
|
||||||
field?: string | null;
|
field?: string | null;
|
||||||
|
id?: string;
|
||||||
school?: string | null;
|
school?: string | null;
|
||||||
startDate?: Date | null;
|
startDate?: Date | null;
|
||||||
type?: string | null;
|
type?: string | null;
|
||||||
@ -64,6 +66,7 @@ type EducationPostData = {
|
|||||||
|
|
||||||
type SpecificYoePostData = {
|
type SpecificYoePostData = {
|
||||||
domain: string;
|
domain: string;
|
||||||
|
id?: string;
|
||||||
yoe: number;
|
yoe: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -72,7 +75,8 @@ type SpecificYoe = SpecificYoePostData;
|
|||||||
export type OfferPostData = {
|
export type OfferPostData = {
|
||||||
comments: string;
|
comments: string;
|
||||||
companyId: string;
|
companyId: string;
|
||||||
jobType: string;
|
id?: string;
|
||||||
|
jobType: JobType;
|
||||||
location: string;
|
location: string;
|
||||||
monthYearReceived: Date;
|
monthYearReceived: Date;
|
||||||
negotiationStrategy: string;
|
negotiationStrategy: string;
|
||||||
@ -87,6 +91,7 @@ export type OfferFormData = Omit<OfferPostData, 'monthYearReceived'> & {
|
|||||||
export type OfferFullTimePostData = {
|
export type OfferFullTimePostData = {
|
||||||
baseSalary: Money;
|
baseSalary: Money;
|
||||||
bonus: Money;
|
bonus: Money;
|
||||||
|
id?: string;
|
||||||
level: string;
|
level: string;
|
||||||
specialization: string;
|
specialization: string;
|
||||||
stocks: Money;
|
stocks: Money;
|
||||||
@ -95,6 +100,7 @@ export type OfferFullTimePostData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type OfferInternPostData = {
|
export type OfferInternPostData = {
|
||||||
|
id?: string;
|
||||||
internshipCycle: string;
|
internshipCycle: string;
|
||||||
monthlySalary: Money;
|
monthlySalary: Money;
|
||||||
specialization: string;
|
specialization: string;
|
||||||
@ -104,40 +110,41 @@ export type OfferInternPostData = {
|
|||||||
|
|
||||||
export type Money = {
|
export type Money = {
|
||||||
currency: string;
|
currency: string;
|
||||||
|
id?: string;
|
||||||
value: number;
|
value: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type EducationDisplay = {
|
export type EducationDisplayData = {
|
||||||
endDate?: string;
|
endDate?: string | null;
|
||||||
field: string;
|
field?: string | null;
|
||||||
school: string;
|
school?: string | null;
|
||||||
startDate?: string;
|
startDate?: string | null;
|
||||||
type: string;
|
type?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OfferEntity = {
|
export type OfferDisplayData = {
|
||||||
base?: string;
|
base?: string | null;
|
||||||
bonus?: string;
|
bonus?: string | null;
|
||||||
companyName?: string;
|
companyName?: string | null;
|
||||||
duration?: string;
|
duration?: number | null;
|
||||||
id?: string;
|
id?: string;
|
||||||
jobLevel?: string;
|
jobLevel?: string | null;
|
||||||
jobTitle?: string;
|
jobTitle?: string | null;
|
||||||
location?: string;
|
location?: string | null;
|
||||||
monthlySalary?: string;
|
monthlySalary?: string | null;
|
||||||
negotiationStrategy?: string;
|
negotiationStrategy?: string | null;
|
||||||
otherComment?: string;
|
otherComment?: string | null;
|
||||||
receivedMonth?: string;
|
receivedMonth?: string | null;
|
||||||
stocks?: string;
|
stocks?: string | null;
|
||||||
totalCompensation?: string;
|
totalCompensation?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BackgroundCard = {
|
export type BackgroundDisplayData = {
|
||||||
educations: Array<EducationDisplay>;
|
educations: Array<EducationDisplayData>;
|
||||||
experiences: Array<OfferEntity>;
|
experiences: Array<OfferDisplayData>;
|
||||||
profileName: string;
|
profileName: string;
|
||||||
specificYoes: Array<SpecificYoe>;
|
specificYoes: Array<SpecificYoe>;
|
||||||
totalYoe: string;
|
totalYoe: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CommentEntity = {
|
export type CommentEntity = {
|
||||||
|
@ -56,7 +56,13 @@ const analysisOfferDtoMapper = (
|
|||||||
const analysisOfferDto: AnalysisOffer = {
|
const analysisOfferDto: AnalysisOffer = {
|
||||||
company: offersCompanyDtoMapper(offer.company),
|
company: offersCompanyDtoMapper(offer.company),
|
||||||
id: offer.id,
|
id: offer.id,
|
||||||
income: { baseCurrency: '', baseValue: -1, currency: '', value: -1 },
|
income: {
|
||||||
|
baseCurrency: '',
|
||||||
|
baseValue: -1,
|
||||||
|
currency: '',
|
||||||
|
id: '',
|
||||||
|
value: -1,
|
||||||
|
},
|
||||||
jobType: offer.jobType,
|
jobType: offer.jobType,
|
||||||
level: offer.offersFullTime?.level ?? '',
|
level: offer.offersFullTime?.level ?? '',
|
||||||
location: offer.location,
|
location: offer.location,
|
||||||
@ -83,6 +89,7 @@ const analysisOfferDtoMapper = (
|
|||||||
offer.offersFullTime.totalCompensation.value;
|
offer.offersFullTime.totalCompensation.value;
|
||||||
analysisOfferDto.income.currency =
|
analysisOfferDto.income.currency =
|
||||||
offer.offersFullTime.totalCompensation.currency;
|
offer.offersFullTime.totalCompensation.currency;
|
||||||
|
analysisOfferDto.income.id = offer.offersFullTime.totalCompensation.id;
|
||||||
analysisOfferDto.income.baseValue =
|
analysisOfferDto.income.baseValue =
|
||||||
offer.offersFullTime.totalCompensation.baseValue;
|
offer.offersFullTime.totalCompensation.baseValue;
|
||||||
analysisOfferDto.income.baseCurrency =
|
analysisOfferDto.income.baseCurrency =
|
||||||
@ -91,6 +98,7 @@ const analysisOfferDtoMapper = (
|
|||||||
analysisOfferDto.income.value = offer.offersIntern.monthlySalary.value;
|
analysisOfferDto.income.value = offer.offersIntern.monthlySalary.value;
|
||||||
analysisOfferDto.income.currency =
|
analysisOfferDto.income.currency =
|
||||||
offer.offersIntern.monthlySalary.currency;
|
offer.offersIntern.monthlySalary.currency;
|
||||||
|
analysisOfferDto.income.id = offer.offersIntern.monthlySalary.id;
|
||||||
analysisOfferDto.income.baseValue =
|
analysisOfferDto.income.baseValue =
|
||||||
offer.offersIntern.monthlySalary.baseValue;
|
offer.offersIntern.monthlySalary.baseValue;
|
||||||
analysisOfferDto.income.baseCurrency =
|
analysisOfferDto.income.baseCurrency =
|
||||||
@ -255,13 +263,14 @@ export const valuationDtoMapper = (currency: {
|
|||||||
baseCurrency: string;
|
baseCurrency: string;
|
||||||
baseValue: number;
|
baseValue: number;
|
||||||
currency: string;
|
currency: string;
|
||||||
id?: string;
|
id: string;
|
||||||
value: number;
|
value: number;
|
||||||
}) => {
|
}) => {
|
||||||
const valuationDto: Valuation = {
|
const valuationDto: Valuation = {
|
||||||
baseCurrency: currency.baseCurrency,
|
baseCurrency: currency.baseCurrency,
|
||||||
baseValue: currency.baseValue,
|
baseValue: currency.baseValue,
|
||||||
currency: currency.currency,
|
currency: currency.currency,
|
||||||
|
id: currency.id,
|
||||||
value: currency.value,
|
value: currency.value,
|
||||||
};
|
};
|
||||||
return valuationDto;
|
return valuationDto;
|
||||||
@ -595,11 +604,12 @@ export const dashboardOfferDtoMapper = (
|
|||||||
baseCurrency: '',
|
baseCurrency: '',
|
||||||
baseValue: -1,
|
baseValue: -1,
|
||||||
currency: '',
|
currency: '',
|
||||||
|
id: '',
|
||||||
value: -1,
|
value: -1,
|
||||||
}),
|
}),
|
||||||
monthYearReceived: offer.monthYearReceived,
|
monthYearReceived: offer.monthYearReceived,
|
||||||
profileId: offer.profileId,
|
profileId: offer.profileId,
|
||||||
title: offer.offersFullTime?.title ?? '',
|
title: offer.offersFullTime?.title || offer.offersIntern?.title || '',
|
||||||
totalYoe: offer.profile.background?.totalYoe ?? -1,
|
totalYoe: offer.profile.background?.totalYoe ?? -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,14 +5,17 @@ import { useState } from 'react';
|
|||||||
import ProfileComments from '~/components/offers/profile/ProfileComments';
|
import ProfileComments from '~/components/offers/profile/ProfileComments';
|
||||||
import ProfileDetails from '~/components/offers/profile/ProfileDetails';
|
import ProfileDetails from '~/components/offers/profile/ProfileDetails';
|
||||||
import ProfileHeader from '~/components/offers/profile/ProfileHeader';
|
import ProfileHeader from '~/components/offers/profile/ProfileHeader';
|
||||||
import type { BackgroundCard, OfferEntity } from '~/components/offers/types';
|
import type {
|
||||||
|
BackgroundDisplayData,
|
||||||
|
OfferDisplayData,
|
||||||
|
} from '~/components/offers/types';
|
||||||
|
|
||||||
import { convertMoneyToString } from '~/utils/offers/currency';
|
import { convertMoneyToString } from '~/utils/offers/currency';
|
||||||
import { getProfilePath } from '~/utils/offers/link';
|
import { getProfilePath } from '~/utils/offers/link';
|
||||||
import { formatDate } from '~/utils/offers/time';
|
import { formatDate } from '~/utils/offers/time';
|
||||||
import { trpc } from '~/utils/trpc';
|
import { trpc } from '~/utils/trpc';
|
||||||
|
|
||||||
import type { Profile, ProfileOffer } from '~/types/offers';
|
import type { Profile, ProfileAnalysis, ProfileOffer } from '~/types/offers';
|
||||||
|
|
||||||
export default function OfferProfile() {
|
export default function OfferProfile() {
|
||||||
const ErrorPage = (
|
const ErrorPage = (
|
||||||
@ -21,10 +24,11 @@ export default function OfferProfile() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { offerProfileId, token = '' } = router.query;
|
const { offerProfileId, token = '' } = router.query;
|
||||||
const [isEditable, setIsEditable] = useState(false);
|
const [isEditable, setIsEditable] = useState(false);
|
||||||
const [background, setBackground] = useState<BackgroundCard>();
|
const [background, setBackground] = useState<BackgroundDisplayData>();
|
||||||
const [offers, setOffers] = useState<Array<OfferEntity>>([]);
|
const [offers, setOffers] = useState<Array<OfferDisplayData>>([]);
|
||||||
|
|
||||||
const [selectedTab, setSelectedTab] = useState('offers');
|
const [selectedTab, setSelectedTab] = useState('offers');
|
||||||
|
const [analysis, setAnalysis] = useState<ProfileAnalysis>();
|
||||||
|
|
||||||
const getProfileQuery = trpc.useQuery(
|
const getProfileQuery = trpc.useQuery(
|
||||||
[
|
[
|
||||||
@ -44,75 +48,79 @@ export default function OfferProfile() {
|
|||||||
|
|
||||||
setIsEditable(data?.isEditable ?? false);
|
setIsEditable(data?.isEditable ?? false);
|
||||||
|
|
||||||
if (data?.offers) {
|
const filteredOffers: Array<OfferDisplayData> = data
|
||||||
const filteredOffers: Array<OfferEntity> = data
|
? data?.offers.map((res: ProfileOffer) => {
|
||||||
? data?.offers.map((res: ProfileOffer) => {
|
if (res.offersFullTime) {
|
||||||
if (res.offersFullTime) {
|
const filteredOffer: OfferDisplayData = {
|
||||||
const filteredOffer: OfferEntity = {
|
base: convertMoneyToString(res.offersFullTime.baseSalary),
|
||||||
base: convertMoneyToString(res.offersFullTime.baseSalary),
|
bonus: convertMoneyToString(res.offersFullTime.bonus),
|
||||||
bonus: convertMoneyToString(res.offersFullTime.bonus),
|
|
||||||
companyName: res.company.name,
|
|
||||||
id: res.offersFullTime.id,
|
|
||||||
jobLevel: res.offersFullTime.level,
|
|
||||||
jobTitle: res.offersFullTime.title,
|
|
||||||
location: res.location,
|
|
||||||
negotiationStrategy: res.negotiationStrategy || '',
|
|
||||||
otherComment: res.comments || '',
|
|
||||||
receivedMonth: formatDate(res.monthYearReceived),
|
|
||||||
stocks: convertMoneyToString(res.offersFullTime.stocks),
|
|
||||||
totalCompensation: convertMoneyToString(
|
|
||||||
res.offersFullTime.totalCompensation,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
return filteredOffer;
|
|
||||||
}
|
|
||||||
const filteredOffer: OfferEntity = {
|
|
||||||
companyName: res.company.name,
|
companyName: res.company.name,
|
||||||
id: res.offersIntern!.id,
|
id: res.offersFullTime.id,
|
||||||
jobTitle: res.offersIntern!.title,
|
jobLevel: res.offersFullTime.level,
|
||||||
|
jobTitle: res.offersFullTime.title,
|
||||||
location: res.location,
|
location: res.location,
|
||||||
monthlySalary: convertMoneyToString(
|
negotiationStrategy: res.negotiationStrategy,
|
||||||
res.offersIntern!.monthlySalary,
|
otherComment: res.comments,
|
||||||
),
|
|
||||||
negotiationStrategy: res.negotiationStrategy || '',
|
|
||||||
otherComment: res.comments || '',
|
|
||||||
receivedMonth: formatDate(res.monthYearReceived),
|
receivedMonth: formatDate(res.monthYearReceived),
|
||||||
|
stocks: convertMoneyToString(res.offersFullTime.stocks),
|
||||||
|
totalCompensation: convertMoneyToString(
|
||||||
|
res.offersFullTime.totalCompensation,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
return filteredOffer;
|
return filteredOffer;
|
||||||
})
|
}
|
||||||
: [];
|
const filteredOffer: OfferDisplayData = {
|
||||||
setOffers(filteredOffers);
|
companyName: res.company.name,
|
||||||
}
|
id: res.offersIntern!.id,
|
||||||
|
jobTitle: res.offersIntern!.title,
|
||||||
|
location: res.location,
|
||||||
|
monthlySalary: convertMoneyToString(
|
||||||
|
res.offersIntern!.monthlySalary,
|
||||||
|
),
|
||||||
|
negotiationStrategy: res.negotiationStrategy,
|
||||||
|
otherComment: res.comments,
|
||||||
|
receivedMonth: formatDate(res.monthYearReceived),
|
||||||
|
};
|
||||||
|
return filteredOffer;
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
setOffers(filteredOffers);
|
||||||
|
|
||||||
if (data?.background) {
|
if (data?.background) {
|
||||||
const transformedBackground = {
|
const transformedBackground = {
|
||||||
educations: data.background.educations.map((education) => ({
|
educations: data.background.educations.map((education) => ({
|
||||||
endDate: education.endDate ? formatDate(education.endDate) : '-',
|
endDate: education.endDate ? formatDate(education.endDate) : null,
|
||||||
field: education.field || '-',
|
field: education.field,
|
||||||
school: education.school || '-',
|
school: education.school,
|
||||||
startDate: education.startDate
|
startDate: education.startDate
|
||||||
? formatDate(education.startDate)
|
? formatDate(education.startDate)
|
||||||
: '-',
|
: null,
|
||||||
type: education.type || '-',
|
type: education.type,
|
||||||
})),
|
|
||||||
experiences: data.background.experiences.map((experience) => ({
|
|
||||||
companyName: experience.company?.name ?? '-',
|
|
||||||
duration: String(experience.durationInMonths) ?? '-',
|
|
||||||
jobLevel: experience.level ?? '',
|
|
||||||
jobTitle: experience.title ?? '-',
|
|
||||||
monthlySalary: experience.monthlySalary
|
|
||||||
? convertMoneyToString(experience.monthlySalary)
|
|
||||||
: '-',
|
|
||||||
totalCompensation: experience.totalCompensation
|
|
||||||
? convertMoneyToString(experience.totalCompensation)
|
|
||||||
: '-',
|
|
||||||
})),
|
})),
|
||||||
|
experiences: data.background.experiences.map(
|
||||||
|
(experience): OfferDisplayData => ({
|
||||||
|
companyName: experience.company?.name,
|
||||||
|
duration: experience.durationInMonths,
|
||||||
|
jobLevel: experience.level,
|
||||||
|
jobTitle: experience.title,
|
||||||
|
monthlySalary: experience.monthlySalary
|
||||||
|
? convertMoneyToString(experience.monthlySalary)
|
||||||
|
: null,
|
||||||
|
totalCompensation: experience.totalCompensation
|
||||||
|
? convertMoneyToString(experience.totalCompensation)
|
||||||
|
: null,
|
||||||
|
}),
|
||||||
|
),
|
||||||
profileName: data.profileName,
|
profileName: data.profileName,
|
||||||
specificYoes: data.background.specificYoes,
|
specificYoes: data.background.specificYoes,
|
||||||
totalYoe: String(data.background.totalYoe) || '-',
|
totalYoe: data.background.totalYoe,
|
||||||
};
|
};
|
||||||
setBackground(transformedBackground);
|
setBackground(transformedBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.analysis) {
|
||||||
|
setAnalysis(data.analysis);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -153,6 +161,7 @@ export default function OfferProfile() {
|
|||||||
/>
|
/>
|
||||||
<div className="h-4/5 w-full overflow-y-scroll pb-32">
|
<div className="h-4/5 w-full overflow-y-scroll pb-32">
|
||||||
<ProfileDetails
|
<ProfileDetails
|
||||||
|
analysis={analysis}
|
||||||
background={background}
|
background={background}
|
||||||
isLoading={getProfileQuery.isLoading}
|
isLoading={getProfileQuery.isLoading}
|
||||||
offers={offers}
|
offers={offers}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { JobType } from '@prisma/client';
|
||||||
|
|
||||||
import OffersSubmissionForm from '~/components/offers/offersSubmission/OffersSubmissionForm';
|
import OffersSubmissionForm from '~/components/offers/offersSubmission/OffersSubmissionForm';
|
||||||
import type { OffersProfileFormData } from '~/components/offers/types';
|
import type { OffersProfileFormData } from '~/components/offers/types';
|
||||||
import { JobType } from '~/components/offers/types';
|
|
||||||
|
|
||||||
import { Spinner } from '~/../../../packages/ui/dist';
|
import { Spinner } from '~/../../../packages/ui/dist';
|
||||||
import { getProfilePath } from '~/utils/offers/link';
|
import { getProfilePath } from '~/utils/offers/link';
|
||||||
@ -25,7 +25,7 @@ export default function OffersEditPage() {
|
|||||||
console.error(error.message);
|
console.error(error.message);
|
||||||
},
|
},
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
const { educations, experiences, specificYoes, totalYoe } =
|
const { educations, experiences, specificYoes, totalYoe, id } =
|
||||||
data.background!;
|
data.background!;
|
||||||
|
|
||||||
setInitialData({
|
setInitialData({
|
||||||
@ -33,11 +33,13 @@ export default function OffersEditPage() {
|
|||||||
educations,
|
educations,
|
||||||
experiences:
|
experiences:
|
||||||
experiences.length === 0
|
experiences.length === 0
|
||||||
? [{ jobType: JobType.FullTime }]
|
? [{ jobType: JobType.FULLTIME }]
|
||||||
: experiences,
|
: experiences,
|
||||||
|
id,
|
||||||
specificYoes,
|
specificYoes,
|
||||||
totalYoe,
|
totalYoe,
|
||||||
},
|
},
|
||||||
|
id: data.id,
|
||||||
offers: data.offers.map((offer) => ({
|
offers: data.offers.map((offer) => ({
|
||||||
comments: offer.comments,
|
comments: offer.comments,
|
||||||
companyId: offer.company.id,
|
companyId: offer.company.id,
|
||||||
@ -67,7 +69,7 @@ export default function OffersEditPage() {
|
|||||||
<Spinner className="m-10" display="block" size="lg" />
|
<Spinner className="m-10" display="block" size="lg" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!getProfileResult.isLoading && (
|
{!getProfileResult.isLoading && initialData && (
|
||||||
<OffersSubmissionForm
|
<OffersSubmissionForm
|
||||||
initialOfferProfileValues={initialData}
|
initialOfferProfileValues={initialData}
|
||||||
profileId={profile?.id}
|
profileId={profile?.id}
|
||||||
|
@ -285,7 +285,6 @@ export const offersAnalysisRouter = createRouter()
|
|||||||
OR: [
|
OR: [
|
||||||
{
|
{
|
||||||
offersFullTime: {
|
offersFullTime: {
|
||||||
level: overallHighestOffer.offersFullTime?.level,
|
|
||||||
title: overallHighestOffer.offersFullTime?.title,
|
title: overallHighestOffer.offersFullTime?.title,
|
||||||
},
|
},
|
||||||
offersIntern: {
|
offersIntern: {
|
||||||
|
@ -385,7 +385,7 @@ export const offersProfileRouter = createRouter()
|
|||||||
|
|
||||||
throw new trpc.TRPCError({
|
throw new trpc.TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'Missing fields.',
|
message: 'Missing fields in background experiences.',
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -533,7 +533,7 @@ export const offersProfileRouter = createRouter()
|
|||||||
// Throw error
|
// Throw error
|
||||||
throw new trpc.TRPCError({
|
throw new trpc.TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'Missing fields.',
|
message: 'Missing fields in offers.',
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
1
apps/portal/src/types/offers.d.ts
vendored
1
apps/portal/src/types/offers.d.ts
vendored
@ -45,6 +45,7 @@ export type Valuation = {
|
|||||||
baseCurrency: string;
|
baseCurrency: string;
|
||||||
baseValue: number;
|
baseValue: number;
|
||||||
currency: string;
|
currency: string;
|
||||||
|
id: string;
|
||||||
value: number;
|
value: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user