diff --git a/apps/portal/package.json b/apps/portal/package.json index cda1ec4e..fb8b60b9 100644 --- a/apps/portal/package.json +++ b/apps/portal/package.json @@ -43,7 +43,6 @@ "react-query": "^3.39.2", "read-excel-file": "^5.5.3", "superjson": "^1.10.0", - "xlsx": "^0.18.5", "unique-names-generator": "^4.7.1", "xlsx": "^0.18.5", "zod": "^3.18.0" diff --git a/apps/portal/src/pages/resumes/[resumeId].tsx b/apps/portal/src/pages/resumes/[resumeId].tsx index 1b7dea70..fc547bb4 100644 --- a/apps/portal/src/pages/resumes/[resumeId].tsx +++ b/apps/portal/src/pages/resumes/[resumeId].tsx @@ -15,7 +15,6 @@ import { PencilSquareIcon, StarIcon, } from '@heroicons/react/20/solid'; -import type { TypeaheadOption } from '@tih/ui'; import { Button, Spinner } from '@tih/ui'; import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics'; @@ -27,11 +26,9 @@ import loginPageHref from '~/components/shared/loginPageHref'; import { BROWSE_TABS_VALUES, - EXPERIENCES, getFilterLabel, + getTypeaheadOption, INITIAL_FILTER_STATE, - LOCATIONS, - ROLES, } from '~/utils/resumes/resumeFilters'; import { trpc } from '~/utils/trpc'; @@ -121,27 +118,24 @@ export default function ResumeReviewPage() { }; const onInfoTagClick = ({ - locationLabel, - experienceLabel, - roleLabel, + locationName, + locationValue, + experienceValue, + roleValue, }: { - experienceLabel?: string; - locationLabel?: string; - roleLabel?: string; + experienceValue?: string; + locationName?: string; + locationValue?: string; + roleValue?: string; }) => { - const getFilterValue = ( - label: string, - filterOptions: Array, - ) => filterOptions.find((option) => option.label === label)?.value; - router.push({ pathname: '/resumes', query: { currentPage: JSON.stringify(1), isFiltersOpen: JSON.stringify({ - experience: experienceLabel !== undefined, - location: locationLabel !== undefined, - role: roleLabel !== undefined, + experience: experienceValue !== undefined, + location: locationValue !== undefined, + role: roleValue !== undefined, }), searchValue: JSON.stringify(''), shortcutSelected: JSON.stringify('all'), @@ -149,14 +143,16 @@ export default function ResumeReviewPage() { tabsValue: JSON.stringify(BROWSE_TABS_VALUES.ALL), userFilters: JSON.stringify({ ...INITIAL_FILTER_STATE, - ...(locationLabel && { - location: [getFilterValue(locationLabel, LOCATIONS)], + ...(locationValue && { + location: [ + getTypeaheadOption('location', locationValue, locationName), + ], }), - ...(roleLabel && { - role: [getFilterValue(roleLabel, ROLES)], + ...(roleValue && { + role: [getTypeaheadOption('role', roleValue)], }), - ...(experienceLabel && { - experience: [getFilterValue(experienceLabel, EXPERIENCES)], + ...(experienceValue && { + experience: [getTypeaheadOption('experience', experienceValue)], }), }), }, @@ -318,92 +314,71 @@ export default function ResumeReviewPage() {
{renderReviewButton()}
- -
-
-
-
-
-
-
-
-
-
- {detailsQuery.data.additionalInfo && ( -
-
- )} - -
-
- -
-
-
- {renderReviewButton()} -
-
- - Reviews - -
+
+
+
+
+
+
+
+
+
+
{detailsQuery.data.additionalInfo && ( diff --git a/apps/portal/src/pages/resumes/index.tsx b/apps/portal/src/pages/resumes/index.tsx index 01c9a0ae..382425f5 100644 --- a/apps/portal/src/pages/resumes/index.tsx +++ b/apps/portal/src/pages/resumes/index.tsx @@ -30,21 +30,12 @@ import ResumeRoleTypeahead from '~/components/resumes/shared/ResumeRoleTypeahead import ResumeSignInButton from '~/components/resumes/shared/ResumeSignInButton'; import loginPageHref from '~/components/shared/loginPageHref'; -import type { - Filter, - FilterId, - FilterLabel, - Shortcut, -} from '~/utils/resumes/resumeFilters'; -import type { FilterState, SortOrder } from '~/utils/resumes/resumeFilters'; +import type { Filter, FilterId, Shortcut } from '~/utils/resumes/resumeFilters'; +import type { SortOrder } from '~/utils/resumes/resumeFilters'; import { BROWSE_TABS_VALUES, - EXPERIENCES, getFilterLabel, INITIAL_FILTER_STATE, - isInitialFilterState, - LOCATIONS, - ROLES, SHORTCUTS, SORT_OPTIONS, } from '~/utils/resumes/resumeFilters'; @@ -59,17 +50,14 @@ const filters: Array = [ { id: 'role', label: 'Role', - options: ROLES, }, { id: 'experience', label: 'Experience', - options: EXPERIENCES, }, { id: 'location', label: 'Location', - options: LOCATIONS, }, ]; @@ -84,20 +72,14 @@ const getLoggedOutText = (tabsValue: string) => { } }; -const getEmptyDataText = ( - tabsValue: string, - searchValue: string, - userFilters: FilterState, -) => { +const getEmptyDataText = (tabsValue: string, searchValue: string) => { if (searchValue.length > 0) { return 'Try tweaking your search text to see more resumes.'; } - if (!isInitialFilterState(userFilters)) { - return 'Try tweaking your filters to see more resumes.'; - } + switch (tabsValue) { case BROWSE_TABS_VALUES.ALL: - return "There's nothing to see here..."; + return 'Oops, there is no resumes to see here. Maybe try tweaking your filters to see more.'; case BROWSE_TABS_VALUES.STARRED: return 'You have not starred any resumes. Star one to see it here!'; case BROWSE_TABS_VALUES.MY: @@ -387,12 +369,16 @@ export default function ResumeHomePage() { } }; - const getFilterCount = (filter: FilterLabel, value: string) => { + const getFilterCount = (filterId: FilterId, value: string) => { const filterCountsData = getTabFilterCounts(); - if (!filterCountsData) { + if ( + filterCountsData === undefined || + filterCountsData[filterId] === undefined || + filterCountsData[filterId][value] === undefined + ) { return 0; } - return filterCountsData[filter][value]; + return filterCountsData[filterId][value]; }; return ( @@ -498,7 +484,7 @@ export default function ResumeHomePage() { {userFilters[filter.id].map((option) => (
+ className="flex items-center px-1 text-sm"> - ( - {getFilterCount( - filter.label, - option.label, - )} + ({getFilterCount(filter.id, option.value)} )
@@ -615,7 +597,7 @@ export default function ResumeHomePage() { {userFilters[filter.id].map((option) => (
+ className="flex items-center px-1 text-sm"> - ( - {getFilterCount(filter.label, option.label)} - ) + ({getFilterCount(filter.id, option.value)})
))} @@ -740,7 +720,7 @@ export default function ResumeHomePage() { height={196} width={196} /> - {getEmptyDataText(tabsValue, searchValue, userFilters)} + {getEmptyDataText(tabsValue, searchValue)}
) : (
diff --git a/apps/portal/src/server/router/resumes/resumes-resume-router.ts b/apps/portal/src/server/router/resumes/resumes-resume-router.ts index 2842b6cc..81c5625b 100644 --- a/apps/portal/src/server/router/resumes/resumes-resume-router.ts +++ b/apps/portal/src/server/router/resumes/resumes-resume-router.ts @@ -1,8 +1,6 @@ import { z } from 'zod'; import { Vote } from '@prisma/client'; -import { EXPERIENCES, LOCATIONS, ROLES } from '~/utils/resumes/resumeFilters'; - import { createRouter } from '../context'; import type { Resume } from '~/types/resume'; @@ -98,6 +96,7 @@ export const resumesRouter = createRouter() isResolved: r.isResolved, isStarredByUser: r.stars.length > 0, location: r.location.name, + locationId: r.locationId, numComments: r._count.comments, numStars: r._count.stars, role: r.role, @@ -127,20 +126,6 @@ export const resumesRouter = createRouter() roleCounts.map((rc) => [rc.role, rc._count._all]), ); - // Filter out roles with zero counts and map to object where key = role and value = 0 - const zeroRoleCounts = Object.fromEntries( - ROLES.filter((r) => !(r.value in mappedRoleCounts)).map((r) => [ - r.value, - 0, - ]), - ); - - // Combine to form singular role counts object - const processedRoleCounts = { - ...mappedRoleCounts, - ...zeroRoleCounts, - }; - const experienceCounts = await ctx.prisma.resumesResume.groupBy({ _count: { _all: true, @@ -156,15 +141,6 @@ export const resumesRouter = createRouter() const mappedExperienceCounts = Object.fromEntries( experienceCounts.map((ec) => [ec.experience, ec._count._all]), ); - const zeroExperienceCounts = Object.fromEntries( - EXPERIENCES.filter((e) => !(e.value in mappedExperienceCounts)).map( - (e) => [e.value, 0], - ), - ); - const processedExperienceCounts = { - ...mappedExperienceCounts, - ...zeroExperienceCounts, - }; const locationCounts = await ctx.prisma.resumesResume.groupBy({ _count: { @@ -181,21 +157,11 @@ export const resumesRouter = createRouter() const mappedLocationCounts = Object.fromEntries( locationCounts.map((lc) => [lc.locationId, lc._count._all]), ); - const zeroLocationCounts = Object.fromEntries( - LOCATIONS.filter((l) => !(l.value in mappedLocationCounts)).map((l) => [ - l.value, - 0, - ]), - ); - const processedLocationCounts = { - ...mappedLocationCounts, - ...zeroLocationCounts, - }; const filterCounts = { - Experience: processedExperienceCounts, - Location: processedLocationCounts, - Role: processedRoleCounts, + experience: mappedExperienceCounts, + location: mappedLocationCounts, + role: mappedRoleCounts, }; return { diff --git a/apps/portal/src/server/router/resumes/resumes-resume-user-router.ts b/apps/portal/src/server/router/resumes/resumes-resume-user-router.ts index 62016e55..aaeebeed 100644 --- a/apps/portal/src/server/router/resumes/resumes-resume-user-router.ts +++ b/apps/portal/src/server/router/resumes/resumes-resume-user-router.ts @@ -1,7 +1,5 @@ import { z } from 'zod'; -import { EXPERIENCES, LOCATIONS, ROLES } from '~/utils/resumes/resumeFilters'; - import { createProtectedRouter } from '../context'; import type { Resume } from '~/types/resume'; @@ -165,6 +163,7 @@ export const resumesResumeUserRouter = createProtectedRouter() isResolved: rs.resume.isResolved, isStarredByUser: true, location: rs.resume.location.name, + locationId: rs.resume.locationId, numComments: rs.resume._count.comments, numStars: rs.resume._count.stars, role: rs.resume.role, @@ -195,16 +194,6 @@ export const resumesResumeUserRouter = createProtectedRouter() const mappedRoleCounts = Object.fromEntries( roleCounts.map((rc) => [rc.role, rc._count._all]), ); - const zeroRoleCounts = Object.fromEntries( - ROLES.filter((r) => !(r.value in mappedRoleCounts)).map((r) => [ - r.value, - 0, - ]), - ); - const processedRoleCounts = { - ...mappedRoleCounts, - ...zeroRoleCounts, - }; const experienceCounts = await ctx.prisma.resumesResume.groupBy({ _count: { @@ -226,15 +215,6 @@ export const resumesResumeUserRouter = createProtectedRouter() const mappedExperienceCounts = Object.fromEntries( experienceCounts.map((ec) => [ec.experience, ec._count._all]), ); - const zeroExperienceCounts = Object.fromEntries( - EXPERIENCES.filter((e) => !(e.value in mappedExperienceCounts)).map( - (e) => [e.value, 0], - ), - ); - const processedExperienceCounts = { - ...mappedExperienceCounts, - ...zeroExperienceCounts, - }; const locationCounts = await ctx.prisma.resumesResume.groupBy({ _count: { @@ -256,21 +236,11 @@ export const resumesResumeUserRouter = createProtectedRouter() const mappedLocationCounts = Object.fromEntries( locationCounts.map((lc) => [lc.locationId, lc._count._all]), ); - const zeroLocationCounts = Object.fromEntries( - LOCATIONS.filter((l) => !(l.value in mappedLocationCounts)).map((l) => [ - l.value, - 0, - ]), - ); - const processedLocationCounts = { - ...mappedLocationCounts, - ...zeroLocationCounts, - }; const filterCounts = { - Experience: processedExperienceCounts, - Location: processedLocationCounts, - Role: processedRoleCounts, + experience: mappedExperienceCounts, + location: mappedLocationCounts, + role: mappedRoleCounts, }; return { filterCounts, mappedResumeData, totalRecords }; @@ -365,6 +335,7 @@ export const resumesResumeUserRouter = createProtectedRouter() isResolved: r.isResolved, isStarredByUser: r.stars.length > 0, location: r.location.name, + locationId: r.locationId, numComments: r._count.comments, numStars: r._count.stars, role: r.role, @@ -391,16 +362,6 @@ export const resumesResumeUserRouter = createProtectedRouter() const mappedRoleCounts = Object.fromEntries( roleCounts.map((rc) => [rc.role, rc._count._all]), ); - const zeroRoleCounts = Object.fromEntries( - ROLES.filter((r) => !(r.value in mappedRoleCounts)).map((r) => [ - r.value, - 0, - ]), - ); - const processedRoleCounts = { - ...mappedRoleCounts, - ...zeroRoleCounts, - }; const experienceCounts = await ctx.prisma.resumesResume.groupBy({ _count: { @@ -418,15 +379,6 @@ export const resumesResumeUserRouter = createProtectedRouter() const mappedExperienceCounts = Object.fromEntries( experienceCounts.map((ec) => [ec.experience, ec._count._all]), ); - const zeroExperienceCounts = Object.fromEntries( - EXPERIENCES.filter((e) => !(e.value in mappedExperienceCounts)).map( - (e) => [e.value, 0], - ), - ); - const processedExperienceCounts = { - ...mappedExperienceCounts, - ...zeroExperienceCounts, - }; const locationCounts = await ctx.prisma.resumesResume.groupBy({ _count: { @@ -444,21 +396,11 @@ export const resumesResumeUserRouter = createProtectedRouter() const mappedLocationCounts = Object.fromEntries( locationCounts.map((lc) => [lc.locationId, lc._count._all]), ); - const zeroLocationCounts = Object.fromEntries( - LOCATIONS.filter((l) => !(l.value in mappedLocationCounts)).map((l) => [ - l.value, - 0, - ]), - ); - const processedLocationCounts = { - ...mappedLocationCounts, - ...zeroLocationCounts, - }; const filterCounts = { - Experience: processedExperienceCounts, - Location: processedLocationCounts, - Role: processedRoleCounts, + experience: mappedExperienceCounts, + location: mappedLocationCounts, + role: mappedRoleCounts, }; return { filterCounts, mappedResumeData, totalRecords }; diff --git a/apps/portal/src/types/resume.d.ts b/apps/portal/src/types/resume.d.ts index c9a3a567..072ae090 100644 --- a/apps/portal/src/types/resume.d.ts +++ b/apps/portal/src/types/resume.d.ts @@ -6,6 +6,7 @@ export type Resume = { isResolved: boolean; isStarredByUser: boolean; location: string; + locationId: string; numComments: number; numStars: number; role: string; diff --git a/apps/portal/src/utils/resumes/resumeFilters.ts b/apps/portal/src/utils/resumes/resumeFilters.ts index b2d4bb0d..3b324a1d 100644 --- a/apps/portal/src/utils/resumes/resumeFilters.ts +++ b/apps/portal/src/utils/resumes/resumeFilters.ts @@ -4,7 +4,6 @@ import type { JobTitleType } from '~/components/shared/JobTitles'; import { JobTitleLabels } from '~/components/shared/JobTitles'; export type FilterId = 'experience' | 'location' | 'role'; -export type FilterLabel = 'Experience' | 'Location' | 'Role'; export type CustomFilter = { isUnreviewed: boolean; @@ -17,8 +16,7 @@ export type FilterOption = { export type Filter = { id: FilterId; - label: FilterLabel; - options: Array; + label: string; }; export type FilterState = CustomFilter & @@ -33,6 +31,31 @@ export type Shortcut = { sortOrder: SortOrder; }; +export const getTypeaheadOption = ( + filterId: FilterId, + filterValue: string, + locationName?: string, +) => { + switch (filterId) { + case 'experience': + return EXPERIENCES.find(({ value }) => value === filterValue); + case 'role': + return { + id: filterValue, + label: JobTitleLabels[filterValue as keyof typeof JobTitleLabels], + value: filterValue, + }; + case 'location': + return { + id: filterValue, + label: locationName ?? '', + value: filterValue, + }; + default: + break; + } +}; + export const BROWSE_TABS_VALUES = { ALL: 'all', MY: 'my', @@ -54,12 +77,20 @@ const INITIAL_ROLES_VALUES: Array = [ 'android-engineer', 'data-engineer', ]; +export const INITIAL_ROLES: Array = INITIAL_ROLES_VALUES.map( + (value) => + getTypeaheadOption('role', value) ?? { + id: value, + label: value, + value, + }, +); export const EXPERIENCES: Array = [ { id: 'internship', label: 'Internship', - value: 'Internship', + value: 'internship', }, { id: 'entry-level', @@ -69,21 +100,26 @@ export const EXPERIENCES: Array = [ { id: 'mid-level', label: 'Mid Level (3 - 5 years)', - value: 'Mid Level (3 - 5 years)', + value: 'mid-level', }, { - id: 'Senior Level (5+ years)', + id: 'senior-level', label: 'Senior Level (5+ years)', - value: 'Senior Level (5+ years)', + value: 'senior-level', }, ]; -export const LOCATIONS: Array = [ +export const INITIAL_LOCATIONS: Array = [ { id: '196', label: 'Singapore', value: '196', }, + { + id: '101', + label: 'India', + value: '101', + }, { id: '231', label: 'United States', @@ -109,8 +145,8 @@ export const LOCATIONS: Array = [ export const INITIAL_FILTER_STATE: FilterState = { experience: EXPERIENCES, isUnreviewed: true, - location: LOCATIONS, - role: ROLES, + location: INITIAL_LOCATIONS, + role: INITIAL_ROLES, }; export const SHORTCUTS: Array = [ @@ -119,7 +155,7 @@ export const SHORTCUTS: Array = [ ...INITIAL_FILTER_STATE, isUnreviewed: false, }, - name: 'All', + name: 'General', sortOrder: 'latest', }, { @@ -133,7 +169,13 @@ export const SHORTCUTS: Array = [ { filters: { ...INITIAL_FILTER_STATE, - experience: [], + experience: [ + { + id: 'entry-level', + label: 'Entry Level (0 - 2 years)', + value: 'entry-level', + }, + ], isUnreviewed: false, }, name: 'Fresh Grad', @@ -151,25 +193,22 @@ export const SHORTCUTS: Array = [ filters: { ...INITIAL_FILTER_STATE, isUnreviewed: false, - location: [], + location: [ + { + id: '231', + label: 'United States', + value: '231', + }, + ], }, name: 'US Only', sortOrder: 'latest', }, ]; -export const isInitialFilterState = (filters: FilterState) => - Object.keys(filters).every((filter) => { - if (!['experience', 'location', 'role'].includes(filter)) { - return true; - } - return INITIAL_FILTER_STATE[filter as FilterId].every((value) => - filters[filter as FilterId].includes(value), - ); - }); - +// We omit 'location' as its label should be fetched from the Country table. export const getFilterLabel = ( - filterId: FilterId | 'sort', + filterId: Omit, filterValue: SortOrder | string, ): string | undefined => { if (filterId === 'location') {