[offers][fix] Fix offer submission form UI

pull/358/head
Ai Ling 3 years ago
parent 0822bee33b
commit 7891c2d2ed

@ -1,9 +1,9 @@
import { useFormContext, useWatch } from 'react-hook-form';
import { Collapsible, RadioList } from '@tih/ui';
import FormRadioList from './FormRadioList';
import FormSelect from './FormSelect';
import FormTextInput from './FormTextInput';
import FormRadioList from './components/FormRadioList';
import FormSelect from './components/FormSelect';
import FormTextInput from './components/FormTextInput';
import {
companyOptions,
educationFieldOptions,

@ -86,8 +86,7 @@ export default function OfferAnalysis() {
<h5 className="mb-2 text-center text-4xl font-bold text-gray-900">
Result
</h5>
<div className="mx-40">
<div>
<Tabs
label="Result Navigation"
tabs={tabs}

@ -10,9 +10,10 @@ import { PlusIcon } from '@heroicons/react/20/solid';
import { TrashIcon } from '@heroicons/react/24/outline';
import { Button } from '@tih/ui';
import FormSelect from './FormSelect';
import FormTextArea from './FormTextArea';
import FormTextInput from './FormTextInput';
import FormMonthYearPicker from './components/FormMonthYearPicker';
import FormSelect from './components/FormSelect';
import FormTextArea from './components/FormTextArea';
import FormTextInput from './components/FormTextInput';
import {
companyOptions,
internshipCycleOptions,
@ -20,7 +21,7 @@ import {
titleOptions,
yearOptions,
} from '../constants';
import type { FullTimeOfferFormData, InternshipOfferFormData } from '../types';
import type { OfferDetailsFormData } from '../types';
import { JobType } from '../types';
import { CURRENCY_OPTIONS } from '../util/currency/CurrencyEnum';
@ -34,7 +35,7 @@ function FullTimeOfferDetailsForm({
remove,
}: FullTimeOfferDetailsFormProps) {
const { register } = useFormContext<{
offers: Array<FullTimeOfferFormData>;
offers: Array<OfferDetailsFormData>;
}>();
return (
@ -81,10 +82,7 @@ function FullTimeOfferDetailsForm({
required={true}
{...register(`offers.${index}.location`, { required: true })}
/>
<FormTextInput
label="Month Received"
placeholder="MMM/YYYY"
required={true}
<FormMonthYearPicker
{...register(`offers.${index}.monthYearReceived`, { required: true })}
/>
</div>
@ -121,7 +119,9 @@ function FullTimeOfferDetailsForm({
isLabelHidden={true}
label="Currency"
options={CURRENCY_OPTIONS}
{...register(`offers.${index}.job.base.currency`)}
{...register(`offers.${index}.job.base.currency`, {
required: true,
})}
/>
}
endAddOnType="element"
@ -131,7 +131,7 @@ function FullTimeOfferDetailsForm({
startAddOn="$"
startAddOnType="label"
type="number"
{...register(`offers.${index}.job.base.value`)}
{...register(`offers.${index}.job.base.value`, { required: true })}
/>
<FormTextInput
endAddOn={
@ -140,7 +140,9 @@ function FullTimeOfferDetailsForm({
isLabelHidden={true}
label="Currency"
options={CURRENCY_OPTIONS}
{...register(`offers.${index}.job.bonus.currency`)}
{...register(`offers.${index}.job.bonus.currency`, {
required: true,
})}
/>
}
endAddOnType="element"
@ -150,7 +152,7 @@ function FullTimeOfferDetailsForm({
startAddOn="$"
startAddOnType="label"
type="number"
{...register(`offers.${index}.job.bonus.value`)}
{...register(`offers.${index}.job.bonus.value`, { required: true })}
/>
</div>
<div className="mb-5 grid grid-cols-2 space-x-3">
@ -161,7 +163,9 @@ function FullTimeOfferDetailsForm({
isLabelHidden={true}
label="Currency"
options={CURRENCY_OPTIONS}
{...register(`offers.${index}.job.stocks.currency`)}
{...register(`offers.${index}.job.stocks.currency`, {
required: true,
})}
/>
}
endAddOnType="element"
@ -171,7 +175,7 @@ function FullTimeOfferDetailsForm({
startAddOn="$"
startAddOnType="label"
type="number"
{...register(`offers.${index}.job.stocks.value`)}
{...register(`offers.${index}.job.stocks.value`, { required: true })}
/>
</div>
<div className="mb-5">
@ -251,7 +255,7 @@ function InternshipOfferDetailsForm({
remove,
}: InternshipOfferDetailsFormProps) {
const { register } = useFormContext<{
offers: Array<InternshipOfferFormData>;
offers: Array<OfferDetailsFormData>;
}>();
return (
@ -262,13 +266,19 @@ function InternshipOfferDetailsForm({
label="Title"
options={titleOptions}
required={true}
{...register(`offers.${index}.job.title`)}
{...register(`offers.${index}.job.title`, {
minLength: 1,
required: true,
})}
/>
<FormTextInput
label="Focus / Specialization"
placeholder="e.g. Front End"
required={true}
{...register(`offers.${index}.job.specialization`)}
{...register(`offers.${index}.job.specialization`, {
minLength: 1,
required: true,
})}
/>
</div>
<div className="mb-5 grid grid-cols-2 space-x-3">
@ -289,13 +299,7 @@ function InternshipOfferDetailsForm({
{...register(`offers.${index}.location`)}
/>
</div>
<div className="mb-5 grid grid-cols-3 space-x-3">
<FormTextInput
label="Date Received"
placeholder="MMM/YYYY"
required={true}
{...register(`offers.${index}.monthYearReceived`)}
/>
<div className="mb-5 grid grid-cols-2 space-x-3">
<FormSelect
display="block"
label="Internship Cycle"
@ -313,6 +317,12 @@ function InternshipOfferDetailsForm({
{...register(`offers.${index}.job.startYear`)}
/>
</div>
<div className="mb-5 flex items-center space-x-9">
<p className="text-sm">Date received:</p>
<FormMonthYearPicker
{...register(`offers.${index}.monthYearReceived`, { required: true })}
/>
</div>
<div className="mb-5">
<FormTextInput
endAddOn={

@ -0,0 +1,42 @@
import type { ComponentProps } from 'react';
import { forwardRef } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
import MonthYearPicker from '~/components/shared/MonthYearPicker';
import { getCurrentMonth, getCurrentYear } from '../../util/time';
type MonthYearPickerProps = ComponentProps<typeof MonthYearPicker>;
type FormMonthYearPickerProps = Omit<
MonthYearPickerProps,
'onChange' | 'value'
> & {
name: string;
};
function FormMonthYearPickerWithRef({
name,
...rest
}: FormMonthYearPickerProps) {
const { setValue } = useFormContext();
const value = useWatch({
defaultValue: { month: getCurrentMonth(), year: getCurrentYear() },
name,
});
return (
<MonthYearPicker
{...(rest as MonthYearPickerProps)}
value={value}
onChange={(val) => {
setValue(name, val);
}}
/>
);
}
const FormMonthYearPicker = forwardRef(FormMonthYearPickerWithRef);
export default FormMonthYearPicker;

@ -3,6 +3,8 @@
* Offer Profile
*/
import type { MonthYear } from '../shared/MonthYearPicker';
export enum JobType {
FullTime = 'FULLTIME',
Internship = 'INTERNSHIP',
@ -33,16 +35,6 @@ type FullTimeJobData = {
totalCompensation: Money;
};
export type FullTimeOfferFormData = {
comments: string;
companyId: string;
job: FullTimeJobData;
jobType: string;
location: string;
monthYearReceived: string;
negotiationStrategy: string;
};
type InternshipJobData = {
internshipCycle: string;
monthlySalary: Money;
@ -51,18 +43,16 @@ type InternshipJobData = {
title: string;
};
export type InternshipOfferFormData = {
export type OfferDetailsFormData = {
comments: string;
companyId: string;
job: InternshipJobData;
job: FullTimeJobData | InternshipJobData;
jobType: string;
location: string;
monthYearReceived: string;
monthYearReceived: MonthYear;
negotiationStrategy: string;
};
type OfferDetailsFormData = FullTimeOfferFormData | InternshipOfferFormData;
type SpecificYoe = {
domain: string;
yoe: number;

@ -1,3 +1,7 @@
import { getMonth, getYear } from 'date-fns';
import type { MonthYear } from '~/components/shared/MonthYearPicker';
export function formatDate(value: Date | number | string) {
const date = new Date(value);
// Const day = date.toLocaleString('default', { day: '2-digit' });
@ -5,3 +9,17 @@ export function formatDate(value: Date | number | string) {
const year = date.toLocaleString('default', { year: 'numeric' });
return `${month} ${year}`;
}
export function formatMonthYear({ month, year }: MonthYear) {
const monthString = month < 10 ? month.toString() : `0${month}`;
const yearString = year.toString();
return `${monthString}/${yearString}`;
}
export function getCurrentMonth() {
return getMonth(Date.now());
}
export function getCurrentYear() {
return getYear(Date.now());
}

@ -3,9 +3,11 @@ import { Select } from '@tih/ui';
import OffersTable from '~/components/offers/OffersTable';
import OffersTitle from '~/components/offers/OffersTitle';
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
export default function OffersHomePage() {
const [jobTitleFilter, setjobTitleFilter] = useState('Software engineers');
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [companyFilter, setCompanyFilter] = useState('All companies');
return (
@ -13,7 +15,7 @@ export default function OffersHomePage() {
<div className="grid-rows grid h-1/2 bg-gray-100">
<OffersTitle />
<div className="flex items-start justify-center">
<div className="mt-4 flex items-center">
<div className="mt-4 flex items-end">
Viewing offers for
<div className="mx-4">
<Select
@ -43,25 +45,8 @@ export default function OffersHomePage() {
</div>
in
<div className="ml-4">
<Select
isLabelHidden={true}
label="Select a company"
options={[
{
label: 'All companies',
value: 'All companies',
},
{
label: 'Shopee',
value: 'Shopee',
},
{
label: 'Meta',
value: 'Meta',
},
]}
value={companyFilter}
onChange={setCompanyFilter}
<CompaniesTypeahead
onSelect={({ value }) => setCompanyFilter(value)}
/>
</div>
</div>

@ -9,6 +9,8 @@ import OfferAnalysis from '~/components/offers/forms/OfferAnalysis';
import OfferDetailsForm from '~/components/offers/forms/OfferDetailsForm';
import OfferProfileSave from '~/components/offers/forms/OfferProfileSave';
import type { SubmitOfferFormData } from '~/components/offers/types';
import { getCurrentMonth, getCurrentYear } from '~/components/offers/util/time';
import type { Month } from '~/components/shared/MonthYearPicker';
function Breadcrumbs() {
return (
@ -23,53 +25,69 @@ const defaultOfferValues = {
{
comments: '',
companyId: '',
job: {
base: {
currency: 'USD',
value: 0,
},
bonus: {
currency: 'USD',
value: 0,
},
level: '',
specialization: '',
stocks: {
currency: 'USD',
value: 0,
},
title: '',
totalCompensation: {
currency: 'USD',
value: 0,
},
},
job: {},
jobType: 'FULLTIME',
location: '',
monthYearReceived: '',
monthYearReceived: {
month: getCurrentMonth() as Month,
year: getCurrentYear(),
},
negotiationStrategy: '',
},
],
};
type FormStep = {
component: JSX.Element;
hasNext: boolean;
hasPrevious: boolean;
};
export default function OffersSubmissionPage() {
const [formStep, setFormStep] = useState(0);
const formMethods = useForm<SubmitOfferFormData>({
defaultValues: defaultOfferValues,
mode: 'all',
});
const { handleSubmit, trigger } = formMethods;
const nextStep = () => setFormStep(formStep + 1);
const previousStep = () => setFormStep(formStep - 1);
const formComponents = [
<OfferDetailsForm key={0} />,
<BackgroundForm key={1} />,
<OfferAnalysis key={2} />,
<OfferProfileSave key={3} />,
const formSteps: Array<FormStep> = [
{
component: <OfferDetailsForm key={0} />,
hasNext: true,
hasPrevious: false,
},
{
component: <BackgroundForm key={1} />,
hasNext: false,
hasPrevious: true,
},
{ component: <OfferAnalysis key={2} />, hasNext: true, hasPrevious: false },
{
component: <OfferProfileSave key={3} />,
hasNext: false,
hasPrevious: false,
},
];
const nextStep = async (currStep: number) => {
if (currStep === 0) {
const result = await trigger('offers');
if (!result) {
return;
}
}
setFormStep(formStep + 1);
};
const previousStep = () => setFormStep(formStep - 1);
const onSubmit: SubmitHandler<SubmitOfferFormData> = async () => {
nextStep();
const result = await trigger();
if (!result) {
return;
}
setFormStep(formStep + 1);
};
return (
@ -78,16 +96,17 @@ export default function OffersSubmissionPage() {
<div className="my-5 block w-full max-w-screen-md rounded-lg bg-white py-10 px-10 shadow-lg">
<Breadcrumbs />
<FormProvider {...formMethods}>
<form onSubmit={formMethods.handleSubmit(onSubmit)}>
{formComponents[formStep]}
<form onSubmit={handleSubmit(onSubmit)}>
{formSteps[formStep].component}
{/* <pre>{JSON.stringify(formMethods.watch(), null, 2)}</pre> */}
{(formStep === 0 || formStep === 2) && (
{formSteps[formStep].hasNext && (
<div className="flex justify-end">
<Button
disabled={false}
icon={ArrowRightIcon}
label="Next"
variant="secondary"
onClick={nextStep}
onClick={() => nextStep(formStep)}
/>
</div>
)}

Loading…
Cancel
Save