mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-28 12:43:12 +08:00
[offers][feat] Add multiple company analysis
This commit is contained in:

committed by
Bryann Yeap Kok Keong

parent
68f3c72945
commit
91696571fe
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `companyPercentile` on the `OffersAnalysis` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `noOfSimilarCompanyOffers` on the `OffersAnalysis` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `noOfSimilarOffers` on the `OffersAnalysis` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `overallPercentile` on the `OffersAnalysis` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `userId` on the `OffersProfile` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the `_TopCompanyOffers` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `_TopOverallOffers` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- Added the required column `overallAnalysisUnitId` to the `OffersAnalysis` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `updatedAt` to the `OffersAnalysis` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "OffersProfile" DROP CONSTRAINT "OffersProfile_userId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "_TopCompanyOffers" DROP CONSTRAINT "_TopCompanyOffers_A_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "_TopCompanyOffers" DROP CONSTRAINT "_TopCompanyOffers_B_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "_TopOverallOffers" DROP CONSTRAINT "_TopOverallOffers_A_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "_TopOverallOffers" DROP CONSTRAINT "_TopOverallOffers_B_fkey";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "OffersAnalysis" DROP COLUMN "companyPercentile",
|
||||||
|
DROP COLUMN "noOfSimilarCompanyOffers",
|
||||||
|
DROP COLUMN "noOfSimilarOffers",
|
||||||
|
DROP COLUMN "overallPercentile",
|
||||||
|
ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ADD COLUMN "overallAnalysisUnitId" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "OffersProfile" DROP COLUMN "userId";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "_TopCompanyOffers";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "_TopOverallOffers";
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "OffersAnalysisUnit" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"companyName" TEXT NOT NULL,
|
||||||
|
"percentile" DOUBLE PRECISION NOT NULL,
|
||||||
|
"noOfSimilarOffers" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "OffersAnalysisUnit_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_OffersProfileToUser" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_CompanyAnalysis" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_OffersAnalysisUnitToOffersOffer" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "_OffersProfileToUser_AB_unique" ON "_OffersProfileToUser"("A", "B");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_OffersProfileToUser_B_index" ON "_OffersProfileToUser"("B");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "_CompanyAnalysis_AB_unique" ON "_CompanyAnalysis"("A", "B");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_CompanyAnalysis_B_index" ON "_CompanyAnalysis"("B");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "_OffersAnalysisUnitToOffersOffer_AB_unique" ON "_OffersAnalysisUnitToOffersOffer"("A", "B");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_OffersAnalysisUnitToOffersOffer_B_index" ON "_OffersAnalysisUnitToOffersOffer"("B");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersAnalysis" ADD CONSTRAINT "OffersAnalysis_overallAnalysisUnitId_fkey" FOREIGN KEY ("overallAnalysisUnitId") REFERENCES "OffersAnalysisUnit"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_OffersProfileToUser" ADD CONSTRAINT "_OffersProfileToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "OffersProfile"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_OffersProfileToUser" ADD CONSTRAINT "_OffersProfileToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_CompanyAnalysis" ADD CONSTRAINT "_CompanyAnalysis_A_fkey" FOREIGN KEY ("A") REFERENCES "OffersAnalysis"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_CompanyAnalysis" ADD CONSTRAINT "_CompanyAnalysis_B_fkey" FOREIGN KEY ("B") REFERENCES "OffersAnalysisUnit"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_OffersAnalysisUnitToOffersOffer" ADD CONSTRAINT "_OffersAnalysisUnitToOffersOffer_A_fkey" FOREIGN KEY ("A") REFERENCES "OffersAnalysisUnit"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_OffersAnalysisUnitToOffersOffer" ADD CONSTRAINT "_OffersAnalysisUnitToOffersOffer_B_fkey" FOREIGN KEY ("B") REFERENCES "OffersOffer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -225,8 +225,7 @@ model OffersProfile {
|
|||||||
|
|
||||||
offers OffersOffer[]
|
offers OffersOffer[]
|
||||||
|
|
||||||
user User? @relation(fields: [userId], references: [id])
|
users User[]
|
||||||
userId String?
|
|
||||||
|
|
||||||
analysis OffersAnalysis?
|
analysis OffersAnalysis?
|
||||||
}
|
}
|
||||||
@ -362,9 +361,8 @@ model OffersOffer {
|
|||||||
offersFullTime OffersFullTime? @relation(fields: [offersFullTimeId], references: [id], onDelete: Cascade)
|
offersFullTime OffersFullTime? @relation(fields: [offersFullTimeId], references: [id], onDelete: Cascade)
|
||||||
offersFullTimeId String? @unique
|
offersFullTimeId String? @unique
|
||||||
|
|
||||||
OffersAnalysis OffersAnalysis? @relation("HighestOverallOffer")
|
offersAnalysis OffersAnalysis? @relation("HighestOverallOffer")
|
||||||
OffersAnalysisTopOverallOffers OffersAnalysis[] @relation("TopOverallOffers")
|
offersAnalysisUnit OffersAnalysisUnit[]
|
||||||
OffersAnalysisTopCompanyOffers OffersAnalysis[] @relation("TopCompanyOffers")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model OffersIntern {
|
model OffersIntern {
|
||||||
@ -396,7 +394,9 @@ model OffersFullTime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model OffersAnalysis {
|
model OffersAnalysis {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
profile OffersProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
|
profile OffersProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
|
||||||
profileId String @unique
|
profileId String @unique
|
||||||
@ -405,14 +405,22 @@ model OffersAnalysis {
|
|||||||
offerId String @unique
|
offerId String @unique
|
||||||
|
|
||||||
// OVERALL
|
// OVERALL
|
||||||
overallPercentile Float
|
overallAnalysis OffersAnalysisUnit @relation("OverallAnalysis", fields: [overallAnalysisUnitId], references: [id])
|
||||||
noOfSimilarOffers Int
|
overallAnalysisUnitId String
|
||||||
topOverallOffers OffersOffer[] @relation("TopOverallOffers")
|
|
||||||
|
|
||||||
// Company
|
companyAnalysis OffersAnalysisUnit[] @relation("CompanyAnalysis")
|
||||||
companyPercentile Float
|
}
|
||||||
noOfSimilarCompanyOffers Int
|
|
||||||
topCompanyOffers OffersOffer[] @relation("TopCompanyOffers")
|
model OffersAnalysisUnit {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
|
||||||
|
companyName String
|
||||||
|
percentile Float
|
||||||
|
noOfSimilarOffers Int
|
||||||
|
topSimilarOffers OffersOffer[]
|
||||||
|
|
||||||
|
offersAnalysisOverall OffersAnalysis[] @relation("OverallAnalysis")
|
||||||
|
offersAnalysisCompany OffersAnalysis[] @relation("CompanyAnalysis")
|
||||||
}
|
}
|
||||||
|
|
||||||
// End of Offers project models.
|
// End of Offers project models.
|
||||||
|
@ -7,7 +7,7 @@ const navigation: ProductNavigationItems = [
|
|||||||
|
|
||||||
const navigationAuthenticated: ProductNavigationItems = [
|
const navigationAuthenticated: ProductNavigationItems = [
|
||||||
{ href: '/offers/submit', name: 'Analyze your offers' },
|
{ href: '/offers/submit', name: 'Analyze your offers' },
|
||||||
{ href: '/offers/dashboard', name: 'Your repository' },
|
{ href: '/offers/dashboard', name: 'Your dashboard' },
|
||||||
{ href: '/offers/features', name: 'Features' },
|
{ href: '/offers/features', name: 'Features' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -6,29 +6,20 @@ import OfferPercentileAnalysisText from './OfferPercentileAnalysisText';
|
|||||||
import OfferProfileCard from './OfferProfileCard';
|
import OfferProfileCard from './OfferProfileCard';
|
||||||
import { OVERALL_TAB } from '../constants';
|
import { OVERALL_TAB } from '../constants';
|
||||||
|
|
||||||
import type {
|
import type { AnalysisUnit, ProfileAnalysis } from '~/types/offers';
|
||||||
Analysis,
|
|
||||||
AnalysisHighestOffer,
|
|
||||||
ProfileAnalysis,
|
|
||||||
} from '~/types/offers';
|
|
||||||
|
|
||||||
type OfferAnalysisData = {
|
|
||||||
offer?: AnalysisHighestOffer;
|
|
||||||
offerAnalysis?: Analysis;
|
|
||||||
};
|
|
||||||
|
|
||||||
type OfferAnalysisContentProps = Readonly<{
|
type OfferAnalysisContentProps = Readonly<{
|
||||||
analysis: OfferAnalysisData;
|
analysis: AnalysisUnit;
|
||||||
isSubmission: boolean;
|
isSubmission: boolean;
|
||||||
tab: string;
|
tab: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
function OfferAnalysisContent({
|
function OfferAnalysisContent({
|
||||||
analysis: { offer, offerAnalysis },
|
analysis,
|
||||||
tab,
|
tab,
|
||||||
isSubmission,
|
isSubmission,
|
||||||
}: OfferAnalysisContentProps) {
|
}: OfferAnalysisContentProps) {
|
||||||
if (!offerAnalysis || !offer || offerAnalysis.noOfOffers === 0) {
|
if (!analysis || analysis.noOfOffers === 0) {
|
||||||
if (tab === OVERALL_TAB) {
|
if (tab === OVERALL_TAB) {
|
||||||
return (
|
return (
|
||||||
<p className="m-10">
|
<p className="m-10">
|
||||||
@ -47,9 +38,8 @@ function OfferAnalysisContent({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<OfferPercentileAnalysisText
|
<OfferPercentileAnalysisText
|
||||||
companyName={offer.company.name}
|
analysis={analysis}
|
||||||
isSubmission={isSubmission}
|
isSubmission={isSubmission}
|
||||||
offerAnalysis={offerAnalysis}
|
|
||||||
tab={tab}
|
tab={tab}
|
||||||
/>
|
/>
|
||||||
<p className="mt-5">
|
<p className="mt-5">
|
||||||
@ -57,7 +47,7 @@ function OfferAnalysisContent({
|
|||||||
? 'Here are some of the top offers relevant to you:'
|
? 'Here are some of the top offers relevant to you:'
|
||||||
: 'Relevant top offers:'}
|
: 'Relevant top offers:'}
|
||||||
</p>
|
</p>
|
||||||
{offerAnalysis.topPercentileOffers.map((topPercentileOffer) => (
|
{analysis.topPercentileOffers.map((topPercentileOffer) => (
|
||||||
<OfferProfileCard
|
<OfferProfileCard
|
||||||
key={topPercentileOffer.id}
|
key={topPercentileOffer.id}
|
||||||
offerProfile={topPercentileOffer}
|
offerProfile={topPercentileOffer}
|
||||||
@ -77,7 +67,7 @@ function OfferAnalysisContent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OfferAnalysisProps = Readonly<{
|
type OfferAnalysisProps = Readonly<{
|
||||||
allAnalysis?: ProfileAnalysis | null;
|
allAnalysis: ProfileAnalysis;
|
||||||
isError: boolean;
|
isError: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isSubmission?: boolean;
|
isSubmission?: boolean;
|
||||||
@ -90,61 +80,55 @@ export default function OfferAnalysis({
|
|||||||
isSubmission = false,
|
isSubmission = false,
|
||||||
}: OfferAnalysisProps) {
|
}: OfferAnalysisProps) {
|
||||||
const [tab, setTab] = useState(OVERALL_TAB);
|
const [tab, setTab] = useState(OVERALL_TAB);
|
||||||
const [analysis, setAnalysis] = useState<OfferAnalysisData | null>(null);
|
const [analysis, setAnalysis] = useState<AnalysisUnit>(
|
||||||
|
allAnalysis.overallAnalysis,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tab === OVERALL_TAB) {
|
if (tab === OVERALL_TAB) {
|
||||||
setAnalysis({
|
setAnalysis(allAnalysis.overallAnalysis);
|
||||||
offer: allAnalysis?.overallHighestOffer,
|
|
||||||
offerAnalysis: allAnalysis?.overallAnalysis,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
setAnalysis({
|
setAnalysis(allAnalysis.companyAnalysis[parseInt(tab, 10)]);
|
||||||
offer: allAnalysis?.overallHighestOffer,
|
|
||||||
offerAnalysis: allAnalysis?.companyAnalysis[0],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [tab, allAnalysis]);
|
}, [tab, allAnalysis]);
|
||||||
|
|
||||||
const tabOptions = [
|
const companyTabs = allAnalysis.companyAnalysis.map((value, index) => ({
|
||||||
|
label: value.companyName,
|
||||||
|
value: `${index}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let tabOptions = [
|
||||||
{
|
{
|
||||||
label: OVERALL_TAB,
|
label: OVERALL_TAB,
|
||||||
value: OVERALL_TAB,
|
value: OVERALL_TAB,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: allAnalysis?.overallHighestOffer.company.name || '',
|
|
||||||
value: allAnalysis?.overallHighestOffer.company.id || '',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
tabOptions = tabOptions.concat(companyTabs);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
|
{isError && (
|
||||||
|
<p className="m-10 text-center">
|
||||||
|
An error occurred while generating profile analysis.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
{isLoading && <Spinner className="m-10" display="block" size="lg" />}
|
{isLoading && <Spinner className="m-10" display="block" size="lg" />}
|
||||||
{analysis && (
|
{!isError && !isLoading && (
|
||||||
<div>
|
<div>
|
||||||
{isError && (
|
<Tabs
|
||||||
<p className="m-10 text-center">
|
label="Result Navigation"
|
||||||
An error occurred while generating profile analysis.
|
tabs={tabOptions}
|
||||||
</p>
|
value={tab}
|
||||||
)}
|
onChange={setTab}
|
||||||
{!isError && !isLoading && (
|
/>
|
||||||
<div>
|
<HorizontalDivider className="mb-5" />
|
||||||
<Tabs
|
<OfferAnalysisContent
|
||||||
label="Result Navigation"
|
analysis={analysis}
|
||||||
tabs={tabOptions}
|
isSubmission={isSubmission}
|
||||||
value={tab}
|
tab={tab}
|
||||||
onChange={setTab}
|
/>
|
||||||
/>
|
|
||||||
<HorizontalDivider className="mb-5" />
|
|
||||||
<OfferAnalysisContent
|
|
||||||
analysis={analysis}
|
|
||||||
isSubmission={isSubmission}
|
|
||||||
tab={tab}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
import { OVERALL_TAB } from '../constants';
|
import { OVERALL_TAB } from '../constants';
|
||||||
|
|
||||||
import type { Analysis } from '~/types/offers';
|
import type { AnalysisUnit } from '~/types/offers';
|
||||||
|
|
||||||
type OfferPercentileAnalysisTextProps = Readonly<{
|
type OfferPercentileAnalysisTextProps = Readonly<{
|
||||||
companyName: string;
|
analysis: AnalysisUnit;
|
||||||
isSubmission: boolean;
|
isSubmission: boolean;
|
||||||
offerAnalysis: Analysis;
|
|
||||||
tab: string;
|
tab: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export default function OfferPercentileAnalysisText({
|
export default function OfferPercentileAnalysisText({
|
||||||
tab,
|
tab,
|
||||||
companyName,
|
analysis: { noOfOffers, percentile, companyName },
|
||||||
offerAnalysis: { noOfOffers, percentile },
|
|
||||||
isSubmission,
|
isSubmission,
|
||||||
}: OfferPercentileAnalysisTextProps) {
|
}: OfferPercentileAnalysisTextProps) {
|
||||||
return tab === OVERALL_TAB ? (
|
return tab === OVERALL_TAB ? (
|
||||||
|
@ -47,11 +47,13 @@ export default function OfferProfileCard({
|
|||||||
</div>
|
</div>
|
||||||
<div className="col-span-10">
|
<div className="col-span-10">
|
||||||
<p className="font-bold">{profileName}</p>
|
<p className="font-bold">{profileName}</p>
|
||||||
<div className="flex flex-row">
|
{previousCompanies.length > 0 && (
|
||||||
<BuildingOffice2Icon className="mr-2 h-5" />
|
<div className="flex flex-row">
|
||||||
<span className="mr-2 font-bold">Current:</span>
|
<BuildingOffice2Icon className="mr-2 h-5" />
|
||||||
<span>{previousCompanies[0]}</span>
|
<span className="mr-2 font-bold">Current:</span>
|
||||||
</div>
|
<span>{previousCompanies[0]}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<CalendarDaysIcon className="mr-2 h-5" />
|
<CalendarDaysIcon className="mr-2 h-5" />
|
||||||
<span className="mr-2 font-bold">YOE:</span>
|
<span className="mr-2 font-bold">YOE:</span>
|
||||||
|
@ -34,7 +34,7 @@ export default function OffersProfileSave({
|
|||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
showToast({
|
showToast({
|
||||||
title: `Saved to your repository!`,
|
title: `Saved to your dashboard!`,
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -95,8 +95,8 @@ export default function OffersProfileSave({
|
|||||||
</div>
|
</div>
|
||||||
<p className="mb-5 text-slate-900">
|
<p className="mb-5 text-slate-900">
|
||||||
If you do not want to keep the edit link, you can opt to save this
|
If you do not want to keep the edit link, you can opt to save this
|
||||||
profile under your account's respository. It will still only be
|
profile under your account's dashboard. It will still only be editable
|
||||||
editable by you.
|
by you.
|
||||||
</p>
|
</p>
|
||||||
<div className="mb-20">
|
<div className="mb-20">
|
||||||
<Button
|
<Button
|
||||||
|
@ -17,13 +17,17 @@ export default function OffersSubmissionAnalysis({
|
|||||||
<h5 className="mb-8 text-center text-4xl font-bold text-slate-900">
|
<h5 className="mb-8 text-center text-4xl font-bold text-slate-900">
|
||||||
Result
|
Result
|
||||||
</h5>
|
</h5>
|
||||||
<OfferAnalysis
|
{!analysis && (
|
||||||
key={3}
|
<p className="mb-8 text-center">Error generating analysis.</p>
|
||||||
allAnalysis={analysis}
|
)}
|
||||||
isError={isError}
|
{analysis && (
|
||||||
isLoading={isLoading}
|
<OfferAnalysis
|
||||||
isSubmission={true}
|
key={3}
|
||||||
/>
|
allAnalysis={analysis}
|
||||||
|
isError={isError}
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,15 @@ function ProfileAnalysis({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-8 my-4">
|
<div className="mx-8 my-4">
|
||||||
<OfferAnalysis allAnalysis={analysis} isError={false} isLoading={false} />
|
{!analysis ? (
|
||||||
|
<p>No analysis available.</p>
|
||||||
|
) : (
|
||||||
|
<OfferAnalysis
|
||||||
|
allAnalysis={analysis}
|
||||||
|
isError={false}
|
||||||
|
isLoading={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{isEditable && (
|
{isEditable && (
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button
|
<Button
|
||||||
|
@ -41,7 +41,7 @@ export default function ProfileHeader({
|
|||||||
setSelectedTab,
|
setSelectedTab,
|
||||||
}: ProfileHeaderProps) {
|
}: ProfileHeaderProps) {
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
// Const [saved, setSaved] = useState(isSaved);
|
const [saved, setSaved] = useState(isSaved);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const trpcContext = trpc.useContext();
|
const trpcContext = trpc.useContext();
|
||||||
const { offerProfileId = '', token = '' } = router.query;
|
const { offerProfileId = '', token = '' } = router.query;
|
||||||
@ -60,7 +60,7 @@ export default function ProfileHeader({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// SetSaved(true);
|
setSaved(true);
|
||||||
showToast({
|
showToast({
|
||||||
title: `Saved to dashboard!`,
|
title: `Saved to dashboard!`,
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
@ -79,7 +79,7 @@ export default function ProfileHeader({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// SetSaved(false);
|
setSaved(false);
|
||||||
showToast({
|
showToast({
|
||||||
title: `Removed from dashboard!`,
|
title: `Removed from dashboard!`,
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
@ -90,7 +90,7 @@ export default function ProfileHeader({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const toggleSaved = () => {
|
const toggleSaved = () => {
|
||||||
if (isSaved) {
|
if (saved) {
|
||||||
unsaveMutation.mutate({ profileId: offerProfileId as string });
|
unsaveMutation.mutate({ profileId: offerProfileId as string });
|
||||||
} else {
|
} else {
|
||||||
saveMutation.mutate({
|
saveMutation.mutate({
|
||||||
@ -111,10 +111,10 @@ export default function ProfileHeader({
|
|||||||
disabled={
|
disabled={
|
||||||
isLoading || saveMutation.isLoading || unsaveMutation.isLoading
|
isLoading || saveMutation.isLoading || unsaveMutation.isLoading
|
||||||
}
|
}
|
||||||
icon={isSaved ? BookmarkIconSolid : BookmarkIconOutline}
|
icon={saved ? BookmarkIconSolid : BookmarkIconOutline}
|
||||||
isLabelHidden={true}
|
isLabelHidden={true}
|
||||||
isLoading={saveMutation.isLoading || unsaveMutation.isLoading}
|
isLoading={saveMutation.isLoading || unsaveMutation.isLoading}
|
||||||
label={isSaved ? 'Remove from account' : 'Save to your account'}
|
label={saved ? 'Remove from account' : 'Save to your account'}
|
||||||
size="md"
|
size="md"
|
||||||
variant="tertiary"
|
variant="tertiary"
|
||||||
onClick={toggleSaved}
|
onClick={toggleSaved}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
Company,
|
Company,
|
||||||
OffersAnalysis,
|
OffersAnalysis,
|
||||||
|
OffersAnalysisUnit,
|
||||||
OffersBackground,
|
OffersBackground,
|
||||||
OffersCurrency,
|
OffersCurrency,
|
||||||
OffersEducation,
|
OffersEducation,
|
||||||
@ -18,9 +19,9 @@ import { TRPCError } from '@trpc/server';
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
AddToProfileResponse,
|
AddToProfileResponse,
|
||||||
Analysis,
|
|
||||||
AnalysisHighestOffer,
|
AnalysisHighestOffer,
|
||||||
AnalysisOffer,
|
AnalysisOffer,
|
||||||
|
AnalysisUnit,
|
||||||
Background,
|
Background,
|
||||||
CreateOfferProfileResponse,
|
CreateOfferProfileResponse,
|
||||||
DashboardOffer,
|
DashboardOffer,
|
||||||
@ -111,32 +112,33 @@ const analysisOfferDtoMapper = (
|
|||||||
return analysisOfferDto;
|
return analysisOfferDto;
|
||||||
};
|
};
|
||||||
|
|
||||||
const analysisDtoMapper = (
|
const analysisUnitDtoMapper = (
|
||||||
noOfOffers: number,
|
analysisUnit: OffersAnalysisUnit & {
|
||||||
percentile: number,
|
topSimilarOffers: Array<
|
||||||
topPercentileOffers: Array<
|
OffersOffer & {
|
||||||
OffersOffer & {
|
company: Company;
|
||||||
company: Company;
|
offersFullTime:
|
||||||
offersFullTime:
|
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
||||||
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
|
||||||
| null;
|
|
||||||
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
|
|
||||||
profile: OffersProfile & {
|
|
||||||
background:
|
|
||||||
| (OffersBackground & {
|
|
||||||
experiences: Array<
|
|
||||||
OffersExperience & { company: Company | null }
|
|
||||||
>;
|
|
||||||
})
|
|
||||||
| null;
|
| null;
|
||||||
};
|
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
|
||||||
}
|
profile: OffersProfile & {
|
||||||
>,
|
background:
|
||||||
|
| (OffersBackground & {
|
||||||
|
experiences: Array<
|
||||||
|
OffersExperience & { company: Company | null }
|
||||||
|
>;
|
||||||
|
})
|
||||||
|
| null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
},
|
||||||
) => {
|
) => {
|
||||||
const analysisDto: Analysis = {
|
const analysisDto: AnalysisUnit = {
|
||||||
noOfOffers,
|
companyName: analysisUnit.companyName,
|
||||||
percentile,
|
noOfOffers: analysisUnit.noOfSimilarOffers,
|
||||||
topPercentileOffers: topPercentileOffers.map((offer) =>
|
percentile: analysisUnit.percentile,
|
||||||
|
topPercentileOffers: analysisUnit.topSimilarOffers.map((offer) =>
|
||||||
analysisOfferDtoMapper(offer),
|
analysisOfferDtoMapper(offer),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@ -166,6 +168,52 @@ const analysisHighestOfferDtoMapper = (
|
|||||||
export const profileAnalysisDtoMapper = (
|
export const profileAnalysisDtoMapper = (
|
||||||
analysis:
|
analysis:
|
||||||
| (OffersAnalysis & {
|
| (OffersAnalysis & {
|
||||||
|
companyAnalysis: Array<
|
||||||
|
OffersAnalysisUnit & {
|
||||||
|
topSimilarOffers: Array<
|
||||||
|
OffersOffer & {
|
||||||
|
company: Company;
|
||||||
|
offersFullTime:
|
||||||
|
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
||||||
|
| null;
|
||||||
|
offersIntern:
|
||||||
|
| (OffersIntern & { monthlySalary: OffersCurrency })
|
||||||
|
| null;
|
||||||
|
profile: OffersProfile & {
|
||||||
|
background:
|
||||||
|
| (OffersBackground & {
|
||||||
|
experiences: Array<
|
||||||
|
OffersExperience & { company: Company | null }
|
||||||
|
>;
|
||||||
|
})
|
||||||
|
| null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
overallAnalysis: OffersAnalysisUnit & {
|
||||||
|
topSimilarOffers: Array<
|
||||||
|
OffersOffer & {
|
||||||
|
company: Company;
|
||||||
|
offersFullTime:
|
||||||
|
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
||||||
|
| null;
|
||||||
|
offersIntern:
|
||||||
|
| (OffersIntern & { monthlySalary: OffersCurrency })
|
||||||
|
| null;
|
||||||
|
profile: OffersProfile & {
|
||||||
|
background:
|
||||||
|
| (OffersBackground & {
|
||||||
|
experiences: Array<
|
||||||
|
OffersExperience & { company: Company | null }
|
||||||
|
>;
|
||||||
|
})
|
||||||
|
| null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
};
|
||||||
overallHighestOffer: OffersOffer & {
|
overallHighestOffer: OffersOffer & {
|
||||||
company: Company;
|
company: Company;
|
||||||
offersFullTime:
|
offersFullTime:
|
||||||
@ -176,46 +224,6 @@ export const profileAnalysisDtoMapper = (
|
|||||||
| null;
|
| null;
|
||||||
profile: OffersProfile & { background: OffersBackground | null };
|
profile: OffersProfile & { background: OffersBackground | null };
|
||||||
};
|
};
|
||||||
topCompanyOffers: Array<
|
|
||||||
OffersOffer & {
|
|
||||||
company: Company;
|
|
||||||
offersFullTime:
|
|
||||||
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
|
||||||
| null;
|
|
||||||
offersIntern:
|
|
||||||
| (OffersIntern & { monthlySalary: OffersCurrency })
|
|
||||||
| null;
|
|
||||||
profile: OffersProfile & {
|
|
||||||
background:
|
|
||||||
| (OffersBackground & {
|
|
||||||
experiences: Array<
|
|
||||||
OffersExperience & { company: Company | null }
|
|
||||||
>;
|
|
||||||
})
|
|
||||||
| null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
topOverallOffers: Array<
|
|
||||||
OffersOffer & {
|
|
||||||
company: Company;
|
|
||||||
offersFullTime:
|
|
||||||
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
|
||||||
| null;
|
|
||||||
offersIntern:
|
|
||||||
| (OffersIntern & { monthlySalary: OffersCurrency })
|
|
||||||
| null;
|
|
||||||
profile: OffersProfile & {
|
|
||||||
background:
|
|
||||||
| (OffersBackground & {
|
|
||||||
experiences: Array<
|
|
||||||
OffersExperience & { company: Company | null }
|
|
||||||
>;
|
|
||||||
})
|
|
||||||
| null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
})
|
})
|
||||||
| null,
|
| null,
|
||||||
) => {
|
) => {
|
||||||
@ -224,23 +232,17 @@ export const profileAnalysisDtoMapper = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const profileAnalysisDto: ProfileAnalysis = {
|
const profileAnalysisDto: ProfileAnalysis = {
|
||||||
companyAnalysis: [
|
companyAnalysis: analysis.companyAnalysis.map((analysisUnit) =>
|
||||||
analysisDtoMapper(
|
analysisUnitDtoMapper(analysisUnit),
|
||||||
analysis.noOfSimilarCompanyOffers,
|
|
||||||
analysis.companyPercentile,
|
|
||||||
analysis.topCompanyOffers,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
id: analysis.id,
|
|
||||||
overallAnalysis: analysisDtoMapper(
|
|
||||||
analysis.noOfSimilarOffers,
|
|
||||||
analysis.overallPercentile,
|
|
||||||
analysis.topOverallOffers,
|
|
||||||
),
|
),
|
||||||
|
createdAt: analysis.createdAt,
|
||||||
|
id: analysis.id,
|
||||||
|
overallAnalysis: analysisUnitDtoMapper(analysis.overallAnalysis),
|
||||||
overallHighestOffer: analysisHighestOfferDtoMapper(
|
overallHighestOffer: analysisHighestOfferDtoMapper(
|
||||||
analysis.overallHighestOffer,
|
analysis.overallHighestOffer,
|
||||||
),
|
),
|
||||||
profileId: analysis.profileId,
|
profileId: analysis.profileId,
|
||||||
|
updatedAt: analysis.updatedAt,
|
||||||
};
|
};
|
||||||
return profileAnalysisDto;
|
return profileAnalysisDto;
|
||||||
};
|
};
|
||||||
@ -442,6 +444,52 @@ export const profileDtoMapper = (
|
|||||||
profile: OffersProfile & {
|
profile: OffersProfile & {
|
||||||
analysis:
|
analysis:
|
||||||
| (OffersAnalysis & {
|
| (OffersAnalysis & {
|
||||||
|
companyAnalysis: Array<
|
||||||
|
OffersAnalysisUnit & {
|
||||||
|
topSimilarOffers: Array<
|
||||||
|
OffersOffer & {
|
||||||
|
company: Company;
|
||||||
|
offersFullTime:
|
||||||
|
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
||||||
|
| null;
|
||||||
|
offersIntern:
|
||||||
|
| (OffersIntern & { monthlySalary: OffersCurrency })
|
||||||
|
| null;
|
||||||
|
profile: OffersProfile & {
|
||||||
|
background:
|
||||||
|
| (OffersBackground & {
|
||||||
|
experiences: Array<
|
||||||
|
OffersExperience & { company: Company | null }
|
||||||
|
>;
|
||||||
|
})
|
||||||
|
| null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
overallAnalysis: OffersAnalysisUnit & {
|
||||||
|
topSimilarOffers: Array<
|
||||||
|
OffersOffer & {
|
||||||
|
company: Company;
|
||||||
|
offersFullTime:
|
||||||
|
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
||||||
|
| null;
|
||||||
|
offersIntern:
|
||||||
|
| (OffersIntern & { monthlySalary: OffersCurrency })
|
||||||
|
| null;
|
||||||
|
profile: OffersProfile & {
|
||||||
|
background:
|
||||||
|
| (OffersBackground & {
|
||||||
|
experiences: Array<
|
||||||
|
OffersExperience & { company: Company | null }
|
||||||
|
>;
|
||||||
|
})
|
||||||
|
| null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
};
|
||||||
overallHighestOffer: OffersOffer & {
|
overallHighestOffer: OffersOffer & {
|
||||||
company: Company;
|
company: Company;
|
||||||
offersFullTime:
|
offersFullTime:
|
||||||
@ -452,46 +500,6 @@ export const profileDtoMapper = (
|
|||||||
| null;
|
| null;
|
||||||
profile: OffersProfile & { background: OffersBackground | null };
|
profile: OffersProfile & { background: OffersBackground | null };
|
||||||
};
|
};
|
||||||
topCompanyOffers: Array<
|
|
||||||
OffersOffer & {
|
|
||||||
company: Company;
|
|
||||||
offersFullTime:
|
|
||||||
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
|
||||||
| null;
|
|
||||||
offersIntern:
|
|
||||||
| (OffersIntern & { monthlySalary: OffersCurrency })
|
|
||||||
| null;
|
|
||||||
profile: OffersProfile & {
|
|
||||||
background:
|
|
||||||
| (OffersBackground & {
|
|
||||||
experiences: Array<
|
|
||||||
OffersExperience & { company: Company | null }
|
|
||||||
>;
|
|
||||||
})
|
|
||||||
| null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
topOverallOffers: Array<
|
|
||||||
OffersOffer & {
|
|
||||||
company: Company;
|
|
||||||
offersFullTime:
|
|
||||||
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
|
||||||
| null;
|
|
||||||
offersIntern:
|
|
||||||
| (OffersIntern & { monthlySalary: OffersCurrency })
|
|
||||||
| null;
|
|
||||||
profile: OffersProfile & {
|
|
||||||
background:
|
|
||||||
| (OffersBackground & {
|
|
||||||
experiences: Array<
|
|
||||||
OffersExperience & { company: Company | null }
|
|
||||||
>;
|
|
||||||
})
|
|
||||||
| null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
})
|
})
|
||||||
| null;
|
| null;
|
||||||
background:
|
background:
|
||||||
@ -528,7 +536,7 @@ export const profileDtoMapper = (
|
|||||||
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
|
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
user: User | null;
|
users: Array<User>;
|
||||||
},
|
},
|
||||||
inputToken: string | undefined,
|
inputToken: string | undefined,
|
||||||
inputUserId: string | null | undefined,
|
inputUserId: string | null | undefined,
|
||||||
@ -548,18 +556,12 @@ export const profileDtoMapper = (
|
|||||||
profileDto.editToken = profile.editToken ?? null;
|
profileDto.editToken = profile.editToken ?? null;
|
||||||
profileDto.isEditable = true;
|
profileDto.isEditable = true;
|
||||||
|
|
||||||
const users = profile.user;
|
const { users } = profile;
|
||||||
|
|
||||||
// TODO: BRYANN UNCOMMENT THIS ONCE U CHANGE THE SCHEMA
|
for (let i = 0; i < users.length; i++) {
|
||||||
// for (let i = 0; i < users.length; i++) {
|
if (users[i].id === inputUserId) {
|
||||||
// if (users[i].id === inputUserId) {
|
profileDto.isSaved = true;
|
||||||
// profileDto.isSaved = true
|
}
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TODO: REMOVE THIS ONCE U CHANGE THE SCHEMA
|
|
||||||
if (users?.id === inputUserId) {
|
|
||||||
profileDto.isSaved = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,11 +71,11 @@ export default function ProfilesDashboard() {
|
|||||||
{!userProfilesQuery.isLoading && (
|
{!userProfilesQuery.isLoading && (
|
||||||
<div className="mt-8 overflow-y-auto">
|
<div className="mt-8 overflow-y-auto">
|
||||||
<h1 className="mx-auto mb-4 w-3/4 text-start text-4xl font-bold text-slate-900">
|
<h1 className="mx-auto mb-4 w-3/4 text-start text-4xl font-bold text-slate-900">
|
||||||
Your repository
|
Your dashboard
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mx-auto w-3/4 text-start text-xl text-slate-900">
|
<p className="mx-auto w-3/4 text-start text-xl text-slate-900">
|
||||||
Save your offer profiles to respository to easily access and edit
|
Save your offer profiles to dashboard to easily access and edit them
|
||||||
them later.
|
later.
|
||||||
</p>
|
</p>
|
||||||
<div className="justfy-center mt-8 flex w-screen">
|
<div className="justfy-center mt-8 flex w-screen">
|
||||||
<ul className="mx-auto w-3/4 space-y-3" role="list">
|
<ul className="mx-auto w-3/4 space-y-3" role="list">
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Error from 'next/error';
|
import Error from 'next/error';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Spinner, useToast } from '@tih/ui';
|
import { Spinner, useToast } from '@tih/ui';
|
||||||
|
|
||||||
@ -34,11 +35,16 @@ export default function OfferProfile() {
|
|||||||
ProfileDetailTab.OFFERS,
|
ProfileDetailTab.OFFERS,
|
||||||
);
|
);
|
||||||
const [analysis, setAnalysis] = useState<ProfileAnalysis>();
|
const [analysis, setAnalysis] = useState<ProfileAnalysis>();
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
const getProfileQuery = trpc.useQuery(
|
const getProfileQuery = trpc.useQuery(
|
||||||
[
|
[
|
||||||
'offers.profile.listOne',
|
'offers.profile.listOne',
|
||||||
{ profileId: offerProfileId as string, token: token as string },
|
{
|
||||||
|
profileId: offerProfileId as string,
|
||||||
|
token: token as string,
|
||||||
|
userId: session?.user?.id,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
enabled: typeof offerProfileId === 'string',
|
enabled: typeof offerProfileId === 'string',
|
||||||
|
@ -14,20 +14,17 @@ import { profileAnalysisDtoMapper } from '~/mappers/offers-mappers';
|
|||||||
|
|
||||||
import { createRouter } from '../context';
|
import { createRouter } from '../context';
|
||||||
|
|
||||||
|
type Offer = OffersOffer & {
|
||||||
|
company: Company;
|
||||||
|
offersFullTime:
|
||||||
|
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
||||||
|
| null;
|
||||||
|
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
|
||||||
|
profile: OffersProfile & { background: OffersBackground | null };
|
||||||
|
};
|
||||||
|
|
||||||
const searchOfferPercentile = (
|
const searchOfferPercentile = (
|
||||||
offer: OffersOffer & {
|
offer: Offer,
|
||||||
company: Company;
|
|
||||||
offersFullTime:
|
|
||||||
| (OffersFullTime & {
|
|
||||||
baseSalary: OffersCurrency | null;
|
|
||||||
bonus: OffersCurrency | null;
|
|
||||||
stocks: OffersCurrency | null;
|
|
||||||
totalCompensation: OffersCurrency;
|
|
||||||
})
|
|
||||||
| null;
|
|
||||||
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
|
|
||||||
profile: OffersProfile & { background: OffersBackground | null };
|
|
||||||
},
|
|
||||||
similarOffers: Array<
|
similarOffers: Array<
|
||||||
OffersOffer & {
|
OffersOffer & {
|
||||||
company: Company;
|
company: Company;
|
||||||
@ -58,6 +55,70 @@ export const offersAnalysisRouter = createRouter()
|
|||||||
async resolve({ ctx, input }) {
|
async resolve({ ctx, input }) {
|
||||||
const analysis = await ctx.prisma.offersAnalysis.findFirst({
|
const analysis = await ctx.prisma.offersAnalysis.findFirst({
|
||||||
include: {
|
include: {
|
||||||
|
companyAnalysis: {
|
||||||
|
include: {
|
||||||
|
topSimilarOffers: {
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
offersFullTime: {
|
||||||
|
include: {
|
||||||
|
totalCompensation: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
offersIntern: {
|
||||||
|
include: {
|
||||||
|
monthlySalary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
include: {
|
||||||
|
background: {
|
||||||
|
include: {
|
||||||
|
experiences: {
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
overallAnalysis: {
|
||||||
|
include: {
|
||||||
|
topSimilarOffers: {
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
offersFullTime: {
|
||||||
|
include: {
|
||||||
|
totalCompensation: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
offersIntern: {
|
||||||
|
include: {
|
||||||
|
monthlySalary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
include: {
|
||||||
|
background: {
|
||||||
|
include: {
|
||||||
|
experiences: {
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
overallHighestOffer: {
|
overallHighestOffer: {
|
||||||
include: {
|
include: {
|
||||||
company: true,
|
company: true,
|
||||||
@ -78,62 +139,6 @@ export const offersAnalysisRouter = createRouter()
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
profileId: input.profileId,
|
profileId: input.profileId,
|
||||||
@ -310,11 +315,57 @@ export const offersAnalysisRouter = createRouter()
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let similarCompanyOffers = similarOffers.filter(
|
// COMPANY ANALYSIS
|
||||||
(offer) => offer.companyId === overallHighestOffer.companyId,
|
const companyMap = new Map<string, Offer>();
|
||||||
|
offers.forEach((offer) => {
|
||||||
|
if (companyMap.get(offer.companyId) == null) {
|
||||||
|
companyMap.set(offer.companyId, offer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const companyAnalysis = Array.from(companyMap.values()).map(
|
||||||
|
(companyOffer) => {
|
||||||
|
// TODO: Refactor calculating analysis into a function
|
||||||
|
let similarCompanyOffers = similarOffers.filter(
|
||||||
|
(offer) => offer.companyId === companyOffer.companyId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const companyIndex = searchOfferPercentile(
|
||||||
|
companyOffer,
|
||||||
|
similarCompanyOffers,
|
||||||
|
);
|
||||||
|
const companyPercentile =
|
||||||
|
similarCompanyOffers.length <= 1
|
||||||
|
? 100
|
||||||
|
: 100 - (100 * companyIndex) / (similarCompanyOffers.length - 1);
|
||||||
|
|
||||||
|
// Get top offers (excluding user's offer)
|
||||||
|
similarCompanyOffers = similarCompanyOffers.filter(
|
||||||
|
(offer) => offer.id !== companyOffer.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const noOfSimilarCompanyOffers = similarCompanyOffers.length;
|
||||||
|
const similarCompanyOffers90PercentileIndex = Math.ceil(
|
||||||
|
noOfSimilarCompanyOffers * 0.1,
|
||||||
|
);
|
||||||
|
const topPercentileCompanyOffers =
|
||||||
|
noOfSimilarCompanyOffers > 2
|
||||||
|
? similarCompanyOffers.slice(
|
||||||
|
similarCompanyOffers90PercentileIndex,
|
||||||
|
similarCompanyOffers90PercentileIndex + 2,
|
||||||
|
)
|
||||||
|
: similarCompanyOffers;
|
||||||
|
|
||||||
|
return {
|
||||||
|
companyName: companyOffer.company.name,
|
||||||
|
noOfSimilarOffers: noOfSimilarCompanyOffers,
|
||||||
|
percentile: companyPercentile,
|
||||||
|
topSimilarOffers: topPercentileCompanyOffers,
|
||||||
|
};
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// CALCULATE PERCENTILES
|
// OVERALL ANALYSIS
|
||||||
const overallIndex = searchOfferPercentile(
|
const overallIndex = searchOfferPercentile(
|
||||||
overallHighestOffer,
|
overallHighestOffer,
|
||||||
similarOffers,
|
similarOffers,
|
||||||
@ -324,23 +375,9 @@ export const offersAnalysisRouter = createRouter()
|
|||||||
? 100
|
? 100
|
||||||
: 100 - (100 * overallIndex) / (similarOffers.length - 1);
|
: 100 - (100 * overallIndex) / (similarOffers.length - 1);
|
||||||
|
|
||||||
const companyIndex = searchOfferPercentile(
|
|
||||||
overallHighestOffer,
|
|
||||||
similarCompanyOffers,
|
|
||||||
);
|
|
||||||
const companyPercentile =
|
|
||||||
similarCompanyOffers.length <= 1
|
|
||||||
? 100
|
|
||||||
: 100 - (100 * companyIndex) / (similarCompanyOffers.length - 1);
|
|
||||||
|
|
||||||
// FIND TOP >=90 PERCENTILE OFFERS, DOESN'T GIVE 100th PERCENTILE
|
|
||||||
// e.g. If there only 4 offers, it gives the 2nd and 3rd offer
|
|
||||||
similarOffers = similarOffers.filter(
|
similarOffers = similarOffers.filter(
|
||||||
(offer) => offer.id !== overallHighestOffer.id,
|
(offer) => offer.id !== overallHighestOffer.id,
|
||||||
);
|
);
|
||||||
similarCompanyOffers = similarCompanyOffers.filter(
|
|
||||||
(offer) => offer.id !== overallHighestOffer.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
const noOfSimilarOffers = similarOffers.length;
|
const noOfSimilarOffers = similarOffers.length;
|
||||||
const similarOffers90PercentileIndex = Math.ceil(noOfSimilarOffers * 0.1);
|
const similarOffers90PercentileIndex = Math.ceil(noOfSimilarOffers * 0.1);
|
||||||
@ -352,46 +389,110 @@ export const offersAnalysisRouter = createRouter()
|
|||||||
)
|
)
|
||||||
: similarOffers;
|
: 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({
|
const analysis = await ctx.prisma.offersAnalysis.create({
|
||||||
data: {
|
data: {
|
||||||
companyPercentile,
|
companyAnalysis: {
|
||||||
noOfSimilarCompanyOffers,
|
create: companyAnalysis.map((analysisUnit) => {
|
||||||
noOfSimilarOffers,
|
return {
|
||||||
|
companyName: analysisUnit.companyName,
|
||||||
|
noOfSimilarOffers: analysisUnit.noOfSimilarOffers,
|
||||||
|
percentile: analysisUnit.percentile,
|
||||||
|
topSimilarOffers: {
|
||||||
|
connect: analysisUnit.topSimilarOffers.map((offer) => {
|
||||||
|
return { id: offer.id };
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
overallAnalysis: {
|
||||||
|
create: {
|
||||||
|
companyName: overallHighestOffer.company.name,
|
||||||
|
noOfSimilarOffers,
|
||||||
|
percentile: overallPercentile,
|
||||||
|
topSimilarOffers: {
|
||||||
|
connect: topPercentileOffers.map((offer) => {
|
||||||
|
return { id: offer.id };
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
overallHighestOffer: {
|
overallHighestOffer: {
|
||||||
connect: {
|
connect: {
|
||||||
id: overallHighestOffer.id,
|
id: overallHighestOffer.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
overallPercentile,
|
|
||||||
profile: {
|
profile: {
|
||||||
connect: {
|
connect: {
|
||||||
id: input.profileId,
|
id: input.profileId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
topCompanyOffers: {
|
|
||||||
connect: topPercentileCompanyOffers.map((offer) => {
|
|
||||||
return { id: offer.id };
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
topOverallOffers: {
|
|
||||||
connect: topPercentileOffers.map((offer) => {
|
|
||||||
return { id: offer.id };
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
|
companyAnalysis: {
|
||||||
|
include: {
|
||||||
|
topSimilarOffers: {
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
offersFullTime: {
|
||||||
|
include: {
|
||||||
|
totalCompensation: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
offersIntern: {
|
||||||
|
include: {
|
||||||
|
monthlySalary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
include: {
|
||||||
|
background: {
|
||||||
|
include: {
|
||||||
|
experiences: {
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
overallAnalysis: {
|
||||||
|
include: {
|
||||||
|
topSimilarOffers: {
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
offersFullTime: {
|
||||||
|
include: {
|
||||||
|
totalCompensation: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
offersIntern: {
|
||||||
|
include: {
|
||||||
|
monthlySalary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
include: {
|
||||||
|
background: {
|
||||||
|
include: {
|
||||||
|
experiences: {
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
overallHighestOffer: {
|
overallHighestOffer: {
|
||||||
include: {
|
include: {
|
||||||
company: true,
|
company: true,
|
||||||
@ -412,62 +513,6 @@ export const offersAnalysisRouter = createRouter()
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -128,6 +128,70 @@ export const offersProfileRouter = createRouter()
|
|||||||
include: {
|
include: {
|
||||||
analysis: {
|
analysis: {
|
||||||
include: {
|
include: {
|
||||||
|
companyAnalysis: {
|
||||||
|
include: {
|
||||||
|
topSimilarOffers: {
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
offersFullTime: {
|
||||||
|
include: {
|
||||||
|
totalCompensation: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
offersIntern: {
|
||||||
|
include: {
|
||||||
|
monthlySalary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
include: {
|
||||||
|
background: {
|
||||||
|
include: {
|
||||||
|
experiences: {
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
overallAnalysis: {
|
||||||
|
include: {
|
||||||
|
topSimilarOffers: {
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
offersFullTime: {
|
||||||
|
include: {
|
||||||
|
totalCompensation: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
offersIntern: {
|
||||||
|
include: {
|
||||||
|
monthlySalary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
include: {
|
||||||
|
background: {
|
||||||
|
include: {
|
||||||
|
experiences: {
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
overallHighestOffer: {
|
overallHighestOffer: {
|
||||||
include: {
|
include: {
|
||||||
company: true,
|
company: true,
|
||||||
@ -148,62 +212,6 @@ export const offersProfileRouter = createRouter()
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
background: {
|
background: {
|
||||||
@ -244,7 +252,7 @@ export const offersProfileRouter = createRouter()
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
user: true,
|
users: true,
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
id: input.profileId,
|
id: input.profileId,
|
||||||
@ -409,7 +417,7 @@ export const offersProfileRouter = createRouter()
|
|||||||
message: 'Missing fields in background experiences.',
|
message: 'Missing fields in background experiences.',
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
specificYoes: {
|
specificYoes: {
|
||||||
create: input.background.specificYoes.map((x) => {
|
create: input.background.specificYoes.map((x) => {
|
||||||
|
@ -3,129 +3,130 @@ import * as trpc from '@trpc/server';
|
|||||||
import { TRPCError } from '@trpc/server';
|
import { TRPCError } from '@trpc/server';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addToProfileResponseMapper, getUserProfileResponseMapper,
|
addToProfileResponseMapper,
|
||||||
|
getUserProfileResponseMapper,
|
||||||
} from '~/mappers/offers-mappers';
|
} from '~/mappers/offers-mappers';
|
||||||
|
|
||||||
import { createProtectedRouter } from '../context';
|
import { createProtectedRouter } from '../context';
|
||||||
|
|
||||||
export const offersUserProfileRouter = createProtectedRouter()
|
export const offersUserProfileRouter = createProtectedRouter()
|
||||||
.mutation('addToUserProfile', {
|
.mutation('addToUserProfile', {
|
||||||
input: z.object({
|
input: z.object({
|
||||||
profileId: z.string(),
|
profileId: z.string(),
|
||||||
token: z.string(),
|
token: z.string(),
|
||||||
}),
|
}),
|
||||||
async resolve({ ctx, input }) {
|
async resolve({ ctx, input }) {
|
||||||
const profile = await ctx.prisma.offersProfile.findFirst({
|
const profile = await ctx.prisma.offersProfile.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: input.profileId,
|
id: input.profileId,
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const profileEditToken = profile?.editToken;
|
|
||||||
if (profileEditToken === input.token) {
|
|
||||||
|
|
||||||
const userId = ctx.session.user.id
|
|
||||||
const updated = await ctx.prisma.offersProfile.update({
|
|
||||||
data: {
|
|
||||||
user: {
|
|
||||||
connect: {
|
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
id: input.profileId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return addToProfileResponseMapper(updated);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new trpc.TRPCError({
|
|
||||||
code: 'UNAUTHORIZED',
|
|
||||||
message: 'Invalid token.',
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
.query('getUserProfiles', {
|
|
||||||
async resolve({ ctx }) {
|
const profileEditToken = profile?.editToken;
|
||||||
const userId = ctx.session.user.id
|
if (profileEditToken === input.token) {
|
||||||
const result = await ctx.prisma.user.findFirst({
|
const userId = ctx.session.user.id;
|
||||||
|
const updated = await ctx.prisma.offersProfile.update({
|
||||||
|
data: {
|
||||||
|
users: {
|
||||||
|
connect: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
id: input.profileId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return addToProfileResponseMapper(updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new trpc.TRPCError({
|
||||||
|
code: 'UNAUTHORIZED',
|
||||||
|
message: 'Invalid token.',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.query('getUserProfiles', {
|
||||||
|
async resolve({ ctx }) {
|
||||||
|
const userId = ctx.session.user.id;
|
||||||
|
const result = await ctx.prisma.user.findFirst({
|
||||||
|
include: {
|
||||||
|
OffersProfile: {
|
||||||
|
include: {
|
||||||
|
offers: {
|
||||||
include: {
|
include: {
|
||||||
OffersProfile: {
|
company: true,
|
||||||
include: {
|
offersFullTime: {
|
||||||
offers: {
|
include: {
|
||||||
include: {
|
totalCompensation: true,
|
||||||
company: true,
|
},
|
||||||
offersFullTime: {
|
},
|
||||||
include: {
|
offersIntern: {
|
||||||
totalCompensation: true
|
include: {
|
||||||
}
|
monthlySalary: true,
|
||||||
},
|
},
|
||||||
offersIntern: {
|
},
|
||||||
include: {
|
|
||||||
monthlySalary: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
where: {
|
},
|
||||||
id: userId
|
},
|
||||||
}
|
},
|
||||||
})
|
},
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return getUserProfileResponseMapper(result)
|
return getUserProfileResponseMapper(result);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.mutation('removeFromUserProfile', {
|
||||||
|
input: z.object({
|
||||||
|
profileId: z.string(),
|
||||||
|
}),
|
||||||
|
async resolve({ ctx, input }) {
|
||||||
|
const userId = ctx.session.user.id;
|
||||||
|
|
||||||
|
const profiles = await ctx.prisma.user.findFirst({
|
||||||
|
include: {
|
||||||
|
OffersProfile: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
let doesProfileExist = false;
|
||||||
|
|
||||||
|
if (profiles?.OffersProfile) {
|
||||||
|
for (let i = 0; i < profiles.OffersProfile.length; i++) {
|
||||||
|
if (profiles.OffersProfile[i].id === input.profileId) {
|
||||||
|
doesProfileExist = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.mutation('removeFromUserProfile', {
|
|
||||||
input: z.object({
|
|
||||||
profileId: z.string(),
|
|
||||||
}),
|
|
||||||
async resolve({ ctx, input }) {
|
|
||||||
const userId = ctx.session.user.id
|
|
||||||
|
|
||||||
const profiles = await ctx.prisma.user.findFirst({
|
if (!doesProfileExist) {
|
||||||
include: {
|
throw new TRPCError({
|
||||||
OffersProfile: true
|
code: 'NOT_FOUND',
|
||||||
},
|
message: 'No such profile id saved.',
|
||||||
where: {
|
});
|
||||||
id: userId
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Validation
|
await ctx.prisma.user.update({
|
||||||
let doesProfileExist = false;
|
data: {
|
||||||
|
OffersProfile: {
|
||||||
if (profiles?.OffersProfile) {
|
disconnect: [
|
||||||
for (let i = 0; i < profiles.OffersProfile.length; i++) {
|
{
|
||||||
if (profiles.OffersProfile[i].id === input.profileId) {
|
id: input.profileId,
|
||||||
doesProfileExist = true
|
},
|
||||||
}
|
],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
where: {
|
||||||
if (!doesProfileExist) {
|
id: userId,
|
||||||
throw new TRPCError({
|
},
|
||||||
code: 'NOT_FOUND',
|
});
|
||||||
message: 'No such profile id saved.'
|
},
|
||||||
})
|
});
|
||||||
}
|
|
||||||
|
|
||||||
await ctx.prisma.user.update({
|
|
||||||
data: {
|
|
||||||
OffersProfile: {
|
|
||||||
disconnect: [{
|
|
||||||
id: input.profileId
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
id: userId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
9
apps/portal/src/types/offers.d.ts
vendored
9
apps/portal/src/types/offers.d.ts
vendored
@ -143,14 +143,17 @@ export type OffersDiscussion = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ProfileAnalysis = {
|
export type ProfileAnalysis = {
|
||||||
companyAnalysis: Array<Analysis>;
|
companyAnalysis: Array<AnalysisUnit>;
|
||||||
|
createdAt: Date;
|
||||||
id: string;
|
id: string;
|
||||||
overallAnalysis: Analysis;
|
overallAnalysis: AnalysisUnit;
|
||||||
overallHighestOffer: AnalysisHighestOffer;
|
overallHighestOffer: AnalysisHighestOffer;
|
||||||
profileId: string;
|
profileId: string;
|
||||||
|
updatedAt: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Analysis = {
|
export type AnalysisUnit = {
|
||||||
|
companyName: string;
|
||||||
noOfOffers: number;
|
noOfOffers: number;
|
||||||
percentile: number;
|
percentile: number;
|
||||||
topPercentileOffers: Array<AnalysisOffer>;
|
topPercentileOffers: Array<AnalysisOffer>;
|
||||||
|
Reference in New Issue
Block a user