[resumes][feat] migrate to use location db and role enum (#506)
* [resumes][feat] use role and countries typeaheads * [resumes][feat] add location and role typeaheads * [resumes][chore] locationId migration * [resumes][fix] update upsert to take in locationId * [resumes][refactor] use typeahead for browse filters * [resumes][feat] use role and countries typeaheads * [resumes][chore] locationId migration * [resumes][feat] fetch location on resumes page * [resumes][feat] enable edit resume form * [resumes][misc] update namings and oredrings * [resumes][feat] add default locations * [resumes][fix] truncate title text in resume card * [resumes][fix] filter out selected options * [resumes][feat] add more countries to default search * [resumes][feat] update default roles * [resumes][chore] revert removal of value * [resumes]feat] add default location for migration file * [resumes][fix] fix merge conflicts Co-authored-by: Wu Peirong <wupeirong294@gmail.com>pull/519/head
parent
9815d125ff
commit
1ebd32ca2f
@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `location` on the `ResumesResume` table. All the data in the column will be lost.
|
||||||
|
- Added the required column `locationId` to the `ResumesResume` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable. Set default location to Singapore.
|
||||||
|
ALTER TABLE "ResumesResume" DROP COLUMN "location",
|
||||||
|
ADD COLUMN "locationId" TEXT NOT NULL DEFAULT '196';
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "ResumesResume" ADD CONSTRAINT "ResumesResume_locationId_fkey" FOREIGN KEY ("locationId") REFERENCES "Country"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -0,0 +1,51 @@
|
|||||||
|
import type { ComponentProps } from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import type { TypeaheadOption } from '@tih/ui';
|
||||||
|
import { Typeahead } from '@tih/ui';
|
||||||
|
|
||||||
|
import { EXPERIENCES } from '~/utils/resumes/resumeFilters';
|
||||||
|
|
||||||
|
type BaseProps = Pick<
|
||||||
|
ComponentProps<typeof Typeahead>,
|
||||||
|
| 'disabled'
|
||||||
|
| 'errorMessage'
|
||||||
|
| 'isLabelHidden'
|
||||||
|
| 'placeholder'
|
||||||
|
| 'required'
|
||||||
|
| 'textSize'
|
||||||
|
>;
|
||||||
|
|
||||||
|
type Props = BaseProps &
|
||||||
|
Readonly<{
|
||||||
|
onSelect: (option: TypeaheadOption | null) => void;
|
||||||
|
selectedValues?: Set<string>;
|
||||||
|
value?: TypeaheadOption | null;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default function ResumeExperienceTypeahead({
|
||||||
|
onSelect,
|
||||||
|
selectedValues = new Set(),
|
||||||
|
value,
|
||||||
|
...props
|
||||||
|
}: Props) {
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
const options = EXPERIENCES.filter(
|
||||||
|
(option) => !selectedValues.has(option.value),
|
||||||
|
).filter(
|
||||||
|
({ label }) =>
|
||||||
|
label.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()) > -1,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typeahead
|
||||||
|
label="Experiences"
|
||||||
|
noResultsMessage="No available experiences."
|
||||||
|
nullable={true}
|
||||||
|
options={options}
|
||||||
|
value={value}
|
||||||
|
onQueryChange={setQuery}
|
||||||
|
onSelect={onSelect}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
import type { ComponentProps } from 'react';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import type { TypeaheadOption } from '@tih/ui';
|
||||||
|
import { Typeahead } from '@tih/ui';
|
||||||
|
|
||||||
|
import { trpc } from '~/utils/trpc';
|
||||||
|
|
||||||
|
type BaseProps = Pick<
|
||||||
|
ComponentProps<typeof Typeahead>,
|
||||||
|
| 'disabled'
|
||||||
|
| 'errorMessage'
|
||||||
|
| 'isLabelHidden'
|
||||||
|
| 'placeholder'
|
||||||
|
| 'required'
|
||||||
|
| 'textSize'
|
||||||
|
>;
|
||||||
|
|
||||||
|
type Props = BaseProps &
|
||||||
|
Readonly<{
|
||||||
|
onSelect: (option: TypeaheadOption | null) => void;
|
||||||
|
selectedValues?: Set<string>;
|
||||||
|
value?: TypeaheadOption | null;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default function ResumeLocationTypeahead({
|
||||||
|
onSelect,
|
||||||
|
selectedValues = new Set(),
|
||||||
|
value,
|
||||||
|
...props
|
||||||
|
}: Props) {
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
const countries = trpc.useQuery([
|
||||||
|
'locations.countries.list',
|
||||||
|
{
|
||||||
|
name: query,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const options = useMemo(() => {
|
||||||
|
const { data } = countries;
|
||||||
|
if (data == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
.map(({ id, name }) => ({
|
||||||
|
id,
|
||||||
|
label: name,
|
||||||
|
value: id,
|
||||||
|
}))
|
||||||
|
.filter((option) => !selectedValues.has(option.value));
|
||||||
|
}, [countries, selectedValues]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typeahead
|
||||||
|
label="Location"
|
||||||
|
noResultsMessage="No location found"
|
||||||
|
nullable={true}
|
||||||
|
options={options}
|
||||||
|
value={value}
|
||||||
|
onQueryChange={setQuery}
|
||||||
|
onSelect={onSelect}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
import type { ComponentProps } from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import type { TypeaheadOption } from '@tih/ui';
|
||||||
|
import { Typeahead } from '@tih/ui';
|
||||||
|
|
||||||
|
import { JobTitleLabels } from '~/components/shared/JobTitles';
|
||||||
|
|
||||||
|
type BaseProps = Pick<
|
||||||
|
ComponentProps<typeof Typeahead>,
|
||||||
|
| 'disabled'
|
||||||
|
| 'errorMessage'
|
||||||
|
| 'isLabelHidden'
|
||||||
|
| 'placeholder'
|
||||||
|
| 'required'
|
||||||
|
| 'textSize'
|
||||||
|
>;
|
||||||
|
|
||||||
|
type Props = BaseProps &
|
||||||
|
Readonly<{
|
||||||
|
onSelect: (option: TypeaheadOption | null) => void;
|
||||||
|
selectedValues?: Set<string>;
|
||||||
|
value?: TypeaheadOption | null;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default function ResumeRoleTypeahead({
|
||||||
|
onSelect,
|
||||||
|
selectedValues = new Set(),
|
||||||
|
value,
|
||||||
|
...props
|
||||||
|
}: Props) {
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
const options = Object.entries(JobTitleLabels)
|
||||||
|
.map(([slug, label]) => ({
|
||||||
|
id: slug,
|
||||||
|
label,
|
||||||
|
value: slug,
|
||||||
|
}))
|
||||||
|
.filter((option) => !selectedValues.has(option.value))
|
||||||
|
.filter(
|
||||||
|
({ label }) =>
|
||||||
|
label.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()) > -1,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typeahead
|
||||||
|
label="Role"
|
||||||
|
noResultsMessage="No available roles."
|
||||||
|
nullable={true}
|
||||||
|
options={options}
|
||||||
|
value={value}
|
||||||
|
onQueryChange={setQuery}
|
||||||
|
onSelect={onSelect}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in new issue