mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-29 05:02:52 +08:00
[offers][feat] integrate profile API and offer API (#360)
This commit is contained in:
@ -1,16 +1,22 @@
|
|||||||
import { useState } from 'react';
|
import { useRouter } from 'next/router';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
import { HorizontalDivider, Pagination, Select, Tabs } from '@tih/ui';
|
import { HorizontalDivider, Pagination, Select, Tabs } from '@tih/ui';
|
||||||
|
|
||||||
import CurrencySelector from '~/utils/offers/currency/CurrencySelector';
|
import CurrencySelector from '~/utils/offers/currency/CurrencySelector';
|
||||||
|
import { formatDate } from '~/utils/offers/time';
|
||||||
|
import { trpc } from '~/utils/trpc';
|
||||||
|
|
||||||
type TableRow = {
|
type OfferTableRow = {
|
||||||
company: string;
|
company: string;
|
||||||
date: string;
|
date: string;
|
||||||
salary: string;
|
id: string;
|
||||||
|
profileId: string;
|
||||||
|
salary: number | undefined;
|
||||||
title: string;
|
title: string;
|
||||||
yoe: string;
|
yoe: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// To be changed to backend enum
|
||||||
// eslint-disable-next-line no-shadow
|
// eslint-disable-next-line no-shadow
|
||||||
enum YOE_CATEGORY {
|
enum YOE_CATEGORY {
|
||||||
INTERN = 0,
|
INTERN = 0,
|
||||||
@ -19,10 +25,81 @@ enum YOE_CATEGORY {
|
|||||||
SENIOR = 3,
|
SENIOR = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function OffersTable() {
|
type OffersTableProps = {
|
||||||
const [currency, setCurrency] = useState('SGD');
|
companyFilter: string;
|
||||||
|
jobTitleFilter: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Pagination = {
|
||||||
|
currentPage: number;
|
||||||
|
numOfItems: number;
|
||||||
|
numOfPages: number;
|
||||||
|
totalItems: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NUMBER_OF_OFFERS_IN_PAGE = 10;
|
||||||
|
|
||||||
|
export default function OffersTable({ jobTitleFilter }: OffersTableProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const [currency, setCurrency] = useState('SGD'); // TODO
|
||||||
const [selectedTab, setSelectedTab] = useState(YOE_CATEGORY.ENTRY);
|
const [selectedTab, setSelectedTab] = useState(YOE_CATEGORY.ENTRY);
|
||||||
const [selectedPage, setSelectedPage] = useState(1);
|
const [pagination, setPagination] = useState<Pagination>({
|
||||||
|
currentPage: 1,
|
||||||
|
numOfItems: 1,
|
||||||
|
numOfPages: 0,
|
||||||
|
totalItems: 0,
|
||||||
|
});
|
||||||
|
const [offers, setOffers] = useState<Array<OfferTableRow>>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPagination({
|
||||||
|
currentPage: 1,
|
||||||
|
numOfItems: 1,
|
||||||
|
numOfPages: 0,
|
||||||
|
totalItems: 0,
|
||||||
|
});
|
||||||
|
}, [selectedTab]);
|
||||||
|
trpc.useQuery(
|
||||||
|
[
|
||||||
|
'offers.list',
|
||||||
|
{
|
||||||
|
// Company: companyFilter, // TODO
|
||||||
|
limit: NUMBER_OF_OFFERS_IN_PAGE,
|
||||||
|
|
||||||
|
location: 'Singapore, Singapore',
|
||||||
|
offset: pagination.currentPage - 1,
|
||||||
|
sortBy: '-monthYearReceived',
|
||||||
|
title: jobTitleFilter,
|
||||||
|
yoeCategory: selectedTab,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
onSuccess: (response) => {
|
||||||
|
const filteredData = response.data.map((res) => {
|
||||||
|
return {
|
||||||
|
company: res.company.name,
|
||||||
|
date: formatDate(res.monthYearReceived),
|
||||||
|
id: res.OffersFullTime
|
||||||
|
? res.OffersFullTime!.id
|
||||||
|
: res.OffersIntern!.id,
|
||||||
|
profileId: res.profileId,
|
||||||
|
salary: res.OffersFullTime
|
||||||
|
? res.OffersFullTime?.totalCompensation.value
|
||||||
|
: res.OffersIntern?.monthlySalary.value,
|
||||||
|
title: res.OffersFullTime ? res.OffersFullTime?.level : '',
|
||||||
|
yoe: 100,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setOffers(filteredData);
|
||||||
|
setPagination({
|
||||||
|
currentPage: (response.paging.currPage as number) + 1,
|
||||||
|
numOfItems: response.paging.numOfItemsInPage,
|
||||||
|
numOfPages: response.paging.numOfPages,
|
||||||
|
totalItems: response.paging.totalNumberOfOffers,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
function renderTabs() {
|
function renderTabs() {
|
||||||
return (
|
return (
|
||||||
@ -103,9 +180,27 @@ export default function OffersTable() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderRow({ company, title, yoe, salary, date }: TableRow) {
|
const handleClickViewProfile = (profileId: string) => {
|
||||||
|
router.push(`/offers/profile/${profileId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = (currPage: number) => {
|
||||||
|
setPagination({ ...pagination, currentPage: currPage });
|
||||||
|
};
|
||||||
|
|
||||||
|
function renderRow({
|
||||||
|
company,
|
||||||
|
title,
|
||||||
|
yoe,
|
||||||
|
salary,
|
||||||
|
date,
|
||||||
|
profileId,
|
||||||
|
id,
|
||||||
|
}: OfferTableRow) {
|
||||||
return (
|
return (
|
||||||
<tr className="border-b bg-white hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-600">
|
<tr
|
||||||
|
key={id}
|
||||||
|
className="border-b bg-white hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-600">
|
||||||
<th
|
<th
|
||||||
className="whitespace-nowrap py-4 px-6 font-medium text-gray-900 dark:text-white"
|
className="whitespace-nowrap py-4 px-6 font-medium text-gray-900 dark:text-white"
|
||||||
scope="row">
|
scope="row">
|
||||||
@ -118,14 +213,14 @@ export default function OffersTable() {
|
|||||||
<td className="space-x-4 py-4 px-6">
|
<td className="space-x-4 py-4 px-6">
|
||||||
<a
|
<a
|
||||||
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500"
|
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500"
|
||||||
href="#">
|
onClick={() => handleClickViewProfile(profileId)}>
|
||||||
View Profile
|
View Profile
|
||||||
</a>
|
</a>
|
||||||
<a
|
{/* <a
|
||||||
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500"
|
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500"
|
||||||
href="#">
|
href="#">
|
||||||
Comment
|
Comment
|
||||||
</a>
|
</a> */}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
@ -137,22 +232,30 @@ export default function OffersTable() {
|
|||||||
aria-label="Table navigation"
|
aria-label="Table navigation"
|
||||||
className="flex items-center justify-between p-4">
|
className="flex items-center justify-between p-4">
|
||||||
<span className="text-sm font-normal text-gray-500 dark:text-gray-400">
|
<span className="text-sm font-normal text-gray-500 dark:text-gray-400">
|
||||||
Showing{' '}
|
Showing
|
||||||
<span className="font-semibold text-gray-900 dark:text-white">
|
<span className="font-semibold text-gray-900 dark:text-white">
|
||||||
1-10
|
{` ${
|
||||||
</span>{' '}
|
(pagination.currentPage - 1) * NUMBER_OF_OFFERS_IN_PAGE + 1
|
||||||
of{' '}
|
} - ${
|
||||||
|
(pagination.currentPage - 1) * NUMBER_OF_OFFERS_IN_PAGE +
|
||||||
|
offers.length
|
||||||
|
} `}
|
||||||
|
</span>
|
||||||
|
{`of `}
|
||||||
<span className="font-semibold text-gray-900 dark:text-white">
|
<span className="font-semibold text-gray-900 dark:text-white">
|
||||||
1000
|
{pagination.totalItems}
|
||||||
|
{/* {pagination.numOfPages * NUMBER_OF_OFFERS_IN_PAGE} */}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<Pagination
|
<Pagination
|
||||||
current={selectedPage}
|
current={pagination.currentPage}
|
||||||
end={10}
|
end={pagination.numOfPages}
|
||||||
label="Pagination"
|
label="Pagination"
|
||||||
pagePadding={1}
|
pagePadding={1}
|
||||||
start={1}
|
start={1}
|
||||||
onSelect={(page) => setSelectedPage(page)}
|
onSelect={(currPage) => {
|
||||||
|
handlePageChange(currPage);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
@ -167,20 +270,7 @@ export default function OffersTable() {
|
|||||||
<table className="w-full text-left text-sm text-gray-500 dark:text-gray-400">
|
<table className="w-full text-left text-sm text-gray-500 dark:text-gray-400">
|
||||||
{renderHeader()}
|
{renderHeader()}
|
||||||
<tbody>
|
<tbody>
|
||||||
{renderRow({
|
{offers.map((offer: OfferTableRow) => renderRow(offer))}
|
||||||
company: 'Shopee',
|
|
||||||
date: 'May 2022',
|
|
||||||
salary: 'TC/yr',
|
|
||||||
title: 'SWE',
|
|
||||||
yoe: '5',
|
|
||||||
})}
|
|
||||||
{renderRow({
|
|
||||||
company: 'Shopee',
|
|
||||||
date: 'May 2022',
|
|
||||||
salary: 'TC/yr',
|
|
||||||
title: 'SWE',
|
|
||||||
yoe: '5',
|
|
||||||
})}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{renderPagination()}
|
{renderPagination()}
|
||||||
|
@ -3,14 +3,14 @@ import {
|
|||||||
LightBulbIcon,
|
LightBulbIcon,
|
||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
import type { EducationBackgroundType } from '../types';
|
import type { EducationBackgroundType } from '~/components/offers/types';
|
||||||
|
|
||||||
type EducationEntity = {
|
type EducationEntity = {
|
||||||
backgroundType?: EducationBackgroundType;
|
endDate?: string;
|
||||||
field?: string;
|
field?: string;
|
||||||
fromMonth?: string;
|
|
||||||
school?: string;
|
school?: string;
|
||||||
toMonth?: string;
|
startDate?: string;
|
||||||
|
type?: EducationBackgroundType;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = Readonly<{
|
type Props = Readonly<{
|
||||||
@ -18,7 +18,7 @@ type Props = Readonly<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
export default function EducationCard({
|
export default function EducationCard({
|
||||||
education: { backgroundType, field, fromMonth, school, toMonth },
|
education: { type, field, startDate, endDate, school },
|
||||||
}: Props) {
|
}: Props) {
|
||||||
return (
|
return (
|
||||||
<div className="mx-8 my-4 block rounded-lg bg-white py-4 shadow-md">
|
<div className="mx-8 my-4 block rounded-lg bg-white py-4 shadow-md">
|
||||||
@ -27,9 +27,7 @@ export default function EducationCard({
|
|||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<LightBulbIcon className="mr-1 h-5" />
|
<LightBulbIcon className="mr-1 h-5" />
|
||||||
<span className="ml-1 font-bold">
|
<span className="ml-1 font-bold">
|
||||||
{field
|
{field ? `${type ?? 'N/A'}, ${field}` : type ?? `N/A`}
|
||||||
? `${backgroundType ?? 'N/A'}, ${field}`
|
|
||||||
: backgroundType ?? `N/A`}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{school && (
|
{school && (
|
||||||
@ -39,9 +37,11 @@ export default function EducationCard({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{(fromMonth || toMonth) && (
|
{(startDate || endDate) && (
|
||||||
<div className="font-light text-gray-400">
|
<div className="font-light text-gray-400">
|
||||||
<p>{`${fromMonth ?? 'N/A'} - ${toMonth ?? 'N/A'}`}</p>
|
<p>{`${startDate ? startDate : 'N/A'} - ${
|
||||||
|
endDate ? endDate : 'N/A'
|
||||||
|
}`}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,18 +6,19 @@ import {
|
|||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import { HorizontalDivider } from '@tih/ui';
|
import { HorizontalDivider } from '@tih/ui';
|
||||||
|
|
||||||
type OfferEntity = {
|
export type OfferEntity = {
|
||||||
base?: string;
|
base?: string;
|
||||||
bonus?: string;
|
bonus?: string;
|
||||||
companyName: string;
|
companyName: string;
|
||||||
duration?: string; // For background
|
duration?: string;
|
||||||
|
id?: string;
|
||||||
jobLevel?: string;
|
jobLevel?: string;
|
||||||
jobTitle: string;
|
jobTitle: string;
|
||||||
location: string;
|
location?: string;
|
||||||
monthlySalary?: string;
|
monthlySalary?: string;
|
||||||
negotiationStrategy?: string;
|
negotiationStrategy?: string;
|
||||||
otherComment?: string;
|
otherComment?: string;
|
||||||
receivedMonth: string;
|
receivedMonth?: string;
|
||||||
stocks?: string;
|
stocks?: string;
|
||||||
totalCompensation?: string;
|
totalCompensation?: string;
|
||||||
};
|
};
|
||||||
@ -57,14 +58,14 @@ export default function OfferCard({
|
|||||||
<p>{jobLevel ? `${jobTitle}, ${jobLevel}` : jobTitle}</p>
|
<p>{jobLevel ? `${jobTitle}, ${jobLevel}` : jobTitle}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{receivedMonth && (
|
{!duration && receivedMonth && (
|
||||||
<div className="font-light text-gray-400">
|
<div className="font-light text-gray-400">
|
||||||
<p>{receivedMonth}</p>
|
<p>{receivedMonth}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{duration && (
|
{duration && (
|
||||||
<div className="font-light text-gray-400">
|
<div className="font-light text-gray-400">
|
||||||
<p>{duration}</p>
|
<p>{`${duration} months`}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable no-shadow */
|
/* eslint-disable no-shadow */
|
||||||
import type { MonthYear } from '../shared/MonthYearPicker';
|
import type { OfferEntity } from '~/components/offers/profile/OfferCard';
|
||||||
|
import type { MonthYear } from '~/components/shared/MonthYearPicker';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Offer Profile
|
* Offer Profile
|
||||||
@ -82,7 +83,7 @@ type GeneralExperience = {
|
|||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Experience =
|
export type Experience =
|
||||||
| (FullTimeExperience & GeneralExperience)
|
| (FullTimeExperience & GeneralExperience)
|
||||||
| (GeneralExperience & InternshipExperience);
|
| (GeneralExperience & InternshipExperience);
|
||||||
|
|
||||||
@ -110,3 +111,19 @@ export type OfferPostData = {
|
|||||||
background: BackgroundFormData;
|
background: BackgroundFormData;
|
||||||
offers: Array<OfferDetailsPostData>;
|
offers: Array<OfferDetailsPostData>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type EducationDisplay = {
|
||||||
|
endDate?: string;
|
||||||
|
field: string;
|
||||||
|
school: string;
|
||||||
|
startDate?: string;
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BackgroundCard = {
|
||||||
|
educations: Array<EducationDisplay>;
|
||||||
|
experiences: Array<OfferEntity>;
|
||||||
|
profileName: string;
|
||||||
|
specificYoes: Array<SpecificYoe>;
|
||||||
|
totalYoe: string;
|
||||||
|
};
|
||||||
|
@ -6,8 +6,7 @@ import OffersTitle from '~/components/offers/OffersTitle';
|
|||||||
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
|
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
|
||||||
|
|
||||||
export default function OffersHomePage() {
|
export default function OffersHomePage() {
|
||||||
const [jobTitleFilter, setjobTitleFilter] = useState('Software engineers');
|
const [jobTitleFilter, setjobTitleFilter] = useState('Software Engineer');
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const [companyFilter, setCompanyFilter] = useState('All companies');
|
const [companyFilter, setCompanyFilter] = useState('All companies');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -23,20 +22,20 @@ export default function OffersHomePage() {
|
|||||||
label="Select a job title"
|
label="Select a job title"
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
label: 'Software engineers',
|
label: 'Software Engineer',
|
||||||
value: 'Software engineers',
|
value: 'Software Engineer',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Frontend engineers',
|
label: 'Frontend Engineer',
|
||||||
value: 'Frontend engineers',
|
value: 'Frontend Engineer',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Backend engineers',
|
label: 'Backend Engineer',
|
||||||
value: 'Backend engineers',
|
value: 'Backend Engineer',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Full-stack engineers',
|
label: 'Full-stack Engineer',
|
||||||
value: 'Full-stack engineers',
|
value: 'Full-stack Engineer',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
value={jobTitleFilter}
|
value={jobTitleFilter}
|
||||||
@ -53,7 +52,10 @@ export default function OffersHomePage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center bg-white pb-20 pt-10">
|
<div className="flex justify-center bg-white pb-20 pt-10">
|
||||||
<OffersTable />
|
<OffersTable
|
||||||
|
companyFilter={companyFilter}
|
||||||
|
jobTitleFilter={jobTitleFilter}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import Error from 'next/error';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
AcademicCapIcon,
|
AcademicCapIcon,
|
||||||
@ -13,13 +15,129 @@ import {
|
|||||||
import { Button, Dialog, Tabs } from '@tih/ui';
|
import { Button, Dialog, Tabs } from '@tih/ui';
|
||||||
|
|
||||||
import EducationCard from '~/components/offers/profile/EducationCard';
|
import EducationCard from '~/components/offers/profile/EducationCard';
|
||||||
|
import type { OfferEntity } from '~/components/offers/profile/OfferCard';
|
||||||
import OfferCard from '~/components/offers/profile/OfferCard';
|
import OfferCard from '~/components/offers/profile/OfferCard';
|
||||||
import ProfilePhotoHolder from '~/components/offers/profile/ProfilePhotoHolder';
|
import ProfilePhotoHolder from '~/components/offers/profile/ProfilePhotoHolder';
|
||||||
|
import type { BackgroundCard } from '~/components/offers/types';
|
||||||
import { EducationBackgroundType } from '~/components/offers/types';
|
import { EducationBackgroundType } from '~/components/offers/types';
|
||||||
|
|
||||||
|
import { formatDate } from '~/utils/offers/time';
|
||||||
|
import { trpc } from '~/utils/trpc';
|
||||||
export default function OfferProfile() {
|
export default function OfferProfile() {
|
||||||
|
const ErrorPage = (
|
||||||
|
<Error statusCode={404} title="Requested profile does not exist." />
|
||||||
|
);
|
||||||
|
const router = useRouter();
|
||||||
|
const { offerProfileId, token = '' } = router.query;
|
||||||
|
const [isEditable, setIsEditable] = useState(false);
|
||||||
|
const [background, setBackground] = useState<BackgroundCard>();
|
||||||
|
const [offers, setOffers] = useState<Array<OfferEntity>>([]);
|
||||||
const [selectedTab, setSelectedTab] = useState('offers');
|
const [selectedTab, setSelectedTab] = useState('offers');
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
const detailsQuery = trpc.useQuery(
|
||||||
|
[
|
||||||
|
'offers.profile.listOne',
|
||||||
|
{ profileId: offerProfileId as string, token: token as string },
|
||||||
|
],
|
||||||
|
{
|
||||||
|
enabled: typeof offerProfileId === 'string',
|
||||||
|
onSuccess: (data) => {
|
||||||
|
const filteredOffers: Array<OfferEntity> = data!.offers.map((res) => {
|
||||||
|
if (res.OffersFullTime) {
|
||||||
|
const filteredOffer: OfferEntity = {
|
||||||
|
base: res.OffersFullTime.baseSalary.value
|
||||||
|
? `${res.OffersFullTime.baseSalary.value} ${res.OffersFullTime.baseSalary.currency}`
|
||||||
|
: '',
|
||||||
|
bonus: res.OffersFullTime.bonus.value
|
||||||
|
? `${res.OffersFullTime.bonus.value} ${res.OffersFullTime.bonus.currency}`
|
||||||
|
: '',
|
||||||
|
companyName: res.company.name,
|
||||||
|
id: res.OffersFullTime.id,
|
||||||
|
jobLevel: res.OffersFullTime.level,
|
||||||
|
jobTitle: res.OffersFullTime.title,
|
||||||
|
location: res.location,
|
||||||
|
negotiationStrategy: res.negotiationStrategy || '',
|
||||||
|
otherComment: res.comments || '',
|
||||||
|
receivedMonth: formatDate(res.monthYearReceived),
|
||||||
|
stocks: res.OffersFullTime.stocks.value
|
||||||
|
? `${res.OffersFullTime.stocks.value} ${res.OffersFullTime.stocks.currency}`
|
||||||
|
: '',
|
||||||
|
totalCompensation: res.OffersFullTime.totalCompensation.value
|
||||||
|
? `${res.OffersFullTime.totalCompensation.value} ${res.OffersFullTime.totalCompensation.currency}`
|
||||||
|
: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
return filteredOffer;
|
||||||
|
}
|
||||||
|
const filteredOffer: OfferEntity = {
|
||||||
|
companyName: res.company.name,
|
||||||
|
id: res.OffersIntern!.id,
|
||||||
|
jobTitle: res.OffersIntern!.title,
|
||||||
|
location: res.location,
|
||||||
|
monthlySalary: res.OffersIntern!.monthlySalary.value
|
||||||
|
? `${res.OffersIntern!.monthlySalary.value} ${
|
||||||
|
res.OffersIntern!.monthlySalary.currency
|
||||||
|
}`
|
||||||
|
: '',
|
||||||
|
negotiationStrategy: res.negotiationStrategy || '',
|
||||||
|
otherComment: res.comments || '',
|
||||||
|
receivedMonth: formatDate(res.monthYearReceived),
|
||||||
|
};
|
||||||
|
return filteredOffer;
|
||||||
|
});
|
||||||
|
|
||||||
|
setOffers(filteredOffers ?? []);
|
||||||
|
|
||||||
|
if (data?.background) {
|
||||||
|
const filteredBackground: BackgroundCard = {
|
||||||
|
educations: [
|
||||||
|
{
|
||||||
|
endDate: data?.background.educations[0].endDate
|
||||||
|
? formatDate(data.background.educations[0].endDate)
|
||||||
|
: '-',
|
||||||
|
field: data.background.educations[0].field || '-',
|
||||||
|
school: data.background.educations[0].school || '-',
|
||||||
|
startDate: data.background.educations[0].startDate
|
||||||
|
? formatDate(data.background.educations[0].startDate)
|
||||||
|
: '-',
|
||||||
|
type: data.background.educations[0].type || '-',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
experiences: [
|
||||||
|
{
|
||||||
|
companyName:
|
||||||
|
data.background.experiences[0].company?.name ?? '-',
|
||||||
|
duration:
|
||||||
|
String(data.background.experiences[0].durationInMonths) ??
|
||||||
|
'-',
|
||||||
|
jobLevel: data.background.experiences[0].level ?? '',
|
||||||
|
jobTitle: data.background.experiences[0].title ?? '-',
|
||||||
|
monthlySalary: data.background.experiences[0].monthlySalary
|
||||||
|
?.value
|
||||||
|
? `${data.background.experiences[0].monthlySalary?.value} ${data.background.experiences[0].monthlySalary?.currency}`
|
||||||
|
: `-`,
|
||||||
|
totalCompensation: data.background.experiences[0]
|
||||||
|
.totalCompensation?.value
|
||||||
|
? `${data.background.experiences[0].totalCompensation?.value} ${data.background.experiences[0].totalCompensation?.currency}`
|
||||||
|
: ``,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
profileName: data.profileName,
|
||||||
|
specificYoes: data.background.specificYoes ?? [],
|
||||||
|
|
||||||
|
totalYoe: String(data.background.totalYoe) || '-',
|
||||||
|
};
|
||||||
|
|
||||||
|
setBackground(filteredBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsEditable(data?.isEditable ?? false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
function renderActionList() {
|
function renderActionList() {
|
||||||
return (
|
return (
|
||||||
<div className="space-x-2">
|
<div className="space-x-2">
|
||||||
@ -67,8 +185,8 @@ export default function OfferProfile() {
|
|||||||
title="Are you sure you want to delete this offer profile?"
|
title="Are you sure you want to delete this offer profile?"
|
||||||
onClose={() => setIsDialogOpen(false)}>
|
onClose={() => setIsDialogOpen(false)}>
|
||||||
<div>
|
<div>
|
||||||
All comments will gone. You will not be able to access or recover
|
All comments will be gone. You will not be able to access or
|
||||||
it.
|
recover it.
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
@ -84,20 +202,36 @@ export default function OfferProfile() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="justify-left flex ">
|
<div className="justify-left flex ">
|
||||||
<h2 className="flex w-4/5 text-2xl font-bold">anonymised-name</h2>
|
<h2 className="flex w-4/5 text-2xl font-bold">
|
||||||
<div className="flex h-8 w-1/5 justify-end">
|
{background?.profileName ?? 'anonymous'}
|
||||||
{renderActionList()}
|
</h2>
|
||||||
</div>
|
{isEditable && (
|
||||||
|
<div className="flex h-8 w-1/5 justify-end">
|
||||||
|
{isEditable && renderActionList()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<BuildingOffice2Icon className="mr-2.5 h-5" />
|
<BuildingOffice2Icon className="mr-2.5 h-5" />
|
||||||
<span className="mr-2 font-bold">Current:</span>
|
<span className="mr-2 font-bold">Current:</span>
|
||||||
<span>Level 4 Google</span>
|
<span>{`${background?.experiences[0].companyName ?? '-'} ${
|
||||||
|
background?.experiences[0].jobLevel
|
||||||
|
} ${background?.experiences[0].jobTitle}`}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<CalendarDaysIcon className="mr-2.5 h-5" />
|
<CalendarDaysIcon className="mr-2.5 h-5" />
|
||||||
<span className="mr-2 font-bold">YOE:</span>
|
<span className="mr-2 font-bold">YOE:</span>
|
||||||
<span>4</span>
|
<span className="mr-4">{background?.totalYoe}</span>
|
||||||
|
{background?.specificYoes &&
|
||||||
|
background?.specificYoes.length > 0 &&
|
||||||
|
background?.specificYoes.map(({ domain, yoe }) => (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
key={domain}
|
||||||
|
className="mr-2">{`${domain} : ${yoe}`}</span>
|
||||||
|
<span>{background?.totalYoe}</span>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -131,41 +265,7 @@ export default function OfferProfile() {
|
|||||||
if (selectedTab === 'offers') {
|
if (selectedTab === 'offers') {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{[
|
{[...offers].map((offer) => (
|
||||||
{
|
|
||||||
base: undefined,
|
|
||||||
bonus: undefined,
|
|
||||||
companyName: 'Meta',
|
|
||||||
id: 1,
|
|
||||||
jobLevel: 'G5',
|
|
||||||
jobTitle: 'Software Engineer',
|
|
||||||
location: 'Singapore',
|
|
||||||
monthlySalary: undefined,
|
|
||||||
negotiationStrategy:
|
|
||||||
'Nostrud nulla aliqua deserunt commodo id aute.',
|
|
||||||
otherComment:
|
|
||||||
'Pariatur ut est voluptate incididunt consequat do veniam quis irure adipisicing. Deserunt laborum dolor quis voluptate enim.',
|
|
||||||
receivedMonth: 'Jun 2022',
|
|
||||||
stocks: undefined,
|
|
||||||
totalCompensation: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
companyName: 'Meta',
|
|
||||||
id: 2,
|
|
||||||
jobLevel: 'G5',
|
|
||||||
jobTitle: 'Software Engineer',
|
|
||||||
location: 'Singapore',
|
|
||||||
receivedMonth: 'Jun 2022',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
companyName: 'Meta',
|
|
||||||
id: 3,
|
|
||||||
jobLevel: 'G5',
|
|
||||||
jobTitle: 'Software Engineer',
|
|
||||||
location: 'Singapore',
|
|
||||||
receivedMonth: 'Jun 2022',
|
|
||||||
},
|
|
||||||
].map((offer) => (
|
|
||||||
<OfferCard key={offer.id} offer={offer} />
|
<OfferCard key={offer.id} offer={offer} />
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
@ -174,37 +274,32 @@ export default function OfferProfile() {
|
|||||||
if (selectedTab === 'background') {
|
if (selectedTab === 'background') {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mx-8 my-4 flex flex-row">
|
{background?.experiences && background?.experiences.length > 0 && (
|
||||||
<BriefcaseIcon className="mr-1 h-5" />
|
<>
|
||||||
<span className="font-bold">Work Experience</span>
|
<div className="mx-8 my-4 flex flex-row">
|
||||||
</div>
|
<BriefcaseIcon className="mr-1 h-5" />
|
||||||
<OfferCard
|
<span className="font-bold">Work Experience</span>
|
||||||
offer={{
|
</div>
|
||||||
base: undefined,
|
<OfferCard offer={background?.experiences[0]} />
|
||||||
bonus: undefined,
|
</>
|
||||||
companyName: 'Prefer not to say',
|
)}
|
||||||
jobLevel: 'G4',
|
{background?.educations && background?.educations.length > 0 && (
|
||||||
jobTitle: 'N/A',
|
<>
|
||||||
location: '',
|
<div className="mx-8 my-4 flex flex-row">
|
||||||
monthlySalary: '1,400k',
|
<AcademicCapIcon className="mr-1 h-5" />
|
||||||
receivedMonth: '',
|
<span className="font-bold">Education</span>
|
||||||
stocks: undefined,
|
</div>
|
||||||
totalCompensation: undefined,
|
<EducationCard
|
||||||
}}
|
education={{
|
||||||
/>
|
endDate: background.educations[0].endDate,
|
||||||
<div className="mx-8 my-4 flex flex-row">
|
field: background.educations[0].field,
|
||||||
<AcademicCapIcon className="mr-1 h-5" />
|
school: background.educations[0].school,
|
||||||
<span className="font-bold">Education</span>
|
startDate: background.educations[0].startDate,
|
||||||
</div>
|
type: EducationBackgroundType.Bachelor,
|
||||||
<EducationCard
|
}}
|
||||||
education={{
|
/>
|
||||||
backgroundType: EducationBackgroundType.Bachelor,
|
</>
|
||||||
field: 'CS',
|
)}
|
||||||
fromMonth: 'Aug 2019',
|
|
||||||
school: 'NUS',
|
|
||||||
toMonth: 'May 2021',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -215,14 +310,22 @@ export default function OfferProfile() {
|
|||||||
return (
|
return (
|
||||||
<div className="m-4">
|
<div className="m-4">
|
||||||
<div className="flex-end flex justify-end space-x-4">
|
<div className="flex-end flex justify-end space-x-4">
|
||||||
<Button
|
{isEditable && (
|
||||||
addonPosition="start"
|
<Button
|
||||||
icon={ClipboardDocumentIcon}
|
addonPosition="start"
|
||||||
isLabelHidden={false}
|
icon={ClipboardDocumentIcon}
|
||||||
label="Copy profile edit link"
|
isLabelHidden={false}
|
||||||
size="sm"
|
label="Copy profile edit link"
|
||||||
variant="secondary"
|
size="sm"
|
||||||
/>
|
variant="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
// TODO: Add notification
|
||||||
|
navigator.clipboard.writeText(
|
||||||
|
`${router.pathname}/${offerProfileId}?token=${token}`,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
addonPosition="start"
|
addonPosition="start"
|
||||||
icon={ShareIcon}
|
icon={ShareIcon}
|
||||||
@ -230,6 +333,12 @@ export default function OfferProfile() {
|
|||||||
label="Copy public link"
|
label="Copy public link"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
// TODO: Add notification
|
||||||
|
navigator.clipboard.writeText(
|
||||||
|
`${window.location.origin}/offers/profile/${offerProfileId}`,
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="mt-2 text-2xl font-bold">
|
<h2 className="mt-2 text-2xl font-bold">
|
||||||
@ -241,16 +350,19 @@ export default function OfferProfile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-4 flex flex h-screen w-screen items-center justify-center divide-x">
|
<>
|
||||||
<div className="h-full w-2/3 divide-y">
|
{detailsQuery.isError && ErrorPage}
|
||||||
<ProfileHeader />
|
<div className="mb-4 flex flex h-screen w-screen items-center justify-center divide-x">
|
||||||
<div className="h-4/5 w-full overflow-y-scroll pb-32">
|
<div className="h-full w-2/3 divide-y">
|
||||||
<ProfileDetails />
|
<ProfileHeader />
|
||||||
|
<div className="h-4/5 w-full overflow-y-scroll pb-32">
|
||||||
|
<ProfileDetails />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="h-full w-1/3 bg-white">
|
||||||
|
<ProfileComments />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full w-1/3 bg-white">
|
</>
|
||||||
<ProfileComments />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user