[offers][chore] Change location fields (#491)

* [offers][chore] Change location in schema

* Include City, State, and Country in profileDtoMapper params

* [offers][chore] Change backend endpoints to new location field

* [offers][feat] integrate cityId into create profile endpoint

* [offers][feat] integrate location with update profile endpoint

* [offers][feat] integrate location for offer table and profile

* [offers][chore] fix import of cities typeahead

* [offers][feat] Use city typeahead for location field

* [offers][fix] Fix test pages

* [offers][fix] Fix ui error

* [offers][fix] Delete test pages

Co-authored-by: Bryann Yeap Kok Keong <bryannyeapkk@gmail.com>
Co-authored-by: Stuart Long Chay Boon <chayboon@gmail.com>
Co-authored-by: Zhang Ziqing <zhangziqing9926@gmail.com>
Co-authored-by: Ai Ling <hong-ailing@hotmail.com>
This commit is contained in:
Bryann Yeap Kok Keong
2022-11-02 23:20:18 +08:00
committed by GitHub
parent 47bc5fb154
commit c0615baf5c
24 changed files with 970 additions and 684 deletions

View File

@@ -0,0 +1,21 @@
/*
Warnings:
- You are about to drop the column `location` on the `OffersExperience` table. All the data in the column will be lost.
- You are about to drop the column `location` on the `OffersOffer` table. All the data in the column will be lost.
- Added the required column `cityId` to the `OffersOffer` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "OffersExperience" DROP COLUMN "location",
ADD COLUMN "cityId" TEXT;
-- AlterTable
ALTER TABLE "OffersOffer" DROP COLUMN "location",
ADD COLUMN "cityId" TEXT NOT NULL;
-- AddForeignKey
ALTER TABLE "OffersExperience" ADD CONSTRAINT "OffersExperience_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OffersOffer" ADD CONSTRAINT "OffersOffer_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -131,6 +131,8 @@ model City {
stateId String stateId String
state State @relation(fields: [stateId], references: [id]) state State @relation(fields: [stateId], references: [id])
questionsQuestionEncounters QuestionsQuestionEncounter[] questionsQuestionEncounters QuestionsQuestionEncounter[]
OffersExperience OffersExperience[]
OffersOffer OffersOffer[]
@@unique([name, stateId]) @@unique([name, stateId])
} }
@@ -265,7 +267,8 @@ model OffersExperience {
// Add more fields // Add more fields
durationInMonths Int? durationInMonths Int?
location String? location City? @relation(fields: [cityId], references: [id])
cityId String?
// FULLTIME fields // FULLTIME fields
level String? level String?
@@ -348,8 +351,9 @@ model OffersOffer {
company Company @relation(fields: [companyId], references: [id]) company Company @relation(fields: [companyId], references: [id])
companyId String companyId String
location City @relation(fields: [cityId], references: [id])
cityId String
monthYearReceived DateTime monthYearReceived DateTime
location String
negotiationStrategy String negotiationStrategy String
comments String comments String

View File

@@ -2,21 +2,6 @@ import { EducationBackgroundType } from './types';
export const emptyOption = '----'; export const emptyOption = '----';
export const locationOptions = [
{
label: 'Singapore, Singapore',
value: 'Singapore, Singapore',
},
{
label: 'New York, US',
value: 'New York, US',
},
{
label: 'San Francisco, US',
value: 'San Francisco, US',
},
];
export const internshipCycleOptions = [ export const internshipCycleOptions = [
{ {
label: 'Summer', label: 'Summer',

View File

@@ -51,7 +51,7 @@ export default function DashboardProfileCard({
aria-hidden="true" aria-hidden="true"
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
/> />
{location} {location.cityName}
</div> </div>
)} )}
{level && ( {level && (

View File

@@ -69,9 +69,7 @@ export default function OfferProfileCard({
{getLabelForJobTitleType(title as JobTitleType)}{' '} {getLabelForJobTitleType(title as JobTitleType)}{' '}
{`(${JobTypeLabel[jobType]})`} {`(${JobTypeLabel[jobType]})`}
</p> </p>
<p> <p>{`Company: ${company.name}, ${location}`}</p>
Company: {company.name}, {location}
</p>
{level && <p>Level: {level}</p>} {level && <p>Level: {level}</p>}
</div> </div>
<div className="col-span-1 row-span-3"> <div className="col-span-1 row-span-3">

View File

@@ -26,11 +26,11 @@ import { getCurrentMonth, getCurrentYear } from '~/utils/offers/time';
import { trpc } from '~/utils/trpc'; import { trpc } from '~/utils/trpc';
const defaultOfferValues = { const defaultOfferValues = {
cityId: '',
comments: '', comments: '',
companyId: '', companyId: '',
jobTitle: '', jobTitle: '',
jobType: JobType.FULLTIME, jobType: JobType.FULLTIME,
location: '',
monthYearReceived: { monthYearReceived: {
month: getCurrentMonth() as Month, month: getCurrentMonth() as Month,
year: getCurrentYear(), year: getCurrentYear(),

View File

@@ -7,9 +7,9 @@ import {
educationLevelOptions, educationLevelOptions,
emptyOption, emptyOption,
FieldError, FieldError,
locationOptions,
} from '~/components/offers/constants'; } from '~/components/offers/constants';
import type { BackgroundPostData } from '~/components/offers/types'; import type { BackgroundPostData } from '~/components/offers/types';
import CitiesTypeahead from '~/components/shared/CitiesTypeahead';
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
import type { JobTitleType } from '~/components/shared/JobTitles'; import type { JobTitleType } from '~/components/shared/JobTitles';
import { getLabelForJobTitleType } from '~/components/shared/JobTitles'; import { getLabelForJobTitleType } from '~/components/shared/JobTitles';
@@ -102,6 +102,12 @@ function FullTimeJobFields() {
const watchCompanyName = useWatch({ const watchCompanyName = useWatch({
name: 'background.experiences.0.companyName', name: 'background.experiences.0.companyName',
}); });
const watchCityId = useWatch({
name: 'background.experiences.0.cityId',
});
const watchCityName = useWatch({
name: 'background.experiences.0.cityName',
});
return ( return (
<> <>
@@ -166,11 +172,22 @@ function FullTimeJobFields() {
placeholder="e.g. L4, Junior" placeholder="e.g. L4, Junior"
{...register(`background.experiences.0.level`)} {...register(`background.experiences.0.level`)}
/> />
<FormSelect <CitiesTypeahead
display="block"
label="Location" label="Location"
options={locationOptions} value={{
{...register(`background.experiences.0.location`)} id: watchCityId,
label: watchCityName,
value: watchCityId,
}}
onSelect={(option) => {
if (option) {
setValue('background.experiences.0.cityId', option.value);
setValue('background.experiences.0.cityName', option.label);
} else {
setValue('background.experiences.0.cityId', '');
setValue('background.experiences.0.cityName', '');
}
}}
/> />
<FormTextInput <FormTextInput
errorMessage={experiencesField?.durationInMonths?.message} errorMessage={experiencesField?.durationInMonths?.message}
@@ -202,6 +219,12 @@ function InternshipJobFields() {
const watchCompanyName = useWatch({ const watchCompanyName = useWatch({
name: 'background.experiences.0.companyName', name: 'background.experiences.0.companyName',
}); });
const watchCityId = useWatch({
name: 'background.experiences.0.cityId',
});
const watchCityName = useWatch({
name: 'background.experiences.0.cityName',
});
return ( return (
<> <>
@@ -256,12 +279,22 @@ function InternshipJobFields() {
})} })}
/> />
<Collapsible label="Add more details"> <Collapsible label="Add more details">
<FormSelect <CitiesTypeahead
display="block"
label="Location" label="Location"
options={locationOptions} value={{
placeholder={emptyOption} id: watchCityId,
{...register(`background.experiences.0.location`)} label: watchCityName,
value: watchCityId,
}}
onSelect={(option) => {
if (option) {
setValue('background.experiences.0.cityId', option.value);
setValue('background.experiences.0.cityName', option.label);
} else {
setValue('background.experiences.0.cityId', '');
setValue('background.experiences.0.cityName', '');
}
}}
/> />
</Collapsible> </Collapsible>
</> </>

View File

@@ -12,6 +12,7 @@ import { TrashIcon } from '@heroicons/react/24/outline';
import { JobType } from '@prisma/client'; import { JobType } from '@prisma/client';
import { Button, Dialog, HorizontalDivider } from '@tih/ui'; import { Button, Dialog, HorizontalDivider } from '@tih/ui';
import CitiesTypeahead from '~/components/shared/CitiesTypeahead';
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
import type { JobTitleType } from '~/components/shared/JobTitles'; import type { JobTitleType } from '~/components/shared/JobTitles';
import { getLabelForJobTitleType } from '~/components/shared/JobTitles'; import { getLabelForJobTitleType } from '~/components/shared/JobTitles';
@@ -25,7 +26,6 @@ import {
emptyOption, emptyOption,
FieldError, FieldError,
internshipCycleOptions, internshipCycleOptions,
locationOptions,
yearOptions, yearOptions,
} from '../../constants'; } from '../../constants';
import FormMonthYearPicker from '../../forms/FormMonthYearPicker'; import FormMonthYearPicker from '../../forms/FormMonthYearPicker';
@@ -64,6 +64,12 @@ function FullTimeOfferDetailsForm({
const watchCompanyName = useWatch({ const watchCompanyName = useWatch({
name: `offers.${index}.companyName`, name: `offers.${index}.companyName`,
}); });
const watchCityId = useWatch({
name: `offers.${index}.cityId`,
});
const watchCityName = useWatch({
name: `offers.${index}.cityName`,
});
const watchCurrency = useWatch({ const watchCurrency = useWatch({
name: `offers.${index}.offersFullTime.totalCompensation.currency`, name: `offers.${index}.offersFullTime.totalCompensation.currency`,
}); });
@@ -119,16 +125,23 @@ function FullTimeOfferDetailsForm({
} }
}} }}
/> />
<FormSelect <CitiesTypeahead
display="block"
errorMessage={offerFields?.location?.message}
label="Location" label="Location"
options={locationOptions}
placeholder={emptyOption}
required={true} required={true}
{...register(`offers.${index}.location`, { value={{
required: FieldError.REQUIRED, 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>
</FormSection> </FormSection>
@@ -308,6 +321,12 @@ function InternshipOfferDetailsForm({
const watchCompanyName = useWatch({ const watchCompanyName = useWatch({
name: `offers.${index}.companyName`, name: `offers.${index}.companyName`,
}); });
const watchCityId = useWatch({
name: `offers.${index}.cityId`,
});
const watchCityName = useWatch({
name: `offers.${index}.cityName`,
});
return ( return (
<div className="space-y-8 rounded-lg border border-slate-200 p-6 sm:space-y-16 sm:p-8"> <div className="space-y-8 rounded-lg border border-slate-200 p-6 sm:space-y-16 sm:p-8">
@@ -341,16 +360,23 @@ function InternshipOfferDetailsForm({
} }
}} }}
/> />
<FormSelect <CitiesTypeahead
display="block"
errorMessage={offerFields?.location?.message}
label="Location" label="Location"
options={locationOptions}
placeholder={emptyOption}
required={true} required={true}
{...register(`offers.${index}.location`, { value={{
required: FieldError.REQUIRED, 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>
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6"> <div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">

View File

@@ -40,7 +40,7 @@ export default function OfferCard({
<BuildingOffice2Icon className="mr-3 h-5" /> <BuildingOffice2Icon className="mr-3 h-5" />
</span> </span>
<span className="font-bold"> <span className="font-bold">
{location ? `${companyName}, ${location}` : companyName} {location ? `${companyName}, ${location.cityName}` : companyName}
</span> </span>
</div> </div>
<div className="ml-8 flex flex-row"> <div className="ml-8 flex flex-row">
@@ -92,11 +92,11 @@ export default function OfferCard({
</span> </span>
</div> </div>
)} )}
{totalCompensation && ( {(base || stocks || bonus) && totalCompensation && (
<div className="ml-8 flex flex-row font-light"> <div className="ml-8 flex flex-row font-light">
<p> <p>
Base / year: {base} Stocks / year: {stocks} Bonus / year:{' '} Base / year: {base ?? 'N/A'} Stocks / year:{' '}
{bonus} {stocks ?? 'N/A'} Bonus / year: {bonus ?? 'N/A'}
</p> </p>
</div> </div>
)} )}

View File

@@ -21,10 +21,12 @@ import type { DashboardOffer, GetOffersResponse, Paging } from '~/types/offers';
const NUMBER_OF_OFFERS_IN_PAGE = 10; const NUMBER_OF_OFFERS_IN_PAGE = 10;
export type OffersTableProps = Readonly<{ export type OffersTableProps = Readonly<{
cityFilter: string;
companyFilter: string; companyFilter: string;
jobTitleFilter: string; jobTitleFilter: string;
}>; }>;
export default function OffersTable({ export default function OffersTable({
cityFilter,
companyFilter, companyFilter,
jobTitleFilter, jobTitleFilter,
}: OffersTableProps) { }: OffersTableProps) {
@@ -53,10 +55,11 @@ export default function OffersTable({
[ [
'offers.list', 'offers.list',
{ {
// Location: 'Singapore, Singapore', // TODO: Geolocation
cityId: cityFilter,
companyId: companyFilter, companyId: companyFilter,
currency, currency,
limit: NUMBER_OF_OFFERS_IN_PAGE, limit: NUMBER_OF_OFFERS_IN_PAGE,
location: 'Singapore, Singapore', // TODO: Geolocation
offset: pagination.currentPage, offset: pagination.currentPage,
sortBy: OfferTableSortBy[selectedFilter] ?? '-monthYearReceived', sortBy: OfferTableSortBy[selectedFilter] ?? '-monthYearReceived',
title: jobTitleFilter, title: jobTitleFilter,
@@ -97,7 +100,7 @@ export default function OffersTable({
<div className="divide-x-slate-200 col-span-3 flex items-center justify-end space-x-4 divide-x"> <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"> <div className="justify-left flex items-center space-x-2">
<span className="sr-only sm:not-sr-only sm:inline"> <span className="sr-only sm:not-sr-only sm:inline">
View all offers in Display offers in
</span> </span>
<CurrencySelector <CurrencySelector
handleCurrencyChange={(value: string) => setCurrency(value)} handleCurrencyChange={(value: string) => setCurrency(value)}

View File

@@ -2,6 +2,8 @@ import type { JobType } from '@prisma/client';
import type { MonthYear } from '~/components/shared/MonthYearPicker'; import type { MonthYear } from '~/components/shared/MonthYearPicker';
import type { Location } from '~/types/offers';
export const HOME_URL = '/offers'; export const HOME_URL = '/offers';
/* /*
@@ -44,13 +46,14 @@ export type BackgroundPostData = {
}; };
type ExperiencePostData = { type ExperiencePostData = {
cityId?: string | null;
cityName?: string | null;
companyId?: string | null; companyId?: string | null;
companyName?: string | null; companyName?: string | null;
durationInMonths?: number | null; durationInMonths?: number | null;
id?: string; id?: string;
jobType?: string | null; jobType?: string | null;
level?: string | null; level?: string | null;
location?: string | null;
monthlySalary?: Money | null; monthlySalary?: Money | null;
title?: string | null; title?: string | null;
totalCompensation?: Money | null; totalCompensation?: Money | null;
@@ -75,12 +78,13 @@ type SpecificYoePostData = {
type SpecificYoe = SpecificYoePostData; type SpecificYoe = SpecificYoePostData;
export type OfferPostData = { export type OfferPostData = {
cityId: string;
cityName?: string;
comments: string; comments: string;
companyId: string; companyId: string;
companyName?: string; companyName?: string;
id?: string; id?: string;
jobType: JobType; jobType: JobType;
location: string;
monthYearReceived: Date; monthYearReceived: Date;
negotiationStrategy: string; negotiationStrategy: string;
offersFullTime?: OfferFullTimePostData | null; offersFullTime?: OfferFullTimePostData | null;
@@ -132,7 +136,7 @@ export type OfferDisplayData = {
jobLevel?: string | null; jobLevel?: string | null;
jobTitle?: string | null; jobTitle?: string | null;
jobType?: JobType; jobType?: JobType;
location?: string | null; location?: Location | null;
monthlySalary?: string | null; monthlySalary?: string | null;
negotiationStrategy?: string | null; negotiationStrategy?: string | null;
otherComment?: string | null; otherComment?: string | null;

View File

@@ -1,5 +1,7 @@
import type { import type {
City,
Company, Company,
Country,
OffersAnalysis, OffersAnalysis,
OffersAnalysisUnit, OffersAnalysisUnit,
OffersBackground, OffersBackground,
@@ -12,6 +14,7 @@ import type {
OffersProfile, OffersProfile,
OffersReply, OffersReply,
OffersSpecificYoe, OffersSpecificYoe,
State,
User, User,
} from '@prisma/client'; } from '@prisma/client';
import { JobType } from '@prisma/client'; import { JobType } from '@prisma/client';
@@ -28,6 +31,7 @@ import type {
Education, Education,
Experience, Experience,
GetOffersResponse, GetOffersResponse,
Location,
OffersCompany, OffersCompany,
Paging, Paging,
Profile, Profile,
@@ -42,6 +46,7 @@ import type {
const analysisOfferDtoMapper = ( const analysisOfferDtoMapper = (
offer: OffersOffer & { offer: OffersOffer & {
company: Company; company: Company;
location: City & { state: State & { country: Country } };
offersFullTime: offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency }) | (OffersFullTime & { totalCompensation: OffersCurrency })
| null; | null;
@@ -49,7 +54,14 @@ const analysisOfferDtoMapper = (
profile: OffersProfile & { profile: OffersProfile & {
background: background:
| (OffersBackground & { | (OffersBackground & {
experiences: Array<OffersExperience & { company: Company | null }>; experiences: Array<
OffersExperience & {
company: Company | null;
location:
| (City & { state: State & { country: Country } })
| null;
}
>;
}) })
| null; | null;
}; };
@@ -68,7 +80,7 @@ const analysisOfferDtoMapper = (
}, },
jobType: offer.jobType, jobType: offer.jobType,
level: offer.offersFullTime?.level ?? '', level: offer.offersFullTime?.level ?? '',
location: offer.location, location: locationDtoMapper(offer.location),
monthYearReceived: offer.monthYearReceived, monthYearReceived: offer.monthYearReceived,
negotiationStrategy: offer.negotiationStrategy, negotiationStrategy: offer.negotiationStrategy,
previousCompanies: previousCompanies:
@@ -117,6 +129,7 @@ const analysisUnitDtoMapper = (
topSimilarOffers: Array< topSimilarOffers: Array<
OffersOffer & { OffersOffer & {
company: Company; company: Company;
location: City & { state: State & { country: Country } };
offersFullTime: offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency }) | (OffersFullTime & { totalCompensation: OffersCurrency })
| null; | null;
@@ -125,7 +138,12 @@ const analysisUnitDtoMapper = (
background: background:
| (OffersBackground & { | (OffersBackground & {
experiences: Array< experiences: Array<
OffersExperience & { company: Company | null } OffersExperience & {
company: Company | null;
location:
| (City & { state: State & { country: Country } })
| null;
}
>; >;
}) })
| null; | null;
@@ -148,6 +166,7 @@ const analysisUnitDtoMapper = (
const analysisHighestOfferDtoMapper = ( const analysisHighestOfferDtoMapper = (
offer: OffersOffer & { offer: OffersOffer & {
company: Company; company: Company;
location: City & { state: State & { country: Country } };
offersFullTime: offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency }) | (OffersFullTime & { totalCompensation: OffersCurrency })
| null; | null;
@@ -159,7 +178,7 @@ const analysisHighestOfferDtoMapper = (
company: offersCompanyDtoMapper(offer.company), company: offersCompanyDtoMapper(offer.company),
id: offer.id, id: offer.id,
level: offer.offersFullTime?.level ?? '', level: offer.offersFullTime?.level ?? '',
location: offer.location, location: locationDtoMapper(offer.location),
totalYoe: offer.profile.background?.totalYoe ?? -1, totalYoe: offer.profile.background?.totalYoe ?? -1,
}; };
return analysisHighestOfferDto; return analysisHighestOfferDto;
@@ -173,6 +192,7 @@ export const profileAnalysisDtoMapper = (
topSimilarOffers: Array< topSimilarOffers: Array<
OffersOffer & { OffersOffer & {
company: Company; company: Company;
location: City & { state: State & { country: Country } };
offersFullTime: offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency }) | (OffersFullTime & { totalCompensation: OffersCurrency })
| null; | null;
@@ -183,7 +203,12 @@ export const profileAnalysisDtoMapper = (
background: background:
| (OffersBackground & { | (OffersBackground & {
experiences: Array< experiences: Array<
OffersExperience & { company: Company | null } OffersExperience & {
company: Company | null;
location:
| (City & { state: State & { country: Country } })
| null;
}
>; >;
}) })
| null; | null;
@@ -196,6 +221,7 @@ export const profileAnalysisDtoMapper = (
topSimilarOffers: Array< topSimilarOffers: Array<
OffersOffer & { OffersOffer & {
company: Company; company: Company;
location: City & { state: State & { country: Country } };
offersFullTime: offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency }) | (OffersFullTime & { totalCompensation: OffersCurrency })
| null; | null;
@@ -206,7 +232,12 @@ export const profileAnalysisDtoMapper = (
background: background:
| (OffersBackground & { | (OffersBackground & {
experiences: Array< experiences: Array<
OffersExperience & { company: Company | null } OffersExperience & {
company: Company | null;
location:
| (City & { state: State & { country: Country } })
| null;
}
>; >;
}) })
| null; | null;
@@ -216,6 +247,7 @@ export const profileAnalysisDtoMapper = (
}; };
overallHighestOffer: OffersOffer & { overallHighestOffer: OffersOffer & {
company: Company; company: Company;
location: City & { state: State & { country: Country } };
offersFullTime: offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency }) | (OffersFullTime & { totalCompensation: OffersCurrency })
| null; | null;
@@ -247,6 +279,23 @@ export const profileAnalysisDtoMapper = (
return profileAnalysisDto; return profileAnalysisDto;
}; };
export const locationDtoMapper = (
city: City & { state: State & { country: Country } },
) => {
const { state } = city;
const { country } = state;
const locationDto: Location = {
cityId: city.id,
cityName: city.name,
countryCode: country.code,
countryId: country.id,
countryName: country.name,
stateId: state.id,
stateName: state.name,
};
return locationDto;
};
export const valuationDtoMapper = (currency: { export const valuationDtoMapper = (currency: {
baseCurrency: string; baseCurrency: string;
baseValue: number; baseValue: number;
@@ -300,6 +349,7 @@ export const educationDtoMapper = (education: {
export const experienceDtoMapper = ( export const experienceDtoMapper = (
experience: OffersExperience & { experience: OffersExperience & {
company: Company | null; company: Company | null;
location: (City & { state: State & { country: Country } }) | null;
monthlySalary: OffersCurrency | null; monthlySalary: OffersCurrency | null;
totalCompensation: OffersCurrency | null; totalCompensation: OffersCurrency | null;
}, },
@@ -312,7 +362,10 @@ export const experienceDtoMapper = (
id: experience.id, id: experience.id,
jobType: experience.jobType, jobType: experience.jobType,
level: experience.level, level: experience.level,
location: experience.location, location:
experience.location != null
? locationDtoMapper(experience.location)
: null,
monthlySalary: experience.monthlySalary monthlySalary: experience.monthlySalary
? valuationDtoMapper(experience.monthlySalary) ? valuationDtoMapper(experience.monthlySalary)
: null, : null,
@@ -345,6 +398,7 @@ export const backgroundDtoMapper = (
experiences: Array< experiences: Array<
OffersExperience & { OffersExperience & {
company: Company | null; company: Company | null;
location: (City & { state: State & { country: Country } }) | null;
monthlySalary: OffersCurrency | null; monthlySalary: OffersCurrency | null;
totalCompensation: OffersCurrency | null; totalCompensation: OffersCurrency | null;
} }
@@ -383,6 +437,7 @@ export const backgroundDtoMapper = (
export const profileOfferDtoMapper = ( export const profileOfferDtoMapper = (
offer: OffersOffer & { offer: OffersOffer & {
company: Company; company: Company;
location: City & { state: State & { country: Country } };
offersFullTime: offersFullTime:
| (OffersFullTime & { | (OffersFullTime & {
baseSalary: OffersCurrency | null; baseSalary: OffersCurrency | null;
@@ -399,7 +454,7 @@ export const profileOfferDtoMapper = (
company: offersCompanyDtoMapper(offer.company), company: offersCompanyDtoMapper(offer.company),
id: offer.id, id: offer.id,
jobType: offer.jobType, jobType: offer.jobType,
location: offer.location, location: locationDtoMapper(offer.location),
monthYearReceived: offer.monthYearReceived, monthYearReceived: offer.monthYearReceived,
negotiationStrategy: offer.negotiationStrategy, negotiationStrategy: offer.negotiationStrategy,
offersFullTime: offer.offersFullTime, offersFullTime: offer.offersFullTime,
@@ -449,6 +504,7 @@ export const profileDtoMapper = (
topSimilarOffers: Array< topSimilarOffers: Array<
OffersOffer & { OffersOffer & {
company: Company; company: Company;
location: City & { state: State & { country: Country } };
offersFullTime: offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency }) | (OffersFullTime & { totalCompensation: OffersCurrency })
| null; | null;
@@ -459,7 +515,14 @@ export const profileDtoMapper = (
background: background:
| (OffersBackground & { | (OffersBackground & {
experiences: Array< experiences: Array<
OffersExperience & { company: Company | null } OffersExperience & {
company: Company | null;
location:
| (City & {
state: State & { country: Country };
})
| null;
}
>; >;
}) })
| null; | null;
@@ -472,6 +535,7 @@ export const profileDtoMapper = (
topSimilarOffers: Array< topSimilarOffers: Array<
OffersOffer & { OffersOffer & {
company: Company; company: Company;
location: City & { state: State & { country: Country } };
offersFullTime: offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency }) | (OffersFullTime & { totalCompensation: OffersCurrency })
| null; | null;
@@ -482,7 +546,12 @@ export const profileDtoMapper = (
background: background:
| (OffersBackground & { | (OffersBackground & {
experiences: Array< experiences: Array<
OffersExperience & { company: Company | null } OffersExperience & {
company: Company | null;
location:
| (City & { state: State & { country: Country } })
| null;
}
>; >;
}) })
| null; | null;
@@ -492,6 +561,7 @@ export const profileDtoMapper = (
}; };
overallHighestOffer: OffersOffer & { overallHighestOffer: OffersOffer & {
company: Company; company: Company;
location: City & { state: State & { country: Country } };
offersFullTime: offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency }) | (OffersFullTime & { totalCompensation: OffersCurrency })
| null; | null;
@@ -508,6 +578,7 @@ export const profileDtoMapper = (
experiences: Array< experiences: Array<
OffersExperience & { OffersExperience & {
company: Company | null; company: Company | null;
location: (City & { state: State & { country: Country } }) | null;
monthlySalary: OffersCurrency | null; monthlySalary: OffersCurrency | null;
totalCompensation: OffersCurrency | null; totalCompensation: OffersCurrency | null;
} }
@@ -525,6 +596,7 @@ export const profileDtoMapper = (
offers: Array< offers: Array<
OffersOffer & { OffersOffer & {
company: Company; company: Company;
location: City & { state: State & { country: Country } };
offersFullTime: offersFullTime:
| (OffersFullTime & { | (OffersFullTime & {
baseSalary: OffersCurrency | null; baseSalary: OffersCurrency | null;
@@ -656,6 +728,7 @@ export const getUserProfileResponseMapper = (
offers: Array< offers: Array<
OffersOffer & { OffersOffer & {
company: Company; company: Company;
location: City & { state: State & { country: Country } };
offersFullTime: offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency }) | (OffersFullTime & { totalCompensation: OffersCurrency })
| null; | null;
@@ -691,6 +764,7 @@ export const getUserProfileResponseMapper = (
const userProfileOfferDtoMapper = ( const userProfileOfferDtoMapper = (
offer: OffersOffer & { offer: OffersOffer & {
company: Company; company: Company;
location: City & { state: State & { country: Country } };
offersFullTime: offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency }) | (OffersFullTime & { totalCompensation: OffersCurrency })
| null; | null;
@@ -709,7 +783,7 @@ const userProfileOfferDtoMapper = (
}, },
jobType: offer.jobType, jobType: offer.jobType,
level: offer.offersFullTime?.level ?? '', level: offer.offersFullTime?.level ?? '',
location: offer.location, location: locationDtoMapper(offer.location),
monthYearReceived: offer.monthYearReceived, monthYearReceived: offer.monthYearReceived,
title: title:
offer.jobType === JobType.FULLTIME offer.jobType === JobType.FULLTIME

View File

@@ -1,9 +1,11 @@
import Link from 'next/link'; import Link from 'next/link';
import { useState } from 'react'; import { useState } from 'react';
import { MapPinIcon } from '@heroicons/react/24/outline';
import { Banner } from '@tih/ui'; import { Banner } from '@tih/ui';
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics'; import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
import OffersTable from '~/components/offers/table/OffersTable'; import OffersTable from '~/components/offers/table/OffersTable';
import CitiesTypeahead from '~/components/shared/CitiesTypeahead';
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
import Container from '~/components/shared/Container'; import Container from '~/components/shared/Container';
import type { JobTitleType } from '~/components/shared/JobTitles'; import type { JobTitleType } from '~/components/shared/JobTitles';
@@ -14,6 +16,7 @@ export default function OffersHomePage() {
'software-engineer', 'software-engineer',
); );
const [companyFilter, setCompanyFilter] = useState(''); const [companyFilter, setCompanyFilter] = useState('');
const [cityFilter, setCityFilter] = useState('');
const { event: gaEvent } = useGoogleAnalytics(); const { event: gaEvent } = useGoogleAnalytics();
return ( return (
@@ -25,6 +28,25 @@ export default function OffersHomePage() {
</Link> </Link>
. .
</Banner> </Banner>
<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',
});
}
}}
/>
</div>
<div className="bg-slate-100 py-16 px-4"> <div className="bg-slate-100 py-16 px-4">
<div> <div>
<div> <div>
@@ -66,7 +88,7 @@ export default function OffersHomePage() {
if (option) { if (option) {
setCompanyFilter(option.value); setCompanyFilter(option.value);
gaEvent({ gaEvent({
action: 'offers.table_filter_company', action: `offers.table_filter_company_${option.value}`,
category: 'engagement', category: 'engagement',
label: 'Filter by company', label: 'Filter by company',
}); });
@@ -80,6 +102,7 @@ export default function OffersHomePage() {
</div> </div>
<Container className="pb-20 pt-10"> <Container className="pb-20 pt-10">
<OffersTable <OffersTable
cityFilter={cityFilter}
companyFilter={companyFilter} companyFilter={companyFilter}
jobTitleFilter={jobTitleFilter} jobTitleFilter={jobTitleFilter}
/> />

View File

@@ -36,13 +36,14 @@ export default function OffersEditPage() {
experiences.length === 0 experiences.length === 0
? [{ jobType: JobType.FULLTIME }] ? [{ jobType: JobType.FULLTIME }]
: experiences.map((exp) => ({ : experiences.map((exp) => ({
cityId: exp.location?.cityId,
cityName: exp.location?.cityName,
companyId: exp.company?.id, companyId: exp.company?.id,
companyName: exp.company?.name, companyName: exp.company?.name,
durationInMonths: exp.durationInMonths, durationInMonths: exp.durationInMonths,
id: exp.id, id: exp.id,
jobType: exp.jobType, jobType: exp.jobType,
level: exp.level, level: exp.level,
location: exp.location,
monthlySalary: exp.monthlySalary, monthlySalary: exp.monthlySalary,
title: exp.title, title: exp.title,
totalCompensation: exp.totalCompensation, totalCompensation: exp.totalCompensation,
@@ -53,12 +54,13 @@ export default function OffersEditPage() {
}, },
id: data.id, id: data.id,
offers: data.offers.map((offer) => ({ offers: data.offers.map((offer) => ({
cityId: offer.location.cityId,
cityName: offer.location.cityName,
comments: offer.comments, comments: offer.comments,
companyId: offer.company.id, companyId: offer.company.id,
companyName: offer.company.name, companyName: offer.company.name,
id: offer.id, id: offer.id,
jobType: offer.jobType, jobType: offer.jobType,
location: offer.location,
monthYearReceived: convertToMonthYear(offer.monthYearReceived), monthYearReceived: convertToMonthYear(offer.monthYearReceived),
negotiationStrategy: offer.negotiationStrategy, negotiationStrategy: offer.negotiationStrategy,
offersFullTime: offer.offersFullTime, offersFullTime: offer.offersFullTime,

View File

@@ -1,412 +0,0 @@
import React, { useState } from 'react';
import { trpc } from '~/utils/trpc';
function Test() {
const [createdData, setCreatedData] = useState('');
const [error, setError] = useState('');
const createMutation = trpc.useMutation(['offers.profile.create'], {
onError(err) {
alert(err);
},
onSuccess(data) {
setCreatedData(JSON.stringify(data));
},
});
const addToUserProfileMutation = trpc.useMutation(
['offers.user.profile.addToUserProfile'],
{
onError(err) {
alert(err);
},
onSuccess(data) {
setCreatedData(JSON.stringify(data));
},
},
);
const deleteCommentMutation = trpc.useMutation(['offers.comments.delete'], {
onError(err) {
alert(err);
},
onSuccess(data) {
setCreatedData(JSON.stringify(data));
},
});
const handleDeleteComment = () => {
deleteCommentMutation.mutate({
id: 'cl97fprun001j7iyg6ev9x983',
profileId: 'cl96stky5002ew32gx2kale2x',
token: '24bafa6fef803f447d7f2e229b14cb8ee43f0c22dffbe41ee1c1e5e6e870f117',
userId: 'cl97dl51k001e7iygd5v5gt58',
});
};
const updateCommentMutation = trpc.useMutation(['offers.comments.update'], {
onError(err) {
alert(err);
},
onSuccess(data) {
setCreatedData(JSON.stringify(data));
},
});
const handleUpdateComment = () => {
updateCommentMutation.mutate({
id: 'cl97fxb0y001l7iyg14sdobt2',
message: 'hello hello',
profileId: 'cl96stky5002ew32gx2kale2x',
token: 'afca11e436d21bde24543718fa957c6c625335439dc504f24ee35eae7b5ef1ba',
});
};
const createCommentMutation = trpc.useMutation(['offers.comments.create'], {
onError(err) {
alert(err);
},
onSuccess(data) {
setCreatedData(JSON.stringify(data));
},
});
const handleCreate = () => {
createCommentMutation.mutate({
message: 'wassup bro',
profileId: 'cl9efyn9p004ww3u42mjgl1vn',
replyingToId: 'cl9el4xj10001w3w21o3p2iny',
userId: 'cl9ehvpng0000w3ec2mpx0bdd',
});
};
const handleLink = () => {
addToUserProfileMutation.mutate({
profileId: 'cl9efyn9p004ww3u42mjgl1vn',
token: '24bafa6fef803f447d7f2e229b14cb8ee43f0c22dffbe41ee1c1e5e6e870f117',
// UserId: 'cl9ehvpng0000w3ec2mpx0bdd',
});
};
const handleClick = () => {
createMutation.mutate({
background: {
educations: [
{
endDate: new Date('2018-09-30T07:58:54.000Z'),
field: 'Computer Science',
school: 'National University of Singapore',
startDate: new Date('2014-09-30T07:58:54.000Z'),
type: 'Bachelors',
},
],
experiences: [
{
companyId: 'cl9j4yawz0003utlp1uaa1t8o',
durationInMonths: 24,
jobType: 'FULLTIME',
level: 'Junior',
title: 'software-engineer',
totalCompensation: {
currency: 'SGD',
value: 104100,
},
},
],
specificYoes: [
{
domain: 'Front End',
yoe: 2,
},
{
domain: 'Full Stack',
yoe: 2,
},
],
totalYoe: 4,
},
offers: [
{
comments: 'I am a Raffles Institution almumni',
// Comments: '',
companyId: 'cl9j4yawz0003utlp1uaa1t8o',
jobType: 'FULLTIME',
location: 'Singapore, Singapore',
monthYearReceived: new Date('2022-09-30T07:58:54.000Z'),
negotiationStrategy: 'Leveraged having multiple offers',
offersFullTime: {
baseSalary: {
currency: 'SGD',
value: 2222,
},
bonus: {
currency: 'SGD',
value: 2222,
},
level: 'Junior',
stocks: {
currency: 'SGD',
value: 0,
},
title: 'software-engineer',
totalCompensation: {
currency: 'SGD',
value: 4444,
},
},
},
{
comments: '',
companyId: 'cl9j4yawz0003utlp1uaa1t8o',
jobType: 'FULLTIME',
location: 'Singapore, Singapore',
monthYearReceived: new Date('2022-09-30T07:58:54.000Z'),
negotiationStrategy: 'Leveraged having multiple offers',
offersFullTime: {
baseSalary: {
currency: 'SGD',
value: 84000,
},
bonus: {
currency: 'SGD',
value: 20000,
},
level: 'Junior',
stocks: {
currency: 'SGD',
value: 100,
},
title: 'software-engineer',
totalCompensation: {
currency: 'SGD',
value: 104100,
},
},
},
],
});
};
const profileId = 'cl9j50xzk008vutfqg6mta2ey'; // Remember to change this filed after testing deleting
const data = trpc.useQuery(
[
`offers.profile.listOne`,
{
profileId,
token:
'24bafa6fef803f447d7f2e229b14cb8ee43f0c22dffbe41ee1c1e5e6e870f117',
},
],
{
onError(err) {
setError(err.shape?.message || '');
},
},
);
trpc.useQuery(
[
`offers.profile.isValidToken`,
{
profileId: 'cl9scdzuh0000tt727ipone1k',
token:
'aa628d0db3ad7a5f84895537d4cca38edd0a9b8b96d869cddeb967fccf068c08',
},
],
{
onError(err) {
setError(err.shape?.message || '');
},
},
);
const replies = trpc.useQuery(
['offers.comments.getComments', { profileId }],
{
onError(err) {
setError(err.shape?.message || '');
},
},
);
const deleteMutation = trpc.useMutation(['offers.profile.delete']);
const handleDelete = (id: string) => {
deleteMutation.mutate({
profileId: id,
token: '24bafa6fef803f447d7f2e229b14cb8ee43f0c22dffbe41ee1c1e5e6e870f117',
});
};
const updateMutation = trpc.useMutation(['offers.profile.update'], {
onError(err) {
alert(err);
},
onSuccess(response) {
setCreatedData(JSON.stringify(response));
},
});
const handleUpdate = () => {
updateMutation.mutate({
background: {
educations: [
{
backgroundId: 'cl9i68fv60001tthj23g9tuv4',
endDate: new Date('2018-09-30T07:58:54.000Z'),
field: 'Computer Science',
id: 'cl9i87y7z004otthjmpsd48wo',
school: 'National University of Singapore',
startDate: new Date('2014-09-30T07:58:54.000Z'),
type: 'Bachelors',
},
],
experiences: [
{
backgroundId: 'cl9i68fv60001tthj23g9tuv4',
company: {
createdAt: new Date('2022-10-12T16:19:05.196Z'),
description:
'Meta Platforms, Inc., doing business as Meta and formerly named Facebook, Inc., and TheFacebook, Inc., is an American multinational technology conglomerate based in Menlo Park, California. The company owns Facebook, Instagram, and WhatsApp, among other products and services.',
id: 'cl9j4yawz0003utlp1uaa1t8o',
logoUrl: 'https://logo.clearbit.com/meta.com',
name: 'Meta',
slug: 'meta',
updatedAt: new Date('2022-10-12T16:19:05.196Z'),
},
companyId: 'cl9j4yawz0003utlp1uaa1t8o',
durationInMonths: 24,
// Id: 'cl9j4yawz0003utlp1uaa1t8o',
jobType: 'FULLTIME',
level: 'Junior',
monthlySalary: null,
monthlySalaryId: null,
title: 'software-engineer',
totalCompensation: {
currency: 'SGD',
id: 'cl9i68fvc0005tthj7r1rhvb1',
value: 100,
},
totalCompensationId: 'cl9i68fvc0005tthj7r1rhvb1',
},
],
id: 'cl9i68fv60001tthj23g9tuv4',
offersProfileId: 'cl9i68fv60000tthj8t3zkox0',
specificYoes: [
{
backgroundId: 'cl9i68fv60001tthj23g9tuv4',
domain: 'Backend',
id: 'cl9i68fvc0008tthjlxslzfo4',
yoe: 5,
},
{
backgroundId: 'cl9i68fv60001tthj23g9tuv4',
domain: 'Backend',
id: 'cl9i68fvc0009tthjwol3285l',
yoe: 4,
},
],
totalYoe: 1,
},
createdAt: '2022-10-13T08:28:13.518Z',
// Discussion: [],
id: 'cl9i68fv60000tthj8t3zkox0',
isEditable: true,
offers: [
{
comments: 'this IS SO IEUHDAEUIGDI',
company: {
createdAt: new Date('2022-10-12T16:19:05.196Z'),
description:
'Meta Platforms, Inc., doing business as Meta and formerly named Facebook, Inc., and TheFacebook, Inc., is an American multinational technology conglomerate based in Menlo Park, California. The company owns Facebook, Instagram, and WhatsApp, among other products and services.',
id: 'cl9j4yawz0003utlp1uaa1t8o',
logoUrl: 'https://logo.clearbit.com/meta.com',
name: 'Meta',
slug: 'meta',
updatedAt: new Date('2022-10-12T16:19:05.196Z'),
},
companyId: 'cl9j4yawz0003utlp1uaa1t8o',
id: 'cl9i68fve000ntthj5h9yvqnh',
jobType: 'FULLTIME',
location: 'Singapore, Singapore',
monthYearReceived: new Date('2022-09-30T07:58:54.000Z'),
negotiationStrategy: 'Charmed the guy with my face',
offersFullTime: {
baseSalary: {
currency: 'SGD',
id: 'cl9i68fve000ptthjn55hpoe4',
value: 1999999999,
},
baseSalaryId: 'cl9i68fve000ptthjn55hpoe4',
bonus: {
currency: 'SGD',
id: 'cl9i68fve000rtthjqo2ktljt',
value: 1410065407,
},
bonusId: 'cl9i68fve000rtthjqo2ktljt',
id: 'cl9i68fve000otthjqk0g01k0',
level: 'EXPERT',
stocks: {
currency: 'SGD',
id: 'cl9i68fvf000ttthjt2ode0cc',
value: -558038585,
},
stocksId: 'cl9i68fvf000ttthjt2ode0cc',
title: 'software-engineer',
totalCompensation: {
currency: 'SGD',
id: 'cl9i68fvf000vtthjg90s48nj',
value: 55555555,
},
totalCompensationId: 'cl9i68fvf000vtthjg90s48nj',
},
offersFullTimeId: 'cl9i68fve000otthjqk0g01k0',
offersIntern: null,
offersInternId: null,
profileId: 'cl9i68fv60000tthj8t3zkox0',
},
],
token: '24bafa6fef803f447d7f2e229b14cb8ee43f0c22dffbe41ee1c1e5e6e870f117',
userId: null,
});
};
return (
<>
<div>{createdData}</div>
<div>{JSON.stringify(replies.data?.data)}</div>
<button type="button" onClick={handleClick}>
Click Me!
</button>
<button type="button" onClick={handleUpdate}>
UPDATE!
</button>
<button type="button" onClick={handleLink}>
LINKKKK!
</button>
<button type="button" onClick={handleCreate}>
CREATE COMMENT!
</button>
<button type="button" onClick={handleDeleteComment}>
DELETE COMMENT!
</button>
<button type="button" onClick={handleUpdateComment}>
UPDATE COMMENT!
</button>
<button
className="text-danger-600"
type="button"
onClick={() => {
handleDelete(profileId);
}}>
DELETE THIS PROFILE
</button>
<div>{JSON.stringify(data.data)}</div>
<div>{JSON.stringify(error)}</div>
</>
);
}
export default Test;

View File

@@ -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: 'cl9jj2ks1001li9fn9np47wjr' }),
)}
</div>
);
}
export default GenerateAnalysis;

View File

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

View File

@@ -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: 2,
},
]);
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;

View File

@@ -19,6 +19,15 @@ export const offersAnalysisRouter = createRouter()
topSimilarOffers: { topSimilarOffers: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
offersFullTime: { offersFullTime: {
include: { include: {
totalCompensation: true, totalCompensation: true,
@@ -36,6 +45,15 @@ export const offersAnalysisRouter = createRouter()
experiences: { experiences: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
}, },
}, },
}, },
@@ -51,6 +69,15 @@ export const offersAnalysisRouter = createRouter()
topSimilarOffers: { topSimilarOffers: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
offersFullTime: { offersFullTime: {
include: { include: {
totalCompensation: true, totalCompensation: true,
@@ -68,6 +95,15 @@ export const offersAnalysisRouter = createRouter()
experiences: { experiences: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
}, },
}, },
}, },
@@ -81,6 +117,15 @@ export const offersAnalysisRouter = createRouter()
overallHighestOffer: { overallHighestOffer: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
offersFullTime: { offersFullTime: {
include: { include: {
totalCompensation: true, totalCompensation: true,

View File

@@ -35,12 +35,12 @@ const company = z.object({
}); });
const offer = z.object({ const offer = z.object({
cityId: z.string(),
comments: z.string(), comments: z.string(),
company: company.nullish(), company: company.nullish(),
companyId: z.string(), companyId: z.string(),
id: z.string().optional(), id: z.string().optional(),
jobType: z.string().regex(createValidationRegex(Object.keys(JobType), null)), jobType: z.string().regex(createValidationRegex(Object.keys(JobType), null)),
location: z.string(),
monthYearReceived: z.date(), monthYearReceived: z.date(),
negotiationStrategy: z.string(), negotiationStrategy: z.string(),
offersFullTime: z offersFullTime: z
@@ -75,6 +75,7 @@ const offer = z.object({
const experience = z.object({ const experience = z.object({
backgroundId: z.string().nullish(), backgroundId: z.string().nullish(),
cityId: z.string().nullish(),
company: company.nullish(), company: company.nullish(),
companyId: z.string().nullish(), companyId: z.string().nullish(),
durationInMonths: z.number().nullish(), durationInMonths: z.number().nullish(),
@@ -84,7 +85,6 @@ const experience = z.object({
.regex(createValidationRegex(Object.keys(JobType), null)) .regex(createValidationRegex(Object.keys(JobType), null))
.nullish(), .nullish(),
level: z.string().nullish(), level: z.string().nullish(),
location: z.string().nullish(),
monthlySalary: valuation.nullish(), monthlySalary: valuation.nullish(),
monthlySalaryId: z.string().nullish(), monthlySalaryId: z.string().nullish(),
title: z.string().nullish(), title: z.string().nullish(),
@@ -171,6 +171,15 @@ export const offersProfileRouter = createRouter()
topSimilarOffers: { topSimilarOffers: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
offersFullTime: { offersFullTime: {
include: { include: {
totalCompensation: true, totalCompensation: true,
@@ -188,6 +197,15 @@ export const offersProfileRouter = createRouter()
experiences: { experiences: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
}, },
}, },
}, },
@@ -203,6 +221,15 @@ export const offersProfileRouter = createRouter()
topSimilarOffers: { topSimilarOffers: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
offersFullTime: { offersFullTime: {
include: { include: {
totalCompensation: true, totalCompensation: true,
@@ -220,6 +247,15 @@ export const offersProfileRouter = createRouter()
experiences: { experiences: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
}, },
}, },
}, },
@@ -233,6 +269,15 @@ export const offersProfileRouter = createRouter()
overallHighestOffer: { overallHighestOffer: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
offersFullTime: { offersFullTime: {
include: { include: {
totalCompensation: true, totalCompensation: true,
@@ -258,6 +303,15 @@ export const offersProfileRouter = createRouter()
experiences: { experiences: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
monthlySalary: true, monthlySalary: true,
totalCompensation: true, totalCompensation: true,
}, },
@@ -275,6 +329,15 @@ export const offersProfileRouter = createRouter()
offers: { offers: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
offersFullTime: { offersFullTime: {
include: { include: {
baseSalary: true, baseSalary: true,
@@ -350,6 +413,39 @@ export const offersProfileRouter = createRouter()
input.background.experiences.map(async (x) => { input.background.experiences.map(async (x) => {
if (x.jobType === JobType.FULLTIME) { if (x.jobType === JobType.FULLTIME) {
if (x.companyId) { if (x.companyId) {
if (x.cityId) {
return {
company: {
connect: {
id: x.companyId,
},
},
durationInMonths: x.durationInMonths,
jobType: x.jobType,
level: x.level,
location: {
connect: {
id: x.cityId
}
},
title: x.title,
totalCompensation:
x.totalCompensation != null
? {
create: {
baseCurrency: baseCurrencyString,
baseValue: await convert(
x.totalCompensation.value,
x.totalCompensation.currency,
baseCurrencyString,
),
currency: x.totalCompensation.currency,
value: x.totalCompensation.value,
},
}
: undefined,
};
}
return { return {
company: { company: {
connect: { connect: {
@@ -377,11 +473,40 @@ export const offersProfileRouter = createRouter()
: undefined, : undefined,
}; };
} }
if (x.cityId) {
return {
durationInMonths: x.durationInMonths,
jobType: x.jobType,
level: x.level,
location: {
connect: {
where: {
id: x.cityId
}
}
},
title: x.title,
totalCompensation:
x.totalCompensation != null
? {
create: {
baseCurrency: baseCurrencyString,
baseValue: await convert(
x.totalCompensation.value,
x.totalCompensation.currency,
baseCurrencyString,
),
currency: x.totalCompensation.currency,
value: x.totalCompensation.value,
},
}
: undefined,
};
}
return { return {
durationInMonths: x.durationInMonths, durationInMonths: x.durationInMonths,
jobType: x.jobType, jobType: x.jobType,
level: x.level, level: x.level,
location: x.location,
title: x.title, title: x.title,
totalCompensation: totalCompensation:
x.totalCompensation != null x.totalCompensation != null
@@ -402,6 +527,40 @@ export const offersProfileRouter = createRouter()
} }
if (x.jobType === JobType.INTERN) { if (x.jobType === JobType.INTERN) {
if (x.companyId) { if (x.companyId) {
if (x.cityId) {
return {
company: {
connect: {
id: x.companyId,
},
},
durationInMonths: x.durationInMonths,
jobType: x.jobType,
location: {
connect: {
where: {
id: x.cityId
}
}
},
monthlySalary:
x.monthlySalary != null
? {
create: {
baseCurrency: baseCurrencyString,
baseValue: await convert(
x.monthlySalary.value,
x.monthlySalary.currency,
baseCurrencyString,
),
currency: x.monthlySalary.currency,
value: x.monthlySalary.value,
},
}
: undefined,
title: x.title,
};
}
return { return {
company: { company: {
connect: { connect: {
@@ -428,6 +587,37 @@ export const offersProfileRouter = createRouter()
title: x.title, title: x.title,
}; };
} }
if (x.cityId) {
return {
durationInMonths: x.durationInMonths,
jobType: x.jobType,
location: {
connect: {
where: {
id: x.cityId
}
}
},
monthlySalary:
x.monthlySalary != null
? {
create: {
baseCurrency: baseCurrencyString,
baseValue: await convert(
x.monthlySalary.value,
x.monthlySalary.currency,
baseCurrencyString,
),
currency: x.monthlySalary.currency,
value: x.monthlySalary.value,
},
}
: undefined,
title: x.title,
};
}
return { return {
durationInMonths: x.durationInMonths, durationInMonths: x.durationInMonths,
jobType: x.jobType, jobType: x.jobType,
@@ -488,7 +678,13 @@ export const offersProfileRouter = createRouter()
}, },
}, },
jobType: x.jobType, jobType: x.jobType,
location: x.location, location: {
connect: {
where: {
id: x.cityId
}
}
},
monthYearReceived: x.monthYearReceived, monthYearReceived: x.monthYearReceived,
negotiationStrategy: x.negotiationStrategy, negotiationStrategy: x.negotiationStrategy,
offersIntern: { offersIntern: {
@@ -528,7 +724,13 @@ export const offersProfileRouter = createRouter()
}, },
}, },
jobType: x.jobType, jobType: x.jobType,
location: x.location, location: {
connect: {
where: {
id: x.cityId
}
}
},
monthYearReceived: x.monthYearReceived, monthYearReceived: x.monthYearReceived,
negotiationStrategy: x.negotiationStrategy, negotiationStrategy: x.negotiationStrategy,
offersFullTime: { offersFullTime: {
@@ -809,14 +1011,14 @@ export const offersProfileRouter = createRouter()
), ),
currency: exp.monthlySalary.currency, currency: exp.monthlySalary.currency,
value: exp.monthlySalary.value, value: exp.monthlySalary.value,
} },
} },
} },
}, },
where: { where: {
id: exp.id id: exp.id,
} },
}) });
} }
if (exp.totalCompensation) { if (exp.totalCompensation) {
@@ -843,14 +1045,14 @@ export const offersProfileRouter = createRouter()
), ),
currency: exp.totalCompensation.currency, currency: exp.totalCompensation.currency,
value: exp.totalCompensation.value, value: exp.totalCompensation.value,
} },
} },
} },
}, },
where: { where: {
id: exp.id id: exp.id,
} },
}) });
} }
} else if (!exp.id) { } else if (!exp.id) {
// Create new experience // Create new experience
@@ -859,35 +1061,110 @@ export const offersProfileRouter = createRouter()
exp.totalCompensation?.currency != null && exp.totalCompensation?.currency != null &&
exp.totalCompensation?.value != null exp.totalCompensation?.value != null
) { ) {
// FULLTIME
if (exp.companyId) { if (exp.companyId) {
if (exp.cityId) {
await ctx.prisma.offersBackground.update({
data: {
experiences: {
create: {
company: {
connect: {
id: exp.companyId,
},
},
durationInMonths: exp.durationInMonths,
jobType: exp.jobType,
level: exp.level,
location: {
connect: {
id: exp.cityId
}
},
title: exp.title,
totalCompensation: exp.totalCompensation
? {
create: {
baseCurrency: baseCurrencyString,
baseValue: await convert(
exp.totalCompensation.value,
exp.totalCompensation.currency,
baseCurrencyString,
),
currency: exp.totalCompensation.currency,
value: exp.totalCompensation.value,
},
}
: undefined,
},
},
},
where: {
id: input.background.id,
},
});
} else {
await ctx.prisma.offersBackground.update({
data: {
experiences: {
create: {
company: {
connect: {
id: exp.companyId,
},
},
durationInMonths: exp.durationInMonths,
jobType: exp.jobType,
level: exp.level,
title: exp.title,
totalCompensation: exp.totalCompensation
? {
create: {
baseCurrency: baseCurrencyString,
baseValue: await convert(
exp.totalCompensation.value,
exp.totalCompensation.currency,
baseCurrencyString,
),
currency: exp.totalCompensation.currency,
value: exp.totalCompensation.value,
},
}
: undefined,
},
},
},
where: {
id: input.background.id,
},
});
}
} else if (exp.cityId) {
await ctx.prisma.offersBackground.update({ await ctx.prisma.offersBackground.update({
data: { data: {
experiences: { experiences: {
create: { create: {
company: {
connect: {
id: exp.companyId,
},
},
durationInMonths: exp.durationInMonths, durationInMonths: exp.durationInMonths,
jobType: exp.jobType, jobType: exp.jobType,
level: exp.level, level: exp.level,
location: exp.location, location: {
connect: {
id: exp.cityId
}
},
title: exp.title, title: exp.title,
totalCompensation: exp.totalCompensation totalCompensation: {
? { create: {
create: { baseCurrency: baseCurrencyString,
baseCurrency: baseCurrencyString, baseValue: await convert(
baseValue: await convert( exp.totalCompensation.value,
exp.totalCompensation.value, exp.totalCompensation.currency,
exp.totalCompensation.currency, baseCurrencyString,
baseCurrencyString, ),
), currency: exp.totalCompensation.currency,
currency: exp.totalCompensation.currency, value: exp.totalCompensation.value,
value: exp.totalCompensation.value, },
}, },
}
: undefined,
}, },
}, },
}, },
@@ -903,7 +1180,6 @@ export const offersProfileRouter = createRouter()
durationInMonths: exp.durationInMonths, durationInMonths: exp.durationInMonths,
jobType: exp.jobType, jobType: exp.jobType,
level: exp.level, level: exp.level,
location: exp.location,
title: exp.title, title: exp.title,
totalCompensation: { totalCompensation: {
create: { create: {
@@ -926,19 +1202,67 @@ export const offersProfileRouter = createRouter()
}); });
} }
} else if (exp.companyId) { } else if (exp.companyId) {
if (exp.cityId) {
await ctx.prisma.offersBackground.update({
data: {
experiences: {
create: {
company: {
connect: {
id: exp.companyId,
},
},
durationInMonths: exp.durationInMonths,
jobType: exp.jobType,
level: exp.level,
location: {
connect: {
id: exp.cityId
}
},
title: exp.title,
},
},
},
where: {
id: input.background.id,
},
});
} else {
await ctx.prisma.offersBackground.update({
data: {
experiences: {
create: {
company: {
connect: {
id: exp.companyId,
},
},
durationInMonths: exp.durationInMonths,
jobType: exp.jobType,
level: exp.level,
title: exp.title,
},
},
},
where: {
id: input.background.id,
},
});
}
} else if (exp.cityId) {
await ctx.prisma.offersBackground.update({ await ctx.prisma.offersBackground.update({
data: { data: {
experiences: { experiences: {
create: { create: {
company: {
connect: {
id: exp.companyId,
},
},
durationInMonths: exp.durationInMonths, durationInMonths: exp.durationInMonths,
jobType: exp.jobType, jobType: exp.jobType,
level: exp.level, level: exp.level,
location: exp.location, location: {
connect: {
id: exp.cityId
}
},
title: exp.title, title: exp.title,
}, },
}, },
@@ -955,7 +1279,6 @@ export const offersProfileRouter = createRouter()
durationInMonths: exp.durationInMonths, durationInMonths: exp.durationInMonths,
jobType: exp.jobType, jobType: exp.jobType,
level: exp.level, level: exp.level,
location: exp.location,
title: exp.title, title: exp.title,
}, },
}, },
@@ -970,19 +1293,90 @@ export const offersProfileRouter = createRouter()
exp.monthlySalary?.currency != null && exp.monthlySalary?.currency != null &&
exp.monthlySalary?.value != null exp.monthlySalary?.value != null
) { ) {
// INTERN
if (exp.companyId) { if (exp.companyId) {
if (exp.cityId) {
await ctx.prisma.offersBackground.update({
data: {
experiences: {
create: {
company: {
connect: {
id: exp.companyId,
},
},
durationInMonths: exp.durationInMonths,
jobType: exp.jobType,
location: {
connect: {
id: exp.cityId
}
},
monthlySalary: {
create: {
baseCurrency: baseCurrencyString,
baseValue: await convert(
exp.monthlySalary.value,
exp.monthlySalary.currency,
baseCurrencyString,
),
currency: exp.monthlySalary.currency,
value: exp.monthlySalary.value,
},
},
title: exp.title,
},
},
},
where: {
id: input.background.id,
},
});
} else {
await ctx.prisma.offersBackground.update({
data: {
experiences: {
create: {
company: {
connect: {
id: exp.companyId,
},
},
durationInMonths: exp.durationInMonths,
jobType: exp.jobType,
monthlySalary: {
create: {
baseCurrency: baseCurrencyString,
baseValue: await convert(
exp.monthlySalary.value,
exp.monthlySalary.currency,
baseCurrencyString,
),
currency: exp.monthlySalary.currency,
value: exp.monthlySalary.value,
},
},
title: exp.title,
},
},
},
where: {
id: input.background.id,
},
});
}
} else if (exp.cityId) {
await ctx.prisma.offersBackground.update({ await ctx.prisma.offersBackground.update({
data: { data: {
experiences: { experiences: {
create: { create: {
company: {
connect: {
id: exp.companyId,
},
},
durationInMonths: exp.durationInMonths, durationInMonths: exp.durationInMonths,
jobType: exp.jobType, jobType: exp.jobType,
location: exp.location, location: {
connect: {
id: exp.cityId
}
},
monthlySalary: { monthlySalary: {
create: { create: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
@@ -1010,7 +1404,6 @@ export const offersProfileRouter = createRouter()
create: { create: {
durationInMonths: exp.durationInMonths, durationInMonths: exp.durationInMonths,
jobType: exp.jobType, jobType: exp.jobType,
location: exp.location,
monthlySalary: { monthlySalary: {
create: { create: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
@@ -1033,18 +1426,64 @@ export const offersProfileRouter = createRouter()
}); });
} }
} else if (exp.companyId) { } else if (exp.companyId) {
if (exp.cityId) {
await ctx.prisma.offersBackground.update({
data: {
experiences: {
create: {
company: {
connect: {
id: exp.companyId,
},
},
durationInMonths: exp.durationInMonths,
jobType: exp.jobType,
location: {
connect: {
id: exp.cityId,
}
},
title: exp.title,
},
},
},
where: {
id: input.background.id,
},
});
} else {
await ctx.prisma.offersBackground.update({
data: {
experiences: {
create: {
company: {
connect: {
id: exp.companyId,
},
},
durationInMonths: exp.durationInMonths,
jobType: exp.jobType,
title: exp.title,
},
},
},
where: {
id: input.background.id,
},
});
}
} else if (exp.cityId) {
await ctx.prisma.offersBackground.update({ await ctx.prisma.offersBackground.update({
data: { data: {
experiences: { experiences: {
create: { create: {
company: {
connect: {
id: exp.companyId,
},
},
durationInMonths: exp.durationInMonths, durationInMonths: exp.durationInMonths,
jobType: exp.jobType, jobType: exp.jobType,
location: exp.location, location: {
connect: {
id: exp.cityId
}
},
title: exp.title, title: exp.title,
}, },
}, },
@@ -1060,7 +1499,6 @@ export const offersProfileRouter = createRouter()
create: { create: {
durationInMonths: exp.durationInMonths, durationInMonths: exp.durationInMonths,
jobType: exp.jobType, jobType: exp.jobType,
location: exp.location,
title: exp.title, title: exp.title,
}, },
}, },
@@ -1148,12 +1586,20 @@ export const offersProfileRouter = createRouter()
await ctx.prisma.offersOffer.update({ await ctx.prisma.offersOffer.update({
data: { data: {
comments: offerToUpdate.comments, comments: offerToUpdate.comments,
companyId: offerToUpdate.companyId, company: {
connect: {
id: offerToUpdate.companyId
}
},
jobType: jobType:
offerToUpdate.jobType === JobType.FULLTIME offerToUpdate.jobType === JobType.FULLTIME
? JobType.FULLTIME ? JobType.FULLTIME
: JobType.INTERN, : JobType.INTERN,
location: offerToUpdate.location, location: {
connect: {
id: offerToUpdate.cityId
}
},
monthYearReceived: offerToUpdate.monthYearReceived, monthYearReceived: offerToUpdate.monthYearReceived,
negotiationStrategy: offerToUpdate.negotiationStrategy, negotiationStrategy: offerToUpdate.negotiationStrategy,
}, },
@@ -1176,7 +1622,8 @@ export const offersProfileRouter = createRouter()
offerToUpdate.offersIntern.monthlySalary.currency, offerToUpdate.offersIntern.monthlySalary.currency,
baseCurrencyString, baseCurrencyString,
), ),
currency: offerToUpdate.offersIntern.monthlySalary.currency, currency:
offerToUpdate.offersIntern.monthlySalary.currency,
value: offerToUpdate.offersIntern.monthlySalary.value, value: offerToUpdate.offersIntern.monthlySalary.value,
}, },
update: { update: {
@@ -1186,13 +1633,14 @@ export const offersProfileRouter = createRouter()
offerToUpdate.offersIntern.monthlySalary.currency, offerToUpdate.offersIntern.monthlySalary.currency,
baseCurrencyString, baseCurrencyString,
), ),
currency: offerToUpdate.offersIntern.monthlySalary.currency, currency:
offerToUpdate.offersIntern.monthlySalary.currency,
value: offerToUpdate.offersIntern.monthlySalary.value, value: offerToUpdate.offersIntern.monthlySalary.value,
} },
} },
}, },
startYear: offerToUpdate.offersIntern.startYear ?? undefined, startYear: offerToUpdate.offersIntern.startYear ?? undefined,
title: offerToUpdate.offersIntern.title title: offerToUpdate.offersIntern.title,
}, },
where: { where: {
id: offerToUpdate.offersIntern.id, id: offerToUpdate.offersIntern.id,
@@ -1222,7 +1670,8 @@ export const offersProfileRouter = createRouter()
offerToUpdate.offersFullTime.baseSalary.currency, offerToUpdate.offersFullTime.baseSalary.currency,
baseCurrencyString, baseCurrencyString,
), ),
currency: offerToUpdate.offersFullTime.baseSalary.currency, currency:
offerToUpdate.offersFullTime.baseSalary.currency,
value: offerToUpdate.offersFullTime.baseSalary.value, value: offerToUpdate.offersFullTime.baseSalary.value,
}, },
update: { update: {
@@ -1232,11 +1681,12 @@ export const offersProfileRouter = createRouter()
offerToUpdate.offersFullTime.baseSalary.currency, offerToUpdate.offersFullTime.baseSalary.currency,
baseCurrencyString, baseCurrencyString,
), ),
currency: offerToUpdate.offersFullTime.baseSalary.currency, currency:
offerToUpdate.offersFullTime.baseSalary.currency,
value: offerToUpdate.offersFullTime.baseSalary.value, value: offerToUpdate.offersFullTime.baseSalary.value,
} },
} },
} },
}, },
where: { where: {
id: offerToUpdate.offersFullTime.id, id: offerToUpdate.offersFullTime.id,
@@ -1267,9 +1717,9 @@ export const offersProfileRouter = createRouter()
), ),
currency: offerToUpdate.offersFullTime.bonus.currency, currency: offerToUpdate.offersFullTime.bonus.currency,
value: offerToUpdate.offersFullTime.bonus.value, value: offerToUpdate.offersFullTime.bonus.value,
} },
} },
} },
}, },
where: { where: {
id: offerToUpdate.offersFullTime.id, id: offerToUpdate.offersFullTime.id,
@@ -1288,7 +1738,8 @@ export const offersProfileRouter = createRouter()
offerToUpdate.offersFullTime.stocks.currency, offerToUpdate.offersFullTime.stocks.currency,
baseCurrencyString, baseCurrencyString,
), ),
currency: offerToUpdate.offersFullTime.stocks.currency, currency:
offerToUpdate.offersFullTime.stocks.currency,
value: offerToUpdate.offersFullTime.stocks.value, value: offerToUpdate.offersFullTime.stocks.value,
}, },
update: { update: {
@@ -1298,11 +1749,12 @@ export const offersProfileRouter = createRouter()
offerToUpdate.offersFullTime.stocks.currency, offerToUpdate.offersFullTime.stocks.currency,
baseCurrencyString, baseCurrencyString,
), ),
currency: offerToUpdate.offersFullTime.stocks.currency, currency:
offerToUpdate.offersFullTime.stocks.currency,
value: offerToUpdate.offersFullTime.stocks.value, value: offerToUpdate.offersFullTime.stocks.value,
} },
} },
} },
}, },
where: { where: {
id: offerToUpdate.offersFullTime.id, id: offerToUpdate.offersFullTime.id,
@@ -1317,24 +1769,32 @@ export const offersProfileRouter = createRouter()
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
baseValue: await convert( baseValue: await convert(
offerToUpdate.offersFullTime.totalCompensation.value, offerToUpdate.offersFullTime.totalCompensation.value,
offerToUpdate.offersFullTime.totalCompensation.currency, offerToUpdate.offersFullTime.totalCompensation
.currency,
baseCurrencyString, baseCurrencyString,
), ),
currency: offerToUpdate.offersFullTime.totalCompensation.currency, currency:
value: offerToUpdate.offersFullTime.totalCompensation.value, offerToUpdate.offersFullTime.totalCompensation
.currency,
value:
offerToUpdate.offersFullTime.totalCompensation.value,
}, },
update: { update: {
baseCurrency: baseCurrencyString, baseCurrency: baseCurrencyString,
baseValue: await convert( baseValue: await convert(
offerToUpdate.offersFullTime.totalCompensation.value, offerToUpdate.offersFullTime.totalCompensation.value,
offerToUpdate.offersFullTime.totalCompensation.currency, offerToUpdate.offersFullTime.totalCompensation
.currency,
baseCurrencyString, baseCurrencyString,
), ),
currency: offerToUpdate.offersFullTime.totalCompensation.currency, currency:
value: offerToUpdate.offersFullTime.totalCompensation.value, offerToUpdate.offersFullTime.totalCompensation
} .currency,
} value:
} offerToUpdate.offersFullTime.totalCompensation.value,
},
},
},
}, },
where: { where: {
id: offerToUpdate.offersFullTime.id, id: offerToUpdate.offersFullTime.id,
@@ -1362,7 +1822,11 @@ export const offersProfileRouter = createRouter()
}, },
}, },
jobType: offerToUpdate.jobType, jobType: offerToUpdate.jobType,
location: offerToUpdate.location, location: {
connect: {
id: offerToUpdate.cityId
}
},
monthYearReceived: offerToUpdate.monthYearReceived, monthYearReceived: offerToUpdate.monthYearReceived,
negotiationStrategy: offerToUpdate.negotiationStrategy, negotiationStrategy: offerToUpdate.negotiationStrategy,
offersIntern: { offersIntern: {
@@ -1416,7 +1880,11 @@ export const offersProfileRouter = createRouter()
}, },
}, },
jobType: offerToUpdate.jobType, jobType: offerToUpdate.jobType,
location: offerToUpdate.location, location: {
connect: {
id: offerToUpdate.cityId
}
},
monthYearReceived: offerToUpdate.monthYearReceived, monthYearReceived: offerToUpdate.monthYearReceived,
negotiationStrategy: offerToUpdate.negotiationStrategy, negotiationStrategy: offerToUpdate.negotiationStrategy,
offersFullTime: { offersFullTime: {

View File

@@ -57,6 +57,15 @@ export const offersUserProfileRouter = createProtectedRouter()
offers: { offers: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
offersFullTime: { offersFullTime: {
include: { include: {
totalCompensation: true, totalCompensation: true,

View File

@@ -40,12 +40,12 @@ const getYoeRange = (yoeCategory: number) => {
export const offersRouter = createRouter().query('list', { export const offersRouter = createRouter().query('list', {
input: z.object({ input: z.object({
cityId: z.string(),
companyId: z.string().nullish(), companyId: z.string().nullish(),
currency: z.string().nullish(), currency: z.string().nullish(),
dateEnd: z.date().nullish(), dateEnd: z.date().nullish(),
dateStart: z.date().nullish(), dateStart: z.date().nullish(),
limit: z.number().positive(), limit: z.number().positive(),
location: z.string(),
offset: z.number().nonnegative(), offset: z.number().nonnegative(),
salaryMax: z.number().nonnegative().nullish(), salaryMax: z.number().nonnegative().nullish(),
salaryMin: z.number().nonnegative().nullish(), salaryMin: z.number().nonnegative().nullish(),
@@ -129,8 +129,7 @@ export const offersRouter = createRouter().query('list', {
where: { where: {
AND: [ AND: [
{ {
location: cityId: input.cityId.length === 0 ? undefined : input.cityId,
input.location.length === 0 ? undefined : input.location,
}, },
{ {
offersIntern: { offersIntern: {
@@ -243,8 +242,7 @@ export const offersRouter = createRouter().query('list', {
where: { where: {
AND: [ AND: [
{ {
location: cityId: input.cityId.length === 0 ? undefined : input.cityId,
input.location.length === 0 ? undefined : input.location,
}, },
{ {
offersIntern: { offersIntern: {

View File

@@ -25,7 +25,7 @@ export type Experience = {
id: string; id: string;
jobType: JobType?; jobType: JobType?;
level: string?; level: string?;
location: string?; location: Location?;
monthlySalary: Valuation?; monthlySalary: Valuation?;
title: string?; title: string?;
totalCompensation: Valuation?; totalCompensation: Valuation?;
@@ -79,7 +79,7 @@ export type ProfileOffer = {
company: OffersCompany; company: OffersCompany;
id: string; id: string;
jobType: JobType; jobType: JobType;
location: string; location: Location;
monthYearReceived: Date; monthYearReceived: Date;
negotiationStrategy: string; negotiationStrategy: string;
offersFullTime: FullTime?; offersFullTime: FullTime?;
@@ -163,7 +163,7 @@ export type AnalysisHighestOffer = {
company: OffersCompany; company: OffersCompany;
id: string; id: string;
level: string; level: string;
location: string; location: Location;
totalYoe: number; totalYoe: number;
}; };
@@ -173,7 +173,7 @@ export type AnalysisOffer = {
income: Valuation; income: Valuation;
jobType: JobType; jobType: JobType;
level: string; level: string;
location: string; location: Location;
monthYearReceived: Date; monthYearReceived: Date;
negotiationStrategy: string; negotiationStrategy: string;
previousCompanies: Array<string>; previousCompanies: Array<string>;
@@ -202,7 +202,17 @@ export type UserProfileOffer = {
income: Valuation; income: Valuation;
jobType: JobType; jobType: JobType;
level: string; level: string;
location: string; location: Location;
monthYearReceived: Date; monthYearReceived: Date;
title: string; title: string;
}; };
export type Location = {
cityId: string;
cityName: string;
countryCode: string;
countryId: string;
countryName: string;
stateId: string;
stateName: string;
};

View File

@@ -1,6 +1,8 @@
import type { Session } from 'next-auth'; import type { Session } from 'next-auth';
import type { import type {
City,
Company, Company,
Country,
OffersBackground, OffersBackground,
OffersCurrency, OffersCurrency,
OffersFullTime, OffersFullTime,
@@ -9,6 +11,7 @@ import type {
OffersProfile, OffersProfile,
Prisma, Prisma,
PrismaClient, PrismaClient,
State,
} from '@prisma/client'; } from '@prisma/client';
import { TRPCError } from '@trpc/server'; import { TRPCError } from '@trpc/server';
@@ -16,8 +19,14 @@ import { profileAnalysisDtoMapper } from '../../mappers/offers-mappers';
type Offer = OffersOffer & { type Offer = OffersOffer & {
company: Company; company: Company;
location: City & { state: State & { country: Country } };
offersFullTime: offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency }) | (OffersFullTime & {
baseSalary: OffersCurrency | null;
bonus: OffersCurrency | null;
stocks: OffersCurrency | null;
totalCompensation: OffersCurrency;
})
| null; | null;
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null; offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
profile: OffersProfile & { background: OffersBackground | null }; profile: OffersProfile & { background: OffersBackground | null };
@@ -68,6 +77,15 @@ export const generateAnalysis = async (params: {
const offers = await ctx.prisma.offersOffer.findMany({ const offers = await ctx.prisma.offersOffer.findMany({
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
offersFullTime: { offersFullTime: {
include: { include: {
baseSalary: true, baseSalary: true,
@@ -131,9 +149,18 @@ export const generateAnalysis = async (params: {
const monthYearReceived = new Date(overallHighestOffer.monthYearReceived); const monthYearReceived = new Date(overallHighestOffer.monthYearReceived);
monthYearReceived.setFullYear(monthYearReceived.getFullYear() - 1); monthYearReceived.setFullYear(monthYearReceived.getFullYear() - 1);
const similarOffers = await ctx.prisma.offersOffer.findMany({ let similarOffers = await ctx.prisma.offersOffer.findMany({
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
offersFullTime: { offersFullTime: {
include: { include: {
totalCompensation: true, totalCompensation: true,
@@ -151,6 +178,15 @@ export const generateAnalysis = async (params: {
experiences: { experiences: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
}, },
}, },
}, },
@@ -225,7 +261,7 @@ export const generateAnalysis = async (params: {
const companyAnalysis = Array.from(companyMap.values()).map( const companyAnalysis = Array.from(companyMap.values()).map(
(companyOffer) => { (companyOffer) => {
// TODO: Refactor calculating analysis into a function // TODO: Refactor calculating analysis into a function
const similarCompanyOffers = similarOffers.filter( let similarCompanyOffers = similarOffers.filter(
(offer) => offer.companyId === companyOffer.companyId, (offer) => offer.companyId === companyOffer.companyId,
); );
@@ -239,23 +275,21 @@ export const generateAnalysis = async (params: {
: 100 - (100 * companyIndex) / (similarCompanyOffers.length - 1); : 100 - (100 * companyIndex) / (similarCompanyOffers.length - 1);
// Get top offers (excluding user's offer) // Get top offers (excluding user's offer)
const similarCompanyOffersWithoutUsersOffers = similarCompanyOffers = similarCompanyOffers.filter(
similarCompanyOffers.filter( (offer) => offer.id !== companyOffer.id,
(offer) => offer.profileId !== input.profileId, );
);
const noOfSimilarCompanyOffers = const noOfSimilarCompanyOffers = similarCompanyOffers.length;
similarCompanyOffersWithoutUsersOffers.length;
const similarCompanyOffers90PercentileIndex = Math.ceil( const similarCompanyOffers90PercentileIndex = Math.ceil(
noOfSimilarCompanyOffers * 0.1, noOfSimilarCompanyOffers * 0.1,
); );
const topPercentileCompanyOffers = const topPercentileCompanyOffers =
noOfSimilarCompanyOffers > 2 noOfSimilarCompanyOffers > 2
? similarCompanyOffersWithoutUsersOffers.slice( ? similarCompanyOffers.slice(
similarCompanyOffers90PercentileIndex, similarCompanyOffers90PercentileIndex,
similarCompanyOffers90PercentileIndex + 2, similarCompanyOffers90PercentileIndex + 2,
) )
: similarCompanyOffersWithoutUsersOffers; : similarCompanyOffers;
return { return {
companyName: companyOffer.company.name, companyName: companyOffer.company.name,
@@ -276,19 +310,19 @@ export const generateAnalysis = async (params: {
? 100 ? 100
: 100 - (100 * overallIndex) / (similarOffers.length - 1); : 100 - (100 * overallIndex) / (similarOffers.length - 1);
const similarOffersWithoutUsersOffers = similarOffers.filter( similarOffers = similarOffers.filter(
(similarOffer) => similarOffer.profileId !== input.profileId, (offer) => offer.id !== overallHighestOffer.id,
); );
const noOfSimilarOffers = similarOffersWithoutUsersOffers.length; const noOfSimilarOffers = similarOffers.length;
const similarOffers90PercentileIndex = Math.ceil(noOfSimilarOffers * 0.1); const similarOffers90PercentileIndex = Math.ceil(noOfSimilarOffers * 0.1);
const topPercentileOffers = const topPercentileOffers =
noOfSimilarOffers > 2 noOfSimilarOffers > 2
? similarOffersWithoutUsersOffers.slice( ? similarOffers.slice(
similarOffers90PercentileIndex, similarOffers90PercentileIndex,
similarOffers90PercentileIndex + 2, similarOffers90PercentileIndex + 2,
) )
: similarOffersWithoutUsersOffers; : similarOffers;
const analysis = await ctx.prisma.offersAnalysis.create({ const analysis = await ctx.prisma.offersAnalysis.create({
data: { data: {
@@ -335,6 +369,15 @@ export const generateAnalysis = async (params: {
topSimilarOffers: { topSimilarOffers: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
offersFullTime: { offersFullTime: {
include: { include: {
totalCompensation: true, totalCompensation: true,
@@ -352,6 +395,15 @@ export const generateAnalysis = async (params: {
experiences: { experiences: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
}, },
}, },
}, },
@@ -367,6 +419,15 @@ export const generateAnalysis = async (params: {
topSimilarOffers: { topSimilarOffers: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
offersFullTime: { offersFullTime: {
include: { include: {
totalCompensation: true, totalCompensation: true,
@@ -384,6 +445,15 @@ export const generateAnalysis = async (params: {
experiences: { experiences: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
}, },
}, },
}, },
@@ -397,6 +467,15 @@ export const generateAnalysis = async (params: {
overallHighestOffer: { overallHighestOffer: {
include: { include: {
company: true, company: true,
location: {
include: {
state: {
include: {
country: true,
},
},
},
},
offersFullTime: { offersFullTime: {
include: { include: {
totalCompensation: true, totalCompensation: true,