[offers][fix] Use title typeahead, add default currency and remove specialization field (#423)

This commit is contained in:
Ai Ling
2022-10-24 22:34:28 +08:00
committed by GitHub
parent 64cd69d024
commit bf35f97961
7 changed files with 120 additions and 136 deletions

View File

@ -2,26 +2,6 @@ import { EducationBackgroundType } from './types';
export const emptyOption = '----'; export const emptyOption = '----';
// TODO: use enums
export const titleOptions = [
{
label: 'Software Engineer',
value: 'Software Engineer',
},
{
label: 'Frontend Engineer',
value: 'Frontend Engineer',
},
{
label: 'Backend Engineer',
value: 'Backend Engineer',
},
{
label: 'Full-stack Engineer',
value: 'Full-stack Engineer',
},
];
export const locationOptions = [ export const locationOptions = [
{ {
label: 'Singapore, Singapore', label: 'Singapore, Singapore',

View File

@ -115,7 +115,7 @@ export default function OffersSubmissionForm({
), ),
hasNext: true, hasNext: true,
hasPrevious: false, hasPrevious: false,
label: 'Offer details', label: 'Offers',
}, },
{ {
component: <BackgroundForm key={1} />, component: <BackgroundForm key={1} />,
@ -123,30 +123,35 @@ export default function OffersSubmissionForm({
hasPrevious: true, hasPrevious: true,
label: 'Background', label: 'Background',
}, },
{
component: (
<OfferAnalysis
key={2}
allAnalysis={analysis}
isError={generateAnalysisMutation.isError}
isLoading={generateAnalysisMutation.isLoading}
/>
),
hasNext: true,
hasPrevious: false,
label: 'Analysis',
},
{ {
component: ( component: (
<OffersProfileSave <OffersProfileSave
key={3} key={2}
profileId={createProfileResponse.id || ''} profileId={createProfileResponse.id || ''}
token={createProfileResponse.token} token={createProfileResponse.token}
/> />
), ),
hasNext: false, hasNext: true,
hasPrevious: false, hasPrevious: false,
label: 'Save', label: 'Save profile',
},
{
component: (
<div>
<h5 className="mb-8 text-center text-4xl font-bold text-slate-900">
Result
</h5>
<OfferAnalysis
key={3}
allAnalysis={analysis}
isError={generateAnalysisMutation.isError}
isLoading={generateAnalysisMutation.isLoading}
/>
</div>
),
hasNext: false,
hasPrevious: true,
label: 'Analysis',
}, },
]; ];
@ -231,7 +236,7 @@ export default function OffersSubmissionForm({
<FormProvider {...formMethods}> <FormProvider {...formMethods}>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
{formSteps[formStep].component} {formSteps[formStep].component}
{/* <pre>{JSON.stringify(formMethods.watch(), null, 2)}</pre> */} <pre>{JSON.stringify(formMethods.watch(), null, 2)}</pre>
{formSteps[formStep].hasNext && ( {formSteps[formStep].hasNext && (
<div className="flex justify-end"> <div className="flex justify-end">
<Button <Button

View File

@ -8,13 +8,17 @@ import {
emptyOption, emptyOption,
FieldError, FieldError,
locationOptions, locationOptions,
titleOptions,
} from '~/components/offers/constants'; } from '~/components/offers/constants';
import type { BackgroundPostData } from '~/components/offers/types'; import type { BackgroundPostData } from '~/components/offers/types';
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead';
import { CURRENCY_OPTIONS } from '~/utils/offers/currency/CurrencyEnum'; import {
Currency,
CURRENCY_OPTIONS,
} from '~/utils/offers/currency/CurrencyEnum';
import FormMonthYearPicker from '../../forms/FormMonthYearPicker';
import FormRadioList from '../../forms/FormRadioList'; import FormRadioList from '../../forms/FormRadioList';
import FormSelect from '../../forms/FormSelect'; import FormSelect from '../../forms/FormSelect';
import FormTextInput from '../../forms/FormTextInput'; import FormTextInput from '../../forms/FormTextInput';
@ -92,13 +96,13 @@ function FullTimeJobFields() {
return ( return (
<> <>
<div className="mb-5 grid grid-cols-2 space-x-3"> <div className="mb-5 grid grid-cols-2 space-x-3">
<FormSelect <div>
display="block" <JobTitlesTypeahead
label="Title" onSelect={({ value }) =>
options={titleOptions} setValue(`background.experiences.0.title`, value)
placeholder={emptyOption} }
{...register(`background.experiences.0.title`)}
/> />
</div>
<div> <div>
<CompaniesTypeahead <CompaniesTypeahead
onSelect={({ value }) => onSelect={({ value }) =>
@ -112,6 +116,7 @@ function FullTimeJobFields() {
endAddOn={ endAddOn={
<FormSelect <FormSelect
borderStyle="borderless" borderStyle="borderless"
defaultValue={Currency.SGD}
isLabelHidden={true} isLabelHidden={true}
label="Currency" label="Currency"
options={CURRENCY_OPTIONS} options={CURRENCY_OPTIONS}
@ -177,13 +182,13 @@ function InternshipJobFields() {
return ( return (
<> <>
<div className="mb-5 grid grid-cols-2 space-x-3"> <div className="mb-5 grid grid-cols-2 space-x-3">
<FormSelect <div>
display="block" <JobTitlesTypeahead
label="Title" onSelect={({ value }) =>
options={titleOptions} setValue(`background.experiences.0.title`, value)
placeholder={emptyOption} }
{...register(`background.experiences.0.title`)}
/> />
</div>
<div> <div>
<CompaniesTypeahead <CompaniesTypeahead
onSelect={({ value }) => onSelect={({ value }) =>
@ -197,6 +202,7 @@ function InternshipJobFields() {
endAddOn={ endAddOn={
<FormSelect <FormSelect
borderStyle="borderless" borderStyle="borderless"
defaultValue={Currency.SGD}
isLabelHidden={true} isLabelHidden={true}
label="Currency" label="Currency"
options={CURRENCY_OPTIONS} options={CURRENCY_OPTIONS}
@ -310,6 +316,22 @@ function EducationSection() {
{...register(`background.educations.0.school`)} {...register(`background.educations.0.school`)}
/> />
</div> </div>
<div className="grid grid-cols-2 space-x-3">
<FormMonthYearPicker
monthLabel="Candidature Start"
yearLabel=""
{...register(`background.educations.0.startDate`, {
required: FieldError.REQUIRED,
})}
/>
<FormMonthYearPicker
monthLabel="Candidature End"
yearLabel=""
{...register(`background.educations.0.endDate`, {
required: FieldError.REQUIRED,
})}
/>
</div>
</Collapsible> </Collapsible>
</div> </div>
</> </>
@ -319,13 +341,9 @@ function EducationSection() {
export default function BackgroundForm() { export default function BackgroundForm() {
return ( return (
<div> <div>
<h5 className="mb-2 text-center text-4xl font-bold text-slate-900"> <h5 className="mb-8 text-center text-4xl font-bold text-slate-900">
Help us better gauge your offers Help us better gauge your offers
</h5> </h5>
<h6 className="text-md mx-10 mb-8 text-center font-light text-slate-600">
This section is mostly optional, but your background information helps
us benchmark your offers.
</h6>
<div> <div>
<YoeSection /> <YoeSection />
<CurrentJobSection /> <CurrentJobSection />

View File

@ -13,6 +13,7 @@ import { JobType } from '@prisma/client';
import { Button, Dialog } from '@tih/ui'; import { Button, Dialog } from '@tih/ui';
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead';
import { import {
defaultFullTimeOfferValues, defaultFullTimeOfferValues,
@ -23,7 +24,6 @@ import {
FieldError, FieldError,
internshipCycleOptions, internshipCycleOptions,
locationOptions, locationOptions,
titleOptions,
yearOptions, yearOptions,
} from '../../constants'; } from '../../constants';
import FormMonthYearPicker from '../../forms/FormMonthYearPicker'; import FormMonthYearPicker from '../../forms/FormMonthYearPicker';
@ -32,7 +32,10 @@ import FormTextArea from '../../forms/FormTextArea';
import FormTextInput from '../../forms/FormTextInput'; import FormTextInput from '../../forms/FormTextInput';
import type { OfferFormData } from '../../types'; import type { OfferFormData } from '../../types';
import { JobTypeLabel } from '../../types'; import { JobTypeLabel } from '../../types';
import { CURRENCY_OPTIONS } from '../../../../utils/offers/currency/CurrencyEnum'; import {
Currency,
CURRENCY_OPTIONS,
} from '../../../../utils/offers/currency/CurrencyEnum';
type FullTimeOfferDetailsFormProps = Readonly<{ type FullTimeOfferDetailsFormProps = Readonly<{
index: number; index: number;
@ -64,32 +67,11 @@ function FullTimeOfferDetailsForm({
return ( return (
<div className="my-5 rounded-lg border border-slate-200 px-10 py-5"> <div className="my-5 rounded-lg border border-slate-200 px-10 py-5">
<div className="mb-5 grid grid-cols-2 space-x-3"> <div className="mb-5 grid grid-cols-2 space-x-3">
<FormSelect
display="block"
errorMessage={offerFields?.offersFullTime?.title?.message}
label="Title"
options={titleOptions}
placeholder={emptyOption}
required={true}
{...register(`offers.${index}.offersFullTime.title`, {
required: FieldError.REQUIRED,
})}
/>
<FormTextInput
errorMessage={offerFields?.offersFullTime?.specialization?.message}
label="Focus / Specialization"
placeholder="e.g. Front End"
required={true}
{...register(`offers.${index}.offersFullTime.specialization`, {
required: FieldError.REQUIRED,
})}
/>
</div>
<div className="mb-5 flex grid grid-cols-2 space-x-3">
<div> <div>
<CompaniesTypeahead <JobTitlesTypeahead
required={true}
onSelect={({ value }) => onSelect={({ value }) =>
setValue(`offers.${index}.companyId`, value) setValue(`offers.${index}.offersFullTime.title`, value)
} }
/> />
</div> </div>
@ -103,7 +85,15 @@ function FullTimeOfferDetailsForm({
})} })}
/> />
</div> </div>
<div className="mb-5 flex grid grid-cols-2 items-start space-x-3"> <div className="mb-5 flex grid grid-cols-2 space-x-3">
<div>
<CompaniesTypeahead
required={true}
onSelect={({ value }) =>
setValue(`offers.${index}.companyId`, value)
}
/>
</div>
<FormSelect <FormSelect
display="block" display="block"
errorMessage={offerFields?.location?.message} errorMessage={offerFields?.location?.message}
@ -115,6 +105,8 @@ function FullTimeOfferDetailsForm({
required: FieldError.REQUIRED, required: FieldError.REQUIRED,
})} })}
/> />
</div>
<div className="mb-5 flex grid grid-cols-2 items-start space-x-3">
<FormMonthYearPicker <FormMonthYearPicker
monthLabel="Date Received" monthLabel="Date Received"
monthRequired={true} monthRequired={true}
@ -129,6 +121,7 @@ function FullTimeOfferDetailsForm({
endAddOn={ endAddOn={
<FormSelect <FormSelect
borderStyle="borderless" borderStyle="borderless"
defaultValue={Currency.SGD}
isLabelHidden={true} isLabelHidden={true}
label="Currency" label="Currency"
options={CURRENCY_OPTIONS} options={CURRENCY_OPTIONS}
@ -165,14 +158,12 @@ function FullTimeOfferDetailsForm({
endAddOn={ endAddOn={
<FormSelect <FormSelect
borderStyle="borderless" borderStyle="borderless"
defaultValue={Currency.SGD}
isLabelHidden={true} isLabelHidden={true}
label="Currency" label="Currency"
options={CURRENCY_OPTIONS} options={CURRENCY_OPTIONS}
{...register( {...register(
`offers.${index}.offersFullTime.baseSalary.currency`, `offers.${index}.offersFullTime.baseSalary.currency`,
{
required: FieldError.REQUIRED,
},
)} )}
/> />
} }
@ -180,13 +171,11 @@ function FullTimeOfferDetailsForm({
errorMessage={offerFields?.offersFullTime?.baseSalary?.value?.message} errorMessage={offerFields?.offersFullTime?.baseSalary?.value?.message}
label="Base Salary (Annual)" label="Base Salary (Annual)"
placeholder="0" placeholder="0"
required={true}
startAddOn="$" startAddOn="$"
startAddOnType="label" startAddOnType="label"
type="number" type="number"
{...register(`offers.${index}.offersFullTime.baseSalary.value`, { {...register(`offers.${index}.offersFullTime.baseSalary.value`, {
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
required: FieldError.REQUIRED,
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
@ -194,25 +183,22 @@ function FullTimeOfferDetailsForm({
endAddOn={ endAddOn={
<FormSelect <FormSelect
borderStyle="borderless" borderStyle="borderless"
defaultValue={Currency.SGD}
isLabelHidden={true} isLabelHidden={true}
label="Currency" label="Currency"
options={CURRENCY_OPTIONS} options={CURRENCY_OPTIONS}
{...register(`offers.${index}.offersFullTime.bonus.currency`, { {...register(`offers.${index}.offersFullTime.bonus.currency`)}
required: FieldError.REQUIRED,
})}
/> />
} }
endAddOnType="element" endAddOnType="element"
errorMessage={offerFields?.offersFullTime?.bonus?.value?.message} errorMessage={offerFields?.offersFullTime?.bonus?.value?.message}
label="Bonus (Annual)" label="Bonus (Annual)"
placeholder="0" placeholder="0"
required={true}
startAddOn="$" startAddOn="$"
startAddOnType="label" startAddOnType="label"
type="number" type="number"
{...register(`offers.${index}.offersFullTime.bonus.value`, { {...register(`offers.${index}.offersFullTime.bonus.value`, {
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
required: FieldError.REQUIRED,
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
@ -222,25 +208,22 @@ function FullTimeOfferDetailsForm({
endAddOn={ endAddOn={
<FormSelect <FormSelect
borderStyle="borderless" borderStyle="borderless"
defaultValue={Currency.SGD}
isLabelHidden={true} isLabelHidden={true}
label="Currency" label="Currency"
options={CURRENCY_OPTIONS} options={CURRENCY_OPTIONS}
{...register(`offers.${index}.offersFullTime.stocks.currency`, { {...register(`offers.${index}.offersFullTime.stocks.currency`)}
required: FieldError.REQUIRED,
})}
/> />
} }
endAddOnType="element" endAddOnType="element"
errorMessage={offerFields?.offersFullTime?.stocks?.value?.message} errorMessage={offerFields?.offersFullTime?.stocks?.value?.message}
label="Stocks (Annual)" label="Stocks (Annual)"
placeholder="0" placeholder="0"
required={true}
startAddOn="$" startAddOn="$"
startAddOnType="label" startAddOnType="label"
type="number" type="number"
{...register(`offers.${index}.offersFullTime.stocks.value`, { {...register(`offers.${index}.offersFullTime.stocks.value`, {
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
required: FieldError.REQUIRED,
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
@ -291,32 +274,19 @@ function InternshipOfferDetailsForm({
return ( return (
<div className="my-5 rounded-lg border border-slate-200 px-10 py-5"> <div className="my-5 rounded-lg border border-slate-200 px-10 py-5">
<div className="mb-5 grid grid-cols-2 space-x-3"> <div className="mb-5 grid grid-cols-2 space-x-3">
<FormSelect <div>
display="block" <JobTitlesTypeahead
errorMessage={offerFields?.offersIntern?.title?.message}
label="Title"
options={titleOptions}
placeholder={emptyOption}
required={true} required={true}
{...register(`offers.${index}.offersIntern.title`, { onSelect={({ value }) =>
minLength: 1, setValue(`offers.${index}.offersIntern.title`, value)
required: FieldError.REQUIRED, }
})}
/>
<FormTextInput
errorMessage={offerFields?.offersIntern?.specialization?.message}
label="Focus / Specialization"
placeholder="e.g. Front End"
required={true}
{...register(`offers.${index}.offersIntern.specialization`, {
minLength: 1,
required: FieldError.REQUIRED,
})}
/> />
</div> </div>
</div>
<div className="mb-5 grid grid-cols-2 space-x-3"> <div className="mb-5 grid grid-cols-2 space-x-3">
<div> <div>
<CompaniesTypeahead <CompaniesTypeahead
required={true}
onSelect={({ value }) => onSelect={({ value }) =>
setValue(`offers.${index}.companyId`, value) setValue(`offers.${index}.companyId`, value)
} }
@ -374,6 +344,7 @@ function InternshipOfferDetailsForm({
endAddOn={ endAddOn={
<FormSelect <FormSelect
borderStyle="borderless" borderStyle="borderless"
defaultValue={Currency.SGD}
isLabelHidden={true} isLabelHidden={true}
label="Currency" label="Currency"
options={CURRENCY_OPTIONS} options={CURRENCY_OPTIONS}

View File

@ -1,5 +1,8 @@
import Link from 'next/link'; import Link from 'next/link';
import type { JobTitleType } from '~/components/shared/JobTitles';
import { getLabelForJobTitleType } from '~/components/shared/JobTitles';
import { convertMoneyToString } from '~/utils/offers/currency'; import { convertMoneyToString } from '~/utils/offers/currency';
import { formatDate } from '~/utils/offers/time'; import { formatDate } from '~/utils/offers/time';
@ -19,7 +22,9 @@ export default function OfferTableRow({
scope="row"> scope="row">
{company.name} {company.name}
</th> </th>
<td className="py-4 px-6">{title}</td> <td className="py-4 px-6">
{getLabelForJobTitleType(title as JobTitleType)}
</td>
<td className="py-4 px-6">{totalYoe}</td> <td className="py-4 px-6">{totalYoe}</td>
<td className="py-4 px-6">{convertMoneyToString(income)}</td> <td className="py-4 px-6">{convertMoneyToString(income)}</td>
<td className="py-4 px-6">{formatDate(monthYearReceived)}</td> <td className="py-4 px-6">{formatDate(monthYearReceived)}</td>

View File

@ -1,13 +1,12 @@
import { useState } from 'react'; import { useState } from 'react';
import { Select } from '@tih/ui';
import { titleOptions } from '~/components/offers/constants';
import OffersTitle from '~/components/offers/OffersTitle'; import OffersTitle from '~/components/offers/OffersTitle';
import OffersTable from '~/components/offers/table/OffersTable'; import OffersTable from '~/components/offers/table/OffersTable';
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead';
export default function OffersHomePage() { export default function OffersHomePage() {
const [jobTitleFilter, setjobTitleFilter] = useState('Software Engineer'); const [jobTitleFilter, setjobTitleFilter] = useState('software-engineer');
const [companyFilter, setCompanyFilter] = useState(''); const [companyFilter, setCompanyFilter] = useState('');
return ( return (
@ -18,19 +17,17 @@ export default function OffersHomePage() {
<div className="mt-4 flex items-center"> <div className="mt-4 flex items-center">
Viewing offers for Viewing offers for
<div className="mx-4"> <div className="mx-4">
<Select <JobTitlesTypeahead
isLabelHidden={true} isLabelHidden={true}
label="Select a job title" placeHolder="Software Engineer"
options={titleOptions} onSelect={({ value }) => setjobTitleFilter(value)}
value={jobTitleFilter}
onChange={setjobTitleFilter}
/> />
</div> </div>
in in
<div className="ml-4"> <div className="ml-4">
<CompaniesTypeahead <CompaniesTypeahead
isLabelHidden={true} isLabelHidden={true}
placeHolder="All companies" placeHolder="All Companies"
onSelect={({ value }) => setCompanyFilter(value)} onSelect={({ value }) => setCompanyFilter(value)}
/> />
</div> </div>

View File

@ -10,6 +10,8 @@ import type {
BackgroundDisplayData, BackgroundDisplayData,
OfferDisplayData, OfferDisplayData,
} from '~/components/offers/types'; } from '~/components/offers/types';
import type { JobTitleType } from '~/components/shared/JobTitles';
import { getLabelForJobTitleType } from '~/components/shared/JobTitles';
import { useToast } from '~/../../../packages/ui/dist'; import { useToast } from '~/../../../packages/ui/dist';
import { convertMoneyToString } from '~/utils/offers/currency'; import { convertMoneyToString } from '~/utils/offers/currency';
@ -62,7 +64,9 @@ export default function OfferProfile() {
companyName: res.company.name, companyName: res.company.name,
id: res.offersFullTime.id, id: res.offersFullTime.id,
jobLevel: res.offersFullTime.level, jobLevel: res.offersFullTime.level,
jobTitle: res.offersFullTime.title, jobTitle: getLabelForJobTitleType(
res.offersFullTime.title as JobTitleType,
),
location: res.location, location: res.location,
negotiationStrategy: res.negotiationStrategy, negotiationStrategy: res.negotiationStrategy,
otherComment: res.comments, otherComment: res.comments,
@ -77,7 +81,9 @@ export default function OfferProfile() {
const filteredOffer: OfferDisplayData = { const filteredOffer: OfferDisplayData = {
companyName: res.company.name, companyName: res.company.name,
id: res.offersIntern!.id, id: res.offersIntern!.id,
jobTitle: res.offersIntern!.title, jobTitle: getLabelForJobTitleType(
res.offersIntern!.title as JobTitleType,
),
location: res.location, location: res.location,
monthlySalary: convertMoneyToString( monthlySalary: convertMoneyToString(
res.offersIntern!.monthlySalary, res.offersIntern!.monthlySalary,
@ -107,7 +113,9 @@ export default function OfferProfile() {
companyName: experience.company?.name, companyName: experience.company?.name,
duration: experience.durationInMonths, duration: experience.durationInMonths,
jobLevel: experience.level, jobLevel: experience.level,
jobTitle: experience.title, jobTitle: experience.title
? getLabelForJobTitleType(experience.title as JobTitleType)
: null,
monthlySalary: experience.monthlySalary monthlySalary: experience.monthlySalary
? convertMoneyToString(experience.monthlySalary) ? convertMoneyToString(experience.monthlySalary)
: null, : null,