[offers][fix] fix merge conflicts

pull/501/head^2
Stuart Long Chay Boon 3 years ago
parent 0aa6ecb80b
commit 0016113093

@ -5,84 +5,10 @@ export const JobTypeLabel = {
INTERN: 'Internship',
};
<<<<<<< HEAD
export const emptyOption = {
label: '',
value: '',
};
=======
export const internshipCycleOptions = [
{
label: 'Summer',
value: 'Summer',
},
{
label: 'Winter',
value: 'Winter',
},
{
label: 'Spring',
value: 'Spring',
},
{
label: 'Fall',
value: 'Fall',
},
{
label: 'Full year',
value: 'Full year',
},
];
export const yearOptions = [
{
label: '2021',
value: 2021,
},
{
label: '2022',
value: 2022,
},
{
label: '2023',
value: 2023,
},
{
label: '2024',
value: 2024,
},
];
export const educationLevelOptions = Object.entries(
EducationBackgroundType,
).map(([, value]) => ({
label: value,
value,
}));
export const educationFieldOptions = [
{
label: 'Computer Science',
value: 'Computer Science',
},
{
label: 'Information Security',
value: 'Information Security',
},
{
label: 'Information Systems',
value: 'Information Systems',
},
{
label: 'Business Analytics',
value: 'Business Analytics',
},
{
label: 'Data Science and Analytics',
value: 'Data Science and Analytics',
},
];
>>>>>>> a31230f7 ([offers][feat] Use city typeahead for location field)
export enum FieldError {
NON_NEGATIVE_NUMBER = 'Please fill in a non-negative number in this field.',
@ -111,4 +37,4 @@ export const profileDetailTabs = [
label: ProfileDetailTab.ANALYSIS,
value: ProfileDetailTab.ANALYSIS,
},
];
];

@ -33,7 +33,6 @@ export default function DashboardProfileCard({
<div className="px-4 py-4 sm:px-6">
<div className="flex items-end justify-between">
<div className="col-span-1 row-span-3">
<<<<<<< HEAD
<h4 className="font-medium">
{getLabelForJobTitleType(title as JobTitleType)}{' '}
{jobType && <>({JobTypeLabel[jobType]})</>}
@ -67,17 +66,6 @@ export default function DashboardProfileCard({
</div>
)}
</div>
=======
<p className="font-bold">
{getLabelForJobTitleType(title as JobTitleType)}
</p>
<p>
{location
? `Company: ${company.name}, ${location.cityName}`
: `Company: ${company.name}`}
</p>
{level && <p>Level: {level}</p>}
>>>>>>> ac2d047d ([offers][feat] integrate location for offer table and profile)
</div>
<div className="col-span-1 row-span-3">
<p className="text-end text-lg font-medium leading-6 text-slate-900">
@ -92,4 +80,4 @@ export default function DashboardProfileCard({
</div>
</div>
);
}
}

@ -289,7 +289,6 @@ export default function OffersSubmissionForm({
steps={breadcrumbSteps}
/>
</div>
<<<<<<< HEAD
<div className="bg-white p-6 sm:p-10">
<FormProvider {...formMethods}>
<form
@ -350,58 +349,8 @@ export default function OffersSubmissionForm({
</form>
</FormProvider>
</div>
=======
<FormProvider {...formMethods}>
<form className="text-sm" onSubmit={handleSubmit(onSubmit)}>
{steps[step]}
<pre>{JSON.stringify(formMethods.watch(), null, 2)}</pre>
{step === 0 && (
<div className="flex justify-end">
<Button
disabled={false}
icon={ArrowRightIcon}
label="Next"
variant="secondary"
onClick={() => {
goToNextStep(step);
gaEvent({
action: 'offers.profile_submission_navigate_next',
category: 'submission',
label: 'Navigate next',
});
}}
/>
</div>
)}
{step === 1 && (
<div className="flex items-center justify-between">
<Button
icon={ArrowLeftIcon}
label="Previous"
variant="secondary"
onClick={() => {
setStep(step - 1);
gaEvent({
action: 'offers.profile_submission_navigation_back',
category: 'submission',
label: 'Navigate back',
});
}}
/>
<Button
disabled={isSubmitting || isSubmitSuccessful}
isLoading={isSubmitting || isSubmitSuccessful}
label="Submit"
type="submit"
variant="primary"
/>
</div>
)}
</form>
</FormProvider>
>>>>>>> a31230f7 ([offers][feat] Use city typeahead for location field)
</div>
</div>
</div>
);
}
}

@ -2,16 +2,7 @@ import { useFormContext, useWatch } from 'react-hook-form';
import { JobType } from '@prisma/client';
import { Collapsible, RadioList } from '@tih/ui';
<<<<<<< HEAD
import { FieldError } from '~/components/offers/constants';
=======
import {
educationFieldOptions,
educationLevelOptions,
emptyOption,
FieldError,
} from '~/components/offers/constants';
>>>>>>> a31230f7 ([offers][feat] Use city typeahead for location field)
import type { BackgroundPostData } from '~/components/offers/types';
import CitiesTypeahead from '~/components/shared/CitiesTypeahead';
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
@ -264,7 +255,6 @@ function InternshipJobFields() {
}}
/>
</div>
<<<<<<< HEAD
<FormTextInput
endAddOn={
<FormSelect
@ -290,10 +280,6 @@ function InternshipJobFields() {
/>
<Collapsible label="Add more details">
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
=======
<Collapsible label="Add more details">
<div className="mb-5 grid grid-cols-2 space-x-3">
>>>>>>> a31230f7 ([offers][feat] Use city typeahead for location field)
<CitiesTypeahead
label="Location"
value={{
@ -310,7 +296,6 @@ function InternshipJobFields() {
setValue('background.experiences.0.cityName', '');
}
}}
<<<<<<< HEAD
/>
<FormTextInput
errorMessage={experiencesField?.durationInMonths?.message}
@ -320,8 +305,6 @@ function InternshipJobFields() {
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
valueAsNumber: true,
})}
=======
>>>>>>> a31230f7 ([offers][feat] Use city typeahead for location field)
/>
</div>
</Collapsible>
@ -405,4 +388,4 @@ export default function BackgroundForm() {
</div>
</div>
);
}
}

@ -22,16 +22,7 @@ import {
defaultFullTimeOfferValues,
defaultInternshipOfferValues,
} from '../OffersSubmissionForm';
<<<<<<< HEAD
import { FieldError, JobTypeLabel } from '../../constants';
=======
import {
emptyOption,
FieldError,
internshipCycleOptions,
yearOptions,
} from '../../constants';
>>>>>>> a31230f7 ([offers][feat] Use city typeahead for location field)
import FormMonthYearPicker from '../../forms/FormMonthYearPicker';
import FormSection from '../../forms/FormSection';
import FormSelect from '../../forms/FormSelect';
@ -115,7 +106,6 @@ function FullTimeOfferDetailsForm({
})}
/>
</div>
<<<<<<< HEAD
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">
<CompaniesTypeahead
required={true}
@ -158,93 +148,6 @@ function FullTimeOfferDetailsForm({
monthRequired={true}
yearLabel=""
{...register(`offers.${index}.monthYearReceived`, {
=======
<FormTextInput
errorMessage={offerFields?.offersFullTime?.level?.message}
label="Level"
placeholder="e.g. L4, Junior"
required={true}
{...register(`offers.${index}.offersFullTime.level`, {
required: FieldError.REQUIRED,
})}
/>
</div>
<div className="mb-5 flex grid grid-cols-2 space-x-3">
<CompaniesTypeahead
required={true}
value={{
id: watchCompanyId,
label: watchCompanyName,
value: watchCompanyId,
}}
onSelect={(option) => {
if (option) {
setValue(`offers.${index}.companyId`, option.value);
setValue(`offers.${index}.companyName`, option.label);
}
}}
/>
<CitiesTypeahead
label="Location"
required={true}
value={{
id: watchCityId,
label: watchCityName,
value: watchCityId,
}}
onSelect={(option) => {
if (option) {
setValue(`offers.${index}.cityId`, option.value);
setValue(`offers.${index}.cityName`, option.label);
} else {
setValue(`offers.${index}.cityId`, '');
setValue(`offers.${index}.cityName`, '');
}
}}
/>
</div>
<div className="mb-5 flex grid grid-cols-2 items-start space-x-3">
<FormMonthYearPicker
monthLabel="Date Received"
monthRequired={true}
yearLabel=""
{...register(`offers.${index}.monthYearReceived`, {
required: FieldError.REQUIRED,
})}
/>
</div>
<div className="mb-5">
<FormTextInput
endAddOn={
<FormSelect
borderStyle="borderless"
defaultValue={Currency.SGD}
isLabelHidden={true}
label="Currency"
options={CURRENCY_OPTIONS}
{...register(
`offers.${index}.offersFullTime.totalCompensation.currency`,
{
required: FieldError.REQUIRED,
},
)}
/>
}
endAddOnType="element"
errorMessage={
offerFields?.offersFullTime?.totalCompensation?.value?.message
}
label="Total Compensation (Annual)"
placeholder="0"
required={true}
startAddOn="$"
startAddOnType="label"
type="number"
{...register(
`offers.${index}.offersFullTime.totalCompensation.value`,
{
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
>>>>>>> a31230f7 ([offers][feat] Use city typeahead for location field)
required: FieldError.REQUIRED,
})}
/>
@ -472,7 +375,6 @@ function InternshipOfferDetailsForm({
}}
/>
</div>
<<<<<<< HEAD
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">
<FormSelect
display="block"
@ -542,97 +444,6 @@ function InternshipOfferDetailsForm({
</div>
</FormSection>
<FormSection title="Additional Information">
=======
<CitiesTypeahead
label="Location"
required={true}
value={{
id: watchCityId,
label: watchCityName,
value: watchCityId,
}}
onSelect={(option) => {
if (option) {
setValue(`offers.${index}.cityId`, option.value);
setValue(`offers.${index}.cityName`, option.label);
} else {
setValue(`offers.${index}.cityId`, '');
setValue(`offers.${index}.cityName`, '');
}
}}
/>
</div>
<div className="mb-5 grid grid-cols-2 space-x-3">
<FormSelect
display="block"
errorMessage={offerFields?.offersIntern?.internshipCycle?.message}
label="Internship Cycle"
options={internshipCycleOptions}
placeholder={emptyOption}
required={true}
{...register(`offers.${index}.offersIntern.internshipCycle`, {
required: FieldError.REQUIRED,
})}
/>
<FormSelect
display="block"
errorMessage={offerFields?.offersIntern?.startYear?.message}
label="Internship Year"
options={yearOptions}
placeholder={emptyOption}
required={true}
{...register(`offers.${index}.offersIntern.startYear`, {
required: FieldError.REQUIRED,
valueAsNumber: true,
})}
/>
</div>
<div className="mb-5">
<FormMonthYearPicker
monthLabel="Date Received"
monthRequired={true}
yearLabel=""
{...register(`offers.${index}.monthYearReceived`, {
required: FieldError.REQUIRED,
})}
/>
</div>
<div className="mb-5">
<FormTextInput
endAddOn={
<FormSelect
borderStyle="borderless"
defaultValue={Currency.SGD}
isLabelHidden={true}
label="Currency"
options={CURRENCY_OPTIONS}
{...register(
`offers.${index}.offersIntern.monthlySalary.currency`,
{
required: FieldError.REQUIRED,
},
)}
/>
}
endAddOnType="element"
errorMessage={
offerFields?.offersIntern?.monthlySalary?.value?.message
}
label="Salary (Monthly)"
placeholder="0"
required={true}
startAddOn="$"
startAddOnType="label"
type="number"
{...register(`offers.${index}.offersIntern.monthlySalary.value`, {
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
required: FieldError.REQUIRED,
valueAsNumber: true,
})}
/>
</div>
<div className="mb-5">
>>>>>>> a31230f7 ([offers][feat] Use city typeahead for location field)
<FormTextArea
label="Negotiation Strategy / Interview Performance"
placeholder="e.g. Did well in the behavioral interview. Used competing offers to negotiate for a higher salary."
@ -776,4 +587,4 @@ export default function OfferDetailsForm() {
</Dialog>
</div>
);
}
}

@ -35,7 +35,6 @@ export default function OfferCard({
}: Props) {
function UpperSection() {
return (
<<<<<<< HEAD
<div className="px-4 py-5 sm:px-6">
<div className="flex justify-between">
<div>
@ -71,17 +70,6 @@ export default function OfferCard({
</div>
)}
</div>
=======
<div className="flex justify-between px-8">
<div className="flex flex-col">
<div className="flex flex-row">
<span>
<BuildingOffice2Icon className="mr-3 h-5" />
</span>
<span className="font-bold">
{location ? `${companyName}, ${location.cityName}` : companyName}
</span>
>>>>>>> ac2d047d ([offers][feat] integrate location for offer table and profile)
</div>
<div className="space-y-2">
{!duration && receivedMonth && (
@ -111,7 +99,6 @@ export default function OfferCard({
}
return (
<<<<<<< HEAD
<div className="border-t border-slate-200 px-4 py-5 sm:px-6">
<dl className="grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-4">
{jobType === JobType.FULLTIME
@ -155,34 +142,6 @@ export default function OfferCard({
<dd className="mt-1 text-sm text-slate-900">{bonus}</dd>
</div>
)}
=======
<>
<HorizontalDivider />
<div className="px-8">
<div className="flex flex-col py-2">
{(totalCompensation || monthlySalary) && (
<div className="flex flex-row">
<span>
<CurrencyDollarIcon className="mr-3 h-5" />
</span>
<span>
<p>
{totalCompensation && `TC: ${totalCompensation}`}
{monthlySalary && `Monthly Salary: ${monthlySalary}`}
</p>
</span>
</div>
)}
{(base || stocks || bonus) && totalCompensation && (
<div className="ml-8 flex flex-row font-light">
<p>
Base / year: {base ?? 'N/A'} Stocks / year:{' '}
{stocks ?? 'N/A'} Bonus / year: {bonus ?? 'N/A'}
</p>
</div>
)}
</div>
>>>>>>> ac2d047d ([offers][feat] integrate location for offer table and profile)
{negotiationStrategy && (
<div className="col-span-2">
<dt className="text-sm font-medium text-slate-500">
@ -210,4 +169,4 @@ export default function OfferCard({
<BottomSection />
</div>
);
}
}

@ -25,19 +25,14 @@ import type { DashboardOffer, GetOffersResponse, Paging } from '~/types/offers';
const NUMBER_OF_OFFERS_IN_PAGE = 10;
export type OffersTableProps = Readonly<{
cityFilter: string;
companyFilter: string;
companyName?: string;
countryFilter: string;
jobTitleFilter: string;
}>;
export default function OffersTable({
<<<<<<< HEAD
countryFilter,
companyName,
=======
cityFilter,
>>>>>>> ac2d047d ([offers][feat] integrate location for offer table and profile)
companyFilter,
jobTitleFilter,
}: OffersTableProps) {
@ -111,8 +106,6 @@ export default function OffersTable({
[
'offers.list',
{
// Location: 'Singapore, Singapore', // TODO: Geolocation
cityId: cityFilter,
companyId: companyFilter,
countryId: countryFilter,
currency,
@ -172,17 +165,11 @@ export default function OffersTable({
/>
))}
</DropdownMenu>
<<<<<<< HEAD
<div className="divide-x-slate-200 col-span-3 flex items-center justify-end space-x-4 divide-x">
<div className="justify-left flex items-center space-x-2 font-medium text-slate-700">
<span className="sr-only sm:not-sr-only sm:inline">
Display offers in
</span>
=======
<div className="divide-x-slate-200 flex items-center space-x-4 divide-x">
<div className="justify-left flex items-center space-x-2 font-medium text-slate-700">
<span>Display offers in</span>
>>>>>>> ac2d047d ([offers][feat] integrate location for offer table and profile)
<CurrencySelector
handleCurrencyChange={(value: string) => setCurrency(value)}
selectedCurrency={currency}
@ -303,4 +290,4 @@ export default function OffersTable({
/>
</div>
);
}
}

@ -56,4 +56,4 @@ export type OfferTableSortByType =
| '-monthYearReceived'
| '-totalCompensation'
| '-totalYoe'
| '+totalYoe';
| '+totalYoe';

@ -3,30 +3,6 @@ import type { JobType } from '@prisma/client';
import type { MonthYear } from '~/components/shared/MonthYearPicker';
import type { Location } from '~/types/offers';
<<<<<<< HEAD
=======
export const HOME_URL = '/offers';
/*
* Offer Profile
*/
export const JobTypeLabel = {
FULLTIME: 'Full-time',
INTERN: 'Internship',
};
export enum EducationBackgroundType {
Bachelor = 'Bachelor',
Diploma = 'Diploma',
Masters = 'Masters',
PhD = 'PhD',
Professional = 'Professional',
Secondary = 'Secondary',
SelfTaught = 'Self-taught',
}
>>>>>>> ac2d047d ([offers][feat] integrate location for offer table and profile)
export type OffersProfilePostData = {
background: BackgroundPostData;
@ -57,13 +33,6 @@ type ExperiencePostData = {
id?: string;
jobType?: string | null;
level?: string | null;
<<<<<<< HEAD
<<<<<<< HEAD
=======
location?: Location | null;
>>>>>>> ac2d047d ([offers][feat] integrate location for offer table and profile)
=======
>>>>>>> a31230f7 ([offers][feat] Use city typeahead for location field)
monthlySalary?: Money | null;
title?: string | null;
totalCompensation?: Money | null;
@ -172,4 +141,4 @@ export type CommentEntity = {
replyingToId: string;
userId: string;
username: string;
};
};

@ -5,7 +5,6 @@ import { Banner } from '@tih/ui';
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
import OffersTable from '~/components/offers/table/OffersTable';
import CitiesTypeahead from '~/components/shared/CitiesTypeahead';
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
import Container from '~/components/shared/Container';
import CountriesTypeahead from '~/components/shared/CountriesTypeahead';
@ -13,22 +12,10 @@ import type { JobTitleType } from '~/components/shared/JobTitles';
import { JobTitleLabels } from '~/components/shared/JobTitles';
import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead';
<<<<<<< HEAD
<<<<<<< HEAD
import { useSearchParamSingle } from '~/utils/offers/useSearchParam';
export default function OffersHomePage() {
const [countryFilter, setCountryFilter] = useState('');
=======
import CitiesTypeahead from '../../components/shared/CitiesTypeahead';
=======
>>>>>>> d6d25df3 ([offers][chore] fix import of cities typeahead)
export default function OffersHomePage() {
const [jobTitleFilter, setjobTitleFilter] = useState('software-engineer');
const [companyFilter, setCompanyFilter] = useState('');
const [cityFilter, setCityFilter] = useState('');
>>>>>>> ac2d047d ([offers][feat] integrate location for offer table and profile)
const { event: gaEvent } = useGoogleAnalytics();
const [selectedCompanyName, setSelectedCompanyName] =
@ -48,7 +35,6 @@ export default function OffersHomePage() {
</Link>
.
</Banner>
<<<<<<< HEAD
<div className="text-primary-600 flex items-center justify-end space-x-1 bg-slate-100 px-4 pt-4 sm:text-lg">
<span>
<MapPinIcon className="flex h-7 w-7" />
@ -66,23 +52,6 @@ export default function OffersHomePage() {
});
} else {
setCountryFilter('');
=======
<div className="text-primary-600 flex items-center justify-end space-x-1 bg-slate-100 px-4 pt-4">
<span>
<MapPinIcon className="flex h-7 w-7" />
</span>
<CitiesTypeahead
isLabelHidden={true}
placeholder="All Cities"
onSelect={(option) => {
if (option) {
setCityFilter(option.value);
gaEvent({
action: `offers.table_filter_city_${option.value}`,
category: 'engagement',
label: 'Filter by city',
});
>>>>>>> ac2d047d ([offers][feat] integrate location for offer table and profile)
}
}}
/>
@ -162,18 +131,12 @@ export default function OffersHomePage() {
</div>
<Container className="pb-20 pt-10">
<OffersTable
<<<<<<< HEAD
companyFilter={selectedCompanyId}
companyName={selectedCompanyName}
countryFilter={countryFilter}
jobTitleFilter={selectedJobTitleId ?? ''}
=======
cityFilter={cityFilter}
companyFilter={companyFilter}
jobTitleFilter={jobTitleFilter}
>>>>>>> ac2d047d ([offers][feat] integrate location for offer table and profile)
/>
</Container>
</main>
);
}
}

@ -1,17 +0,0 @@
import React from 'react';
import { trpc } from '~/utils/trpc';
function GenerateAnalysis() {
const analysisMutation = trpc.useMutation(['offers.analysis.generate']);
return (
<div>
{JSON.stringify(
analysisMutation.mutate({ profileId: 'cl9lwe9m902k5utskjs52wc0j' }),
)}
</div>
);
}
export default GenerateAnalysis;

@ -1,14 +0,0 @@
import React from 'react';
import { trpc } from '~/utils/trpc';
function GetAnalysis() {
const analysis = trpc.useQuery([
'offers.analysis.get',
{ profileId: 'cl9lwe9m902k5utskjs52wc0j' },
]);
return <div>{JSON.stringify(analysis.data)}</div>;
}
export default GetAnalysis;

@ -1,53 +0,0 @@
import React from 'react';
import { trpc } from '~/utils/trpc';
function Test() {
const data = trpc.useQuery([
'offers.list',
{
currency: 'SGD',
limit: 100,
location: 'Singapore, Singapore',
offset: 0,
sortBy: '-totalCompensation',
yoeCategory: 1,
},
]);
const deleteMutation = trpc.useMutation(['offers.profile.delete']);
const handleDelete = (id: string) => {
deleteMutation.mutate({ profileId: id, token: ' dadaadad' });
};
return (
<ul>
<li>
<b>{JSON.stringify(data.data?.paging)}</b>
</li>
<li>
<ul>
{data.data?.data.map((offer) => {
return (
<li key={offer.id}>
<button
className="text-danger-600"
type="button"
onClick={() => {
handleDelete(offer.profileId);
}}>
DELETE THIS PROFILE AND ALL ITS OFFERS
</button>
<div>{JSON.stringify(offer)}</div>
<br />
</li>
);
})}
</ul>
</li>
</ul>
);
}
export default Test;

@ -1,386 +0,0 @@
import type { Session } from 'next-auth';
import type {
City,
Company,
Country,
OffersBackground,
OffersCurrency,
OffersFullTime,
OffersIntern,
OffersOffer,
OffersProfile,
Prisma,
PrismaClient,
State,
} from '@prisma/client';
import { TRPCError } from '@trpc/server';
import { profileAnalysisDtoMapper } from '../../mappers/offers-mappers';
const searchOfferPercentile = (
offer: OffersOffer & {
company: Company;
offersFullTime:
| (OffersFullTime & {
baseSalary: OffersCurrency;
bonus: OffersCurrency;
stocks: OffersCurrency;
totalCompensation: OffersCurrency;
})
| null;
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
profile: OffersProfile & { background: OffersBackground | null };
},
similarOffers: Array<
OffersOffer & {
company: Company;
offersFullTime:
| (OffersFullTime & {
totalCompensation: OffersCurrency;
})
| null;
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
profile: OffersProfile & { background: OffersBackground | null };
}
>,
) => {
for (let i = 0; i < similarOffers.length; i++) {
if (similarOffers[i].id === offer.id) {
return i;
}
}
return -1;
};
export const generateAnalysis = async (params: {
ctx: {
prisma: PrismaClient<
Prisma.PrismaClientOptions,
never,
Prisma.RejectOnNotFound | Prisma.RejectPerOperation | undefined
>;
session: Session | null;
};
input: { profileId: string };
}) => {
const { ctx, input } = params;
await ctx.prisma.offersAnalysis.deleteMany({
where: {
profileId: input.profileId,
},
});
const offers = await ctx.prisma.offersOffer.findMany({
include: {
company: true,
offersFullTime: {
include: {
baseSalary: true,
bonus: true,
stocks: true,
totalCompensation: true,
},
},
offersIntern: {
include: {
monthlySalary: true,
},
},
profile: {
include: {
background: true,
},
},
},
orderBy: [
{
offersFullTime: {
totalCompensation: {
baseValue: 'desc',
},
},
},
{
offersIntern: {
monthlySalary: {
baseValue: 'desc',
},
},
},
],
where: {
profileId: input.profileId,
},
});
if (!offers || offers.length === 0) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'No offers found on this profile',
});
}
const overallHighestOffer = offers[0];
// TODO: Shift yoe out of background to make it mandatory
if (
!overallHighestOffer.profile.background ||
overallHighestOffer.profile.background.totalYoe == null
) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'YOE not found',
});
}
const yoe = overallHighestOffer.profile.background.totalYoe as number;
const monthYearReceived = new Date(overallHighestOffer.monthYearReceived);
monthYearReceived.setFullYear(monthYearReceived.getFullYear() - 1);
let similarOffers = await ctx.prisma.offersOffer.findMany({
include: {
company: true,
offersFullTime: {
include: {
totalCompensation: true,
},
},
offersIntern: {
include: {
monthlySalary: true,
},
},
profile: {
include: {
background: {
include: {
experiences: {
include: {
company: true,
},
},
},
},
},
},
},
orderBy: [
{
offersFullTime: {
totalCompensation: {
baseValue: 'desc',
},
},
},
{
offersIntern: {
monthlySalary: {
baseValue: 'desc',
},
},
},
],
where: {
AND: [
{
location: overallHighestOffer.location,
},
{
monthYearReceived: {
gte: monthYearReceived,
},
},
{
OR: [
{
offersFullTime: {
title: overallHighestOffer.offersFullTime?.title,
},
offersIntern: {
title: overallHighestOffer.offersIntern?.title,
},
},
],
},
{
profile: {
background: {
AND: [
{
totalYoe: {
gte: Math.max(yoe - 1, 0),
lte: yoe + 1,
},
},
],
},
},
},
],
},
});
let similarCompanyOffers = similarOffers.filter(
(offer) => offer.companyId === overallHighestOffer.companyId,
);
// CALCULATE PERCENTILES
const overallIndex = searchOfferPercentile(
overallHighestOffer,
similarOffers,
);
const overallPercentile =
similarOffers.length === 0
? 100
: (100 * overallIndex) / similarOffers.length;
const companyIndex = searchOfferPercentile(
overallHighestOffer,
similarCompanyOffers,
);
const companyPercentile =
similarCompanyOffers.length === 0
? 100
: (100 * companyIndex) / similarCompanyOffers.length;
// FIND TOP >=90 PERCENTILE OFFERS, DOESN'T GIVE 100th PERCENTILE
// e.g. If there only 4 offers, it gives the 2nd and 3rd offer
similarOffers = similarOffers.filter(
(offer) => offer.id !== overallHighestOffer.id,
);
similarCompanyOffers = similarCompanyOffers.filter(
(offer) => offer.id !== overallHighestOffer.id,
);
const noOfSimilarOffers = similarOffers.length;
const similarOffers90PercentileIndex = Math.ceil(noOfSimilarOffers * 0.1);
const topPercentileOffers =
noOfSimilarOffers > 2
? similarOffers.slice(
similarOffers90PercentileIndex,
similarOffers90PercentileIndex + 2,
)
: similarOffers;
const noOfSimilarCompanyOffers = similarCompanyOffers.length;
const similarCompanyOffers90PercentileIndex = Math.ceil(
noOfSimilarCompanyOffers * 0.1,
);
const topPercentileCompanyOffers =
noOfSimilarCompanyOffers > 2
? similarCompanyOffers.slice(
similarCompanyOffers90PercentileIndex,
similarCompanyOffers90PercentileIndex + 2,
)
: similarCompanyOffers;
const analysis = await ctx.prisma.offersAnalysis.create({
data: {
companyPercentile,
noOfSimilarCompanyOffers,
noOfSimilarOffers,
overallHighestOffer: {
connect: {
id: overallHighestOffer.id,
},
},
overallPercentile,
profile: {
connect: {
id: input.profileId,
},
},
topCompanyOffers: {
connect: topPercentileCompanyOffers.map((offer) => {
return { id: offer.id };
}),
},
topOverallOffers: {
connect: topPercentileOffers.map((offer) => {
return { id: offer.id };
}),
},
},
include: {
overallHighestOffer: {
include: {
company: true,
offersFullTime: {
include: {
totalCompensation: true,
},
},
offersIntern: {
include: {
monthlySalary: true,
},
},
profile: {
include: {
background: true,
},
},
},
},
topCompanyOffers: {
include: {
company: true,
offersFullTime: {
include: {
totalCompensation: true,
},
},
offersIntern: {
include: {
monthlySalary: true,
},
},
profile: {
include: {
background: {
include: {
experiences: {
include: {
company: true,
},
},
},
},
},
},
},
},
topOverallOffers: {
include: {
company: true,
offersFullTime: {
include: {
totalCompensation: true,
},
},
offersIntern: {
include: {
monthlySalary: true,
},
},
profile: {
include: {
background: {
include: {
experiences: {
include: {
company: true,
},
},
},
},
},
},
},
},
},
});
return profileAnalysisDtoMapper(analysis);
};
Loading…
Cancel
Save