mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-27 20:22:33 +08:00
[resumes][feat] Add filter counts on browse page (#446)
This commit is contained in:
@ -24,7 +24,12 @@ import ResumeFilterPill from '~/components/resumes/browse/ResumeFilterPill';
|
|||||||
import ResumeListItems from '~/components/resumes/browse/ResumeListItems';
|
import ResumeListItems from '~/components/resumes/browse/ResumeListItems';
|
||||||
import ResumeSignInButton from '~/components/resumes/shared/ResumeSignInButton';
|
import ResumeSignInButton from '~/components/resumes/shared/ResumeSignInButton';
|
||||||
|
|
||||||
import type { Filter, FilterId, Shortcut } from '~/utils/resumes/resumeFilters';
|
import type {
|
||||||
|
Filter,
|
||||||
|
FilterId,
|
||||||
|
FilterLabel,
|
||||||
|
Shortcut,
|
||||||
|
} from '~/utils/resumes/resumeFilters';
|
||||||
import {
|
import {
|
||||||
BROWSE_TABS_VALUES,
|
BROWSE_TABS_VALUES,
|
||||||
EXPERIENCES,
|
EXPERIENCES,
|
||||||
@ -177,6 +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',
|
||||||
@ -237,6 +249,14 @@ 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');
|
||||||
@ -495,7 +515,10 @@ export default function ResumeHomePage() {
|
|||||||
key={option.value}
|
key={option.value}
|
||||||
className="[&>div>div:nth-child(1)>input]:text-primary-600 [&>div>div:nth-child(1)>input]:ring-primary-500 px-1 [&>div>div:nth-child(2)>label]:font-normal">
|
className="[&>div>div:nth-child(1)>input]:text-primary-600 [&>div>div:nth-child(1)>input]:ring-primary-500 px-1 [&>div>div:nth-child(2)>label]:font-normal">
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
label={option.label}
|
label={`${option.label} (${getFilterCount(
|
||||||
|
filter.label,
|
||||||
|
option.label,
|
||||||
|
)})`}
|
||||||
value={userFilters[filter.id].includes(
|
value={userFilters[filter.id].includes(
|
||||||
option.value,
|
option.value,
|
||||||
)}
|
)}
|
||||||
|
@ -168,6 +168,9 @@ export default function SubmitResumeForm({
|
|||||||
onSuccess() {
|
onSuccess() {
|
||||||
if (isNewForm) {
|
if (isNewForm) {
|
||||||
trpcContext.invalidateQueries('resumes.resume.findAll');
|
trpcContext.invalidateQueries('resumes.resume.findAll');
|
||||||
|
trpcContext.invalidateQueries(
|
||||||
|
'resumes.resume.getTotalFilterCounts',
|
||||||
|
);
|
||||||
router.push('/resumes/browse');
|
router.push('/resumes/browse');
|
||||||
} else {
|
} else {
|
||||||
onClose();
|
onClose();
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { Vote } from '@prisma/client';
|
import { Vote } from '@prisma/client';
|
||||||
|
|
||||||
|
import { EXPERIENCES, LOCATIONS, ROLES } from '~/utils/resumes/resumeFilters';
|
||||||
|
|
||||||
import { createRouter } from '../context';
|
import { createRouter } from '../context';
|
||||||
|
|
||||||
import type { Resume } from '~/types/resume';
|
import type { Resume } from '~/types/resume';
|
||||||
@ -251,4 +253,72 @@ export const resumesRouter = createRouter()
|
|||||||
|
|
||||||
return topUpvotedCommentCount;
|
return topUpvotedCommentCount;
|
||||||
},
|
},
|
||||||
|
})
|
||||||
|
.query('getTotalFilterCounts', {
|
||||||
|
async resolve({ ctx }) {
|
||||||
|
const roleCounts = await ctx.prisma.resumesResume.groupBy({
|
||||||
|
_count: {
|
||||||
|
_all: true,
|
||||||
|
},
|
||||||
|
by: ['role'],
|
||||||
|
});
|
||||||
|
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'],
|
||||||
|
});
|
||||||
|
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'],
|
||||||
|
});
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
Experience: processedExperienceCounts,
|
||||||
|
Location: processedLocationCounts,
|
||||||
|
Role: processedRoleCounts,
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export type FilterId = 'experience' | 'location' | 'role';
|
export type FilterId = 'experience' | 'location' | 'role';
|
||||||
|
export type FilterLabel = 'Experience' | 'Location' | 'Role';
|
||||||
|
|
||||||
export type CustomFilter = {
|
export type CustomFilter = {
|
||||||
numComments: number;
|
numComments: number;
|
||||||
@ -29,7 +30,7 @@ export type FilterOption<T> = {
|
|||||||
|
|
||||||
export type Filter = {
|
export type Filter = {
|
||||||
id: FilterId;
|
id: FilterId;
|
||||||
label: string;
|
label: FilterLabel;
|
||||||
options: Array<FilterOption<FilterValue>>;
|
options: Array<FilterOption<FilterValue>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user