mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-28 20:52:00 +08:00
[resumes][feat] Add dynamic filters and unreviewed shortcut (#455)
This commit is contained in:
@ -118,7 +118,7 @@ export default function ResumeHomePage() {
|
|||||||
'',
|
'',
|
||||||
);
|
);
|
||||||
const [shortcutSelected, setShortcutSelected, isShortcutInit] =
|
const [shortcutSelected, setShortcutSelected, isShortcutInit] =
|
||||||
useSearchParams('shortcutSelected', 'All');
|
useSearchParams('shortcutSelected', 'Unreviewed');
|
||||||
const [currentPage, setCurrentPage, isCurrentPageInit] = useSearchParams(
|
const [currentPage, setCurrentPage, isCurrentPageInit] = useSearchParams(
|
||||||
'currentPage',
|
'currentPage',
|
||||||
1,
|
1,
|
||||||
@ -182,20 +182,13 @@ export default function ResumeHomePage() {
|
|||||||
isSearchOptionsInit,
|
isSearchOptionsInit,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const filterCountsQuery = trpc.useQuery(
|
|
||||||
['resumes.resume.getTotalFilterCounts'],
|
|
||||||
{
|
|
||||||
staleTime: STALE_TIME,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const allResumesQuery = trpc.useQuery(
|
const allResumesQuery = trpc.useQuery(
|
||||||
[
|
[
|
||||||
'resumes.resume.findAll',
|
'resumes.resume.findAll',
|
||||||
{
|
{
|
||||||
experienceFilters: userFilters.experience,
|
experienceFilters: userFilters.experience,
|
||||||
|
isUnreviewed: userFilters.isUnreviewed,
|
||||||
locationFilters: userFilters.location,
|
locationFilters: userFilters.location,
|
||||||
numComments: userFilters.numComments,
|
|
||||||
roleFilters: userFilters.role,
|
roleFilters: userFilters.role,
|
||||||
searchValue: useDebounceValue(searchValue, DEBOUNCE_DELAY),
|
searchValue: useDebounceValue(searchValue, DEBOUNCE_DELAY),
|
||||||
skip,
|
skip,
|
||||||
@ -213,8 +206,8 @@ export default function ResumeHomePage() {
|
|||||||
'resumes.resume.user.findUserStarred',
|
'resumes.resume.user.findUserStarred',
|
||||||
{
|
{
|
||||||
experienceFilters: userFilters.experience,
|
experienceFilters: userFilters.experience,
|
||||||
|
isUnreviewed: userFilters.isUnreviewed,
|
||||||
locationFilters: userFilters.location,
|
locationFilters: userFilters.location,
|
||||||
numComments: userFilters.numComments,
|
|
||||||
roleFilters: userFilters.role,
|
roleFilters: userFilters.role,
|
||||||
searchValue: useDebounceValue(searchValue, DEBOUNCE_DELAY),
|
searchValue: useDebounceValue(searchValue, DEBOUNCE_DELAY),
|
||||||
skip,
|
skip,
|
||||||
@ -233,8 +226,8 @@ export default function ResumeHomePage() {
|
|||||||
'resumes.resume.user.findUserCreated',
|
'resumes.resume.user.findUserCreated',
|
||||||
{
|
{
|
||||||
experienceFilters: userFilters.experience,
|
experienceFilters: userFilters.experience,
|
||||||
|
isUnreviewed: userFilters.isUnreviewed,
|
||||||
locationFilters: userFilters.location,
|
locationFilters: userFilters.location,
|
||||||
numComments: userFilters.numComments,
|
|
||||||
roleFilters: userFilters.role,
|
roleFilters: userFilters.role,
|
||||||
searchValue: useDebounceValue(searchValue, DEBOUNCE_DELAY),
|
searchValue: useDebounceValue(searchValue, DEBOUNCE_DELAY),
|
||||||
skip,
|
skip,
|
||||||
@ -249,14 +242,6 @@ export default function ResumeHomePage() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const getFilterCount = (filter: FilterLabel, value: string) => {
|
|
||||||
if (filterCountsQuery.isLoading) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const filterCountsData = filterCountsQuery.data!;
|
|
||||||
return filterCountsData[filter][value];
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmitResume = () => {
|
const onSubmitResume = () => {
|
||||||
if (sessionData === null) {
|
if (sessionData === null) {
|
||||||
router.push('/api/auth/signin');
|
router.push('/api/auth/signin');
|
||||||
@ -336,6 +321,18 @@ export default function ResumeHomePage() {
|
|||||||
starredResumesQuery.isFetching ||
|
starredResumesQuery.isFetching ||
|
||||||
myResumesQuery.isFetching;
|
myResumesQuery.isFetching;
|
||||||
|
|
||||||
|
const getTabFilterCounts = () => {
|
||||||
|
return getTabQueryData()?.filterCounts;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFilterCount = (filter: FilterLabel, value: string) => {
|
||||||
|
const filterCountsData = getTabFilterCounts();
|
||||||
|
if (!filterCountsData) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return filterCountsData[filter][value];
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
|
@ -11,8 +11,8 @@ export const resumesRouter = createRouter()
|
|||||||
.query('findAll', {
|
.query('findAll', {
|
||||||
input: z.object({
|
input: z.object({
|
||||||
experienceFilters: z.string().array(),
|
experienceFilters: z.string().array(),
|
||||||
|
isUnreviewed: z.boolean(),
|
||||||
locationFilters: z.string().array(),
|
locationFilters: z.string().array(),
|
||||||
numComments: z.number().optional(),
|
|
||||||
roleFilters: z.string().array(),
|
roleFilters: z.string().array(),
|
||||||
searchValue: z.string(),
|
searchValue: z.string(),
|
||||||
skip: z.number(),
|
skip: z.number(),
|
||||||
@ -25,7 +25,7 @@ export const resumesRouter = createRouter()
|
|||||||
locationFilters,
|
locationFilters,
|
||||||
experienceFilters,
|
experienceFilters,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
numComments,
|
isUnreviewed,
|
||||||
skip,
|
skip,
|
||||||
searchValue,
|
searchValue,
|
||||||
take,
|
take,
|
||||||
@ -33,12 +33,8 @@ export const resumesRouter = createRouter()
|
|||||||
const userId = ctx.session?.user?.id;
|
const userId = ctx.session?.user?.id;
|
||||||
const totalRecords = await ctx.prisma.resumesResume.count({
|
const totalRecords = await ctx.prisma.resumesResume.count({
|
||||||
where: {
|
where: {
|
||||||
...(numComments === 0 && {
|
|
||||||
comments: {
|
|
||||||
none: {},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
experience: { in: experienceFilters },
|
experience: { in: experienceFilters },
|
||||||
|
isResolved: isUnreviewed ? false : {},
|
||||||
location: { in: locationFilters },
|
location: { in: locationFilters },
|
||||||
role: { in: roleFilters },
|
role: { in: roleFilters },
|
||||||
title: { contains: searchValue, mode: 'insensitive' },
|
title: { contains: searchValue, mode: 'insensitive' },
|
||||||
@ -81,12 +77,8 @@ export const resumesRouter = createRouter()
|
|||||||
skip,
|
skip,
|
||||||
take,
|
take,
|
||||||
where: {
|
where: {
|
||||||
...(numComments === 0 && {
|
|
||||||
comments: {
|
|
||||||
none: {},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
experience: { in: experienceFilters },
|
experience: { in: experienceFilters },
|
||||||
|
isResolved: isUnreviewed ? false : {},
|
||||||
location: { in: locationFilters },
|
location: { in: locationFilters },
|
||||||
role: { in: roleFilters },
|
role: { in: roleFilters },
|
||||||
title: { contains: searchValue, mode: 'insensitive' },
|
title: { contains: searchValue, mode: 'insensitive' },
|
||||||
@ -110,7 +102,105 @@ export const resumesRouter = createRouter()
|
|||||||
};
|
};
|
||||||
return resume;
|
return resume;
|
||||||
});
|
});
|
||||||
return { mappedResumeData, totalRecords };
|
|
||||||
|
// Group by role and count, taking into account all role/experience/location/isUnreviewed filters and search value
|
||||||
|
const roleCounts = await ctx.prisma.resumesResume.groupBy({
|
||||||
|
_count: {
|
||||||
|
_all: true,
|
||||||
|
},
|
||||||
|
by: ['role'],
|
||||||
|
where: {
|
||||||
|
experience: { in: experienceFilters },
|
||||||
|
isResolved: isUnreviewed ? false : {},
|
||||||
|
location: { in: locationFilters },
|
||||||
|
role: { in: roleFilters },
|
||||||
|
title: { contains: searchValue, mode: 'insensitive' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Map all nonzero counts from array to object where key = role and value = count
|
||||||
|
const mappedRoleCounts = Object.fromEntries(
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
by: ['experience'],
|
||||||
|
where: {
|
||||||
|
experience: { in: experienceFilters },
|
||||||
|
isResolved: isUnreviewed ? false : {},
|
||||||
|
location: { in: locationFilters },
|
||||||
|
role: { in: roleFilters },
|
||||||
|
title: { contains: searchValue, mode: 'insensitive' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
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: {
|
||||||
|
_all: true,
|
||||||
|
},
|
||||||
|
by: ['location'],
|
||||||
|
where: {
|
||||||
|
experience: { in: experienceFilters },
|
||||||
|
isResolved: isUnreviewed ? false : {},
|
||||||
|
location: { in: locationFilters },
|
||||||
|
role: { in: roleFilters },
|
||||||
|
title: { contains: searchValue, mode: 'insensitive' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const mappedLocationCounts = Object.fromEntries(
|
||||||
|
locationCounts.map((lc) => [lc.location, 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
filterCounts,
|
||||||
|
mappedResumeData,
|
||||||
|
totalRecords,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.query('findOne', {
|
.query('findOne', {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { EXPERIENCES, LOCATIONS, ROLES } from '~/utils/resumes/resumeFilters';
|
||||||
|
|
||||||
import { createProtectedRouter } from '../context';
|
import { createProtectedRouter } from '../context';
|
||||||
|
|
||||||
import type { Resume } from '~/types/resume';
|
import type { Resume } from '~/types/resume';
|
||||||
@ -64,8 +66,8 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
.query('findUserStarred', {
|
.query('findUserStarred', {
|
||||||
input: z.object({
|
input: z.object({
|
||||||
experienceFilters: z.string().array(),
|
experienceFilters: z.string().array(),
|
||||||
|
isUnreviewed: z.boolean(),
|
||||||
locationFilters: z.string().array(),
|
locationFilters: z.string().array(),
|
||||||
numComments: z.number().optional(),
|
|
||||||
roleFilters: z.string().array(),
|
roleFilters: z.string().array(),
|
||||||
searchValue: z.string(),
|
searchValue: z.string(),
|
||||||
skip: z.number(),
|
skip: z.number(),
|
||||||
@ -80,19 +82,15 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
experienceFilters,
|
experienceFilters,
|
||||||
searchValue,
|
searchValue,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
numComments,
|
isUnreviewed,
|
||||||
skip,
|
skip,
|
||||||
take,
|
take,
|
||||||
} = input;
|
} = input;
|
||||||
const totalRecords = await ctx.prisma.resumesStar.count({
|
const totalRecords = await ctx.prisma.resumesStar.count({
|
||||||
where: {
|
where: {
|
||||||
resume: {
|
resume: {
|
||||||
...(numComments === 0 && {
|
|
||||||
comments: {
|
|
||||||
none: {},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
experience: { in: experienceFilters },
|
experience: { in: experienceFilters },
|
||||||
|
isResolved: isUnreviewed ? false : {},
|
||||||
location: { in: locationFilters },
|
location: { in: locationFilters },
|
||||||
role: { in: roleFilters },
|
role: { in: roleFilters },
|
||||||
title: { contains: searchValue, mode: 'insensitive' },
|
title: { contains: searchValue, mode: 'insensitive' },
|
||||||
@ -144,12 +142,8 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
take,
|
take,
|
||||||
where: {
|
where: {
|
||||||
resume: {
|
resume: {
|
||||||
...(numComments === 0 && {
|
|
||||||
comments: {
|
|
||||||
none: {},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
experience: { in: experienceFilters },
|
experience: { in: experienceFilters },
|
||||||
|
isResolved: isUnreviewed ? false : {},
|
||||||
location: { in: locationFilters },
|
location: { in: locationFilters },
|
||||||
role: { in: roleFilters },
|
role: { in: roleFilters },
|
||||||
title: { contains: searchValue, mode: 'insensitive' },
|
title: { contains: searchValue, mode: 'insensitive' },
|
||||||
@ -176,14 +170,116 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
};
|
};
|
||||||
return resume;
|
return resume;
|
||||||
});
|
});
|
||||||
return { mappedResumeData, totalRecords };
|
|
||||||
|
const roleCounts = await ctx.prisma.resumesResume.groupBy({
|
||||||
|
_count: {
|
||||||
|
_all: true,
|
||||||
|
},
|
||||||
|
by: ['role'],
|
||||||
|
where: {
|
||||||
|
experience: { in: experienceFilters },
|
||||||
|
isResolved: isUnreviewed ? false : {},
|
||||||
|
location: { in: locationFilters },
|
||||||
|
role: { in: roleFilters },
|
||||||
|
stars: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: { contains: searchValue, mode: 'insensitive' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
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: {
|
||||||
|
_all: true,
|
||||||
|
},
|
||||||
|
by: ['experience'],
|
||||||
|
where: {
|
||||||
|
experience: { in: experienceFilters },
|
||||||
|
isResolved: isUnreviewed ? false : {},
|
||||||
|
location: { in: locationFilters },
|
||||||
|
role: { in: roleFilters },
|
||||||
|
stars: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: { contains: searchValue, mode: 'insensitive' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
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: {
|
||||||
|
_all: true,
|
||||||
|
},
|
||||||
|
by: ['location'],
|
||||||
|
where: {
|
||||||
|
experience: { in: experienceFilters },
|
||||||
|
isResolved: isUnreviewed ? false : {},
|
||||||
|
location: { in: locationFilters },
|
||||||
|
role: { in: roleFilters },
|
||||||
|
stars: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: { contains: searchValue, mode: 'insensitive' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const mappedLocationCounts = Object.fromEntries(
|
||||||
|
locationCounts.map((lc) => [lc.location, 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { filterCounts, mappedResumeData, totalRecords };
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.query('findUserCreated', {
|
.query('findUserCreated', {
|
||||||
input: z.object({
|
input: z.object({
|
||||||
experienceFilters: z.string().array(),
|
experienceFilters: z.string().array(),
|
||||||
|
isUnreviewed: z.boolean(),
|
||||||
locationFilters: z.string().array(),
|
locationFilters: z.string().array(),
|
||||||
numComments: z.number().optional(),
|
|
||||||
roleFilters: z.string().array(),
|
roleFilters: z.string().array(),
|
||||||
searchValue: z.string(),
|
searchValue: z.string(),
|
||||||
skip: z.number(),
|
skip: z.number(),
|
||||||
@ -198,18 +294,14 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
experienceFilters,
|
experienceFilters,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
searchValue,
|
searchValue,
|
||||||
numComments,
|
isUnreviewed,
|
||||||
take,
|
take,
|
||||||
skip,
|
skip,
|
||||||
} = input;
|
} = input;
|
||||||
const totalRecords = await ctx.prisma.resumesResume.count({
|
const totalRecords = await ctx.prisma.resumesResume.count({
|
||||||
where: {
|
where: {
|
||||||
...(numComments === 0 && {
|
|
||||||
comments: {
|
|
||||||
none: {},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
experience: { in: experienceFilters },
|
experience: { in: experienceFilters },
|
||||||
|
isResolved: isUnreviewed ? false : {},
|
||||||
location: { in: locationFilters },
|
location: { in: locationFilters },
|
||||||
role: { in: roleFilters },
|
role: { in: roleFilters },
|
||||||
title: { contains: searchValue, mode: 'insensitive' },
|
title: { contains: searchValue, mode: 'insensitive' },
|
||||||
@ -250,12 +342,8 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
skip,
|
skip,
|
||||||
take,
|
take,
|
||||||
where: {
|
where: {
|
||||||
...(numComments === 0 && {
|
|
||||||
comments: {
|
|
||||||
none: {},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
experience: { in: experienceFilters },
|
experience: { in: experienceFilters },
|
||||||
|
isResolved: isUnreviewed ? false : {},
|
||||||
location: { in: locationFilters },
|
location: { in: locationFilters },
|
||||||
role: { in: roleFilters },
|
role: { in: roleFilters },
|
||||||
title: { contains: searchValue, mode: 'insensitive' },
|
title: { contains: searchValue, mode: 'insensitive' },
|
||||||
@ -280,6 +368,96 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
};
|
};
|
||||||
return resume;
|
return resume;
|
||||||
});
|
});
|
||||||
return { mappedResumeData, totalRecords };
|
|
||||||
|
const roleCounts = await ctx.prisma.resumesResume.groupBy({
|
||||||
|
_count: {
|
||||||
|
_all: true,
|
||||||
|
},
|
||||||
|
by: ['role'],
|
||||||
|
where: {
|
||||||
|
experience: { in: experienceFilters },
|
||||||
|
isResolved: isUnreviewed ? false : {},
|
||||||
|
location: { in: locationFilters },
|
||||||
|
role: { in: roleFilters },
|
||||||
|
title: { contains: searchValue, mode: 'insensitive' },
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
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: {
|
||||||
|
_all: true,
|
||||||
|
},
|
||||||
|
by: ['experience'],
|
||||||
|
where: {
|
||||||
|
experience: { in: experienceFilters },
|
||||||
|
isResolved: isUnreviewed ? false : {},
|
||||||
|
location: { in: locationFilters },
|
||||||
|
role: { in: roleFilters },
|
||||||
|
title: { contains: searchValue, mode: 'insensitive' },
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
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: {
|
||||||
|
_all: true,
|
||||||
|
},
|
||||||
|
by: ['location'],
|
||||||
|
where: {
|
||||||
|
experience: { in: experienceFilters },
|
||||||
|
isResolved: isUnreviewed ? false : {},
|
||||||
|
location: { in: locationFilters },
|
||||||
|
role: { in: roleFilters },
|
||||||
|
title: { contains: searchValue, mode: 'insensitive' },
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const mappedLocationCounts = Object.fromEntries(
|
||||||
|
locationCounts.map((lc) => [lc.location, 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { filterCounts, mappedResumeData, totalRecords };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@ export type FilterId = 'experience' | 'location' | 'role';
|
|||||||
export type FilterLabel = 'Experience' | 'Location' | 'Role';
|
export type FilterLabel = 'Experience' | 'Location' | 'Role';
|
||||||
|
|
||||||
export type CustomFilter = {
|
export type CustomFilter = {
|
||||||
numComments: number;
|
isUnreviewed: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RoleFilter =
|
export type RoleFilter =
|
||||||
@ -34,8 +34,7 @@ export type Filter = {
|
|||||||
options: Array<FilterOption<FilterValue>>;
|
options: Array<FilterOption<FilterValue>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FilterState = Partial<CustomFilter> &
|
export type FilterState = CustomFilter & Record<FilterId, Array<FilterValue>>;
|
||||||
Record<FilterId, Array<FilterValue>>;
|
|
||||||
|
|
||||||
export type SortOrder = 'latest' | 'mostComments' | 'popular';
|
export type SortOrder = 'latest' | 'mostComments' | 'popular';
|
||||||
|
|
||||||
@ -94,20 +93,24 @@ export const LOCATIONS: Array<FilterOption<LocationFilter>> = [
|
|||||||
|
|
||||||
export const INITIAL_FILTER_STATE: FilterState = {
|
export const INITIAL_FILTER_STATE: FilterState = {
|
||||||
experience: Object.values(EXPERIENCES).map(({ value }) => value),
|
experience: Object.values(EXPERIENCES).map(({ value }) => value),
|
||||||
|
isUnreviewed: true,
|
||||||
location: Object.values(LOCATIONS).map(({ value }) => value),
|
location: Object.values(LOCATIONS).map(({ value }) => value),
|
||||||
role: Object.values(ROLES).map(({ value }) => value),
|
role: Object.values(ROLES).map(({ value }) => value),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SHORTCUTS: Array<Shortcut> = [
|
export const SHORTCUTS: Array<Shortcut> = [
|
||||||
{
|
{
|
||||||
filters: INITIAL_FILTER_STATE,
|
filters: {
|
||||||
|
...INITIAL_FILTER_STATE,
|
||||||
|
isUnreviewed: false,
|
||||||
|
},
|
||||||
name: 'All',
|
name: 'All',
|
||||||
sortOrder: 'latest',
|
sortOrder: 'latest',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filters: {
|
filters: {
|
||||||
...INITIAL_FILTER_STATE,
|
...INITIAL_FILTER_STATE,
|
||||||
numComments: 0,
|
isUnreviewed: true,
|
||||||
},
|
},
|
||||||
name: 'Unreviewed',
|
name: 'Unreviewed',
|
||||||
sortOrder: 'latest',
|
sortOrder: 'latest',
|
||||||
@ -116,18 +119,23 @@ export const SHORTCUTS: Array<Shortcut> = [
|
|||||||
filters: {
|
filters: {
|
||||||
...INITIAL_FILTER_STATE,
|
...INITIAL_FILTER_STATE,
|
||||||
experience: ['Entry Level (0 - 2 years)'],
|
experience: ['Entry Level (0 - 2 years)'],
|
||||||
|
isUnreviewed: false,
|
||||||
},
|
},
|
||||||
name: 'Fresh Grad',
|
name: 'Fresh Grad',
|
||||||
sortOrder: 'latest',
|
sortOrder: 'latest',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filters: INITIAL_FILTER_STATE,
|
filters: {
|
||||||
|
...INITIAL_FILTER_STATE,
|
||||||
|
isUnreviewed: false,
|
||||||
|
},
|
||||||
name: 'Top 10',
|
name: 'Top 10',
|
||||||
sortOrder: 'popular',
|
sortOrder: 'popular',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filters: {
|
filters: {
|
||||||
...INITIAL_FILTER_STATE,
|
...INITIAL_FILTER_STATE,
|
||||||
|
isUnreviewed: false,
|
||||||
location: ['United States'],
|
location: ['United States'],
|
||||||
},
|
},
|
||||||
name: 'US Only',
|
name: 'US Only',
|
||||||
|
Reference in New Issue
Block a user