-
Notifications
You must be signed in to change notification settings - Fork 3
fix: 필터별 전체 지원자 수 조회 수정 #637
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,69 +1,26 @@ | ||
| import { type UseQueryOptions, type UseQueryResult, useQuery } from "@tanstack/react-query"; | ||
| import type { AxiosError, AxiosResponse } from "axios"; | ||
| import { useMemo } from "react"; | ||
|
|
||
| import useAuthStore from "@/lib/zustand/useAuthStore"; | ||
| import type { ApplicationListResponse } from "@/types/application"; | ||
| import { QueryKeys } from "../queryKeys"; | ||
| import { universitiesApi } from "../universities/api"; | ||
| import { ApplicationsQueryKeys, applicationsApi } from "./api"; | ||
| import { filterApplicationsByHomeUniversityScope } from "./homeUniversityScope"; | ||
| import { type ApplicantsSearchParams, applicationsApi } from "./api"; | ||
|
|
||
| type UseGetApplicationsListOptions = Omit< | ||
| UseQueryOptions<AxiosResponse<ApplicationListResponse>, AxiosError<{ message: string }>, ApplicationListResponse>, | ||
| "queryKey" | "queryFn" | ||
| >; | ||
|
|
||
| /** | ||
| * 지원 기간에는 지원한 대학만, 기간 종료 후에는 소속 대학의 전체 현황을 공개한다. | ||
| * 전체 현황을 다시 공개할 때 반환값을 "applications"로 변경한다. | ||
| */ | ||
| type ApplicationStatusEndpoint = "applications" | "competitors"; | ||
|
|
||
| const getApplicationStatusEndpoint = (): ApplicationStatusEndpoint => "competitors"; | ||
|
|
||
| const applicationStatusEndpoint = getApplicationStatusEndpoint(); | ||
| const shouldFilterByHomeUniversity = applicationStatusEndpoint === "applications"; | ||
| const applicationStatusQueryFn = | ||
| applicationStatusEndpoint === "competitors" ? applicationsApi.getCompetitors : applicationsApi.getApplicationsList; | ||
|
|
||
| /** | ||
| * @description 내가 지원한 대학의 지원자 현황 조회 훅 | ||
| */ | ||
| const useGetApplicationsList = ( | ||
| params?: ApplicantsSearchParams, | ||
| props?: UseGetApplicationsListOptions, | ||
| ): UseQueryResult<ApplicationListResponse, AxiosError<{ message: string }>> => { | ||
| const homeUniversityId = useAuthStore((state) => state.homeUniversityId); | ||
|
|
||
| const applicationsQuery = useQuery({ | ||
| queryKey: [ApplicationsQueryKeys.competitorsApplicationList], | ||
| queryFn: applicationStatusQueryFn, | ||
| return useQuery({ | ||
| queryKey: [QueryKeys.applications.applicants, params], | ||
| queryFn: () => applicationsApi.getApplicationsList(params), | ||
| staleTime: 1000 * 60 * 5, // 5분간 캐시 | ||
| select: (response) => response.data, | ||
| ...props, | ||
| }); | ||
|
|
||
| // GET /applications를 사용할 때만 소속 대학의 파견학교 목록으로 범위를 제한한다. | ||
| const { data: scopedUniversities } = useQuery({ | ||
| queryKey: [QueryKeys.universities.searchText, { homeUniversityId }], | ||
| queryFn: () => universitiesApi.getSearchText({ value: "", homeUniversityId: homeUniversityId ?? undefined }), | ||
| enabled: shouldFilterByHomeUniversity && homeUniversityId !== null, | ||
| staleTime: 1000 * 60 * 5, | ||
| select: (response) => response.univApplyInfoPreviews, | ||
| }); | ||
|
|
||
| const scopedData = useMemo(() => { | ||
| if (!applicationsQuery.data || !shouldFilterByHomeUniversity || homeUniversityId === null) { | ||
| return applicationsQuery.data; | ||
| } | ||
|
|
||
| return filterApplicationsByHomeUniversityScope(applicationsQuery.data, scopedUniversities); | ||
| }, [applicationsQuery.data, homeUniversityId, scopedUniversities]); | ||
|
|
||
| return { ...applicationsQuery, data: scopedData } as UseQueryResult< | ||
| ApplicationListResponse, | ||
| AxiosError<{ message: string }> | ||
| >; | ||
| }; | ||
|
|
||
| export default useGetApplicationsList; | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,14 +5,14 @@ import clsx from "clsx"; | |
| import Link from "next/link"; | ||
| import { useRouter } from "next/navigation"; | ||
| import { type ReactNode, useEffect, useMemo, useState } from "react"; | ||
| import { useGetApplicationsList } from "@/apis/applications"; | ||
| import { useGetApplicationsList, useGetCompetitors } from "@/apis/applications"; | ||
| import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage"; | ||
| import { DEFAULT_MAX_CHOICE_COUNT, getHomeUniversityById, REGIONS_KO } from "@/constants/university"; | ||
| import { SKIP_GLOBAL_ERROR_TOAST_META } from "@/lib/react-query/errorToastMeta"; | ||
| import useAuthStore from "@/lib/zustand/useAuthStore"; | ||
| import { IconExpandMoreFilled } from "@/public/svgs/community"; | ||
| import type { Applicant, ScoreSheet as ScoreSheetType } from "@/types/application"; | ||
| import type { RegionKo } from "@/types/university"; | ||
| import { type RegionKo, regionMapping } from "@/types/university"; | ||
| import { getApplicationDetailHref, MobileScoreSheet, ScoreSheetLogo } from "../ScoreSheet"; | ||
|
|
||
| type ApplicationAccessErrorCode = "APPLICATION_NOT_FOUND" | "APPLICATION_NOT_APPROVED"; | ||
|
|
@@ -46,7 +46,7 @@ type ScorePageViewProps = { | |
| displayedScoreSheets: ScoreSheetType[]; | ||
| totalUniversityCount: number; | ||
| applicantUniversityCount: number; | ||
| participantCount: number; | ||
| participantCount: number | null; | ||
| scope: ApplicantScope; | ||
| regionFilter: RegionKo | ""; | ||
| sortMode: ScoreSort; | ||
|
|
@@ -68,15 +68,20 @@ const ApprovedApplicationStatusPage = () => { | |
| () => Array.from({ length: maxChoiceCount }, () => [] as ScoreSheetType[]), | ||
| [maxChoiceCount], | ||
| ); | ||
| const applicantSearchParams = useMemo( | ||
| () => ({ region: regionFilter ? (regionMapping[regionFilter] ?? undefined) : undefined }), | ||
| [regionFilter], | ||
| ); | ||
| const { | ||
| data: scoreResponseData, | ||
| isError, | ||
| isLoading, | ||
| error, | ||
| refetch, | ||
| } = useGetApplicationsList({ | ||
| } = useGetCompetitors({ | ||
| meta: SKIP_GLOBAL_ERROR_TOAST_META, | ||
| }); | ||
| const { data: applicantResponseData } = useGetApplicationsList(applicantSearchParams); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 1. 지원자 조회 실패 상태를 처리하세요. 경쟁자 조회가 성공하고 지원자 조회가 실패하면, Line 84는 오류 상태와 재시도 함수를 버립니다. 이 경우 🤖 Prompt for AI AgentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If Useful? React with 👍 / 👎. |
||
| const scoreChoices = scoreResponseData?.choices ?? emptyChoices; | ||
| const isApplicationMissingOrUnapproved = isApplicationAccessError(error); | ||
|
|
||
|
|
@@ -87,7 +92,10 @@ const ApprovedApplicationStatusPage = () => { | |
| ); | ||
| const totalUniversityCount = allScoreSheets.length; | ||
| const applicantUniversityCount = allScoreSheets.filter((scoreSheet) => scoreSheet.applicants.length > 0).length; | ||
| const participantCount = useMemo(() => getParticipantCount(allScoreSheets), [allScoreSheets]); | ||
| const participantCount = useMemo( | ||
| () => (applicantResponseData ? getParticipantCount(applicantResponseData.choices.flat()) : null), | ||
| [applicantResponseData], | ||
| ); | ||
|
|
||
| const displayedScoreSheets = useMemo(() => { | ||
| let result = | ||
|
|
@@ -243,15 +251,19 @@ const AppliedUniversityRow = ({ preference, scoreSheet }: AppliedUniversity) => | |
| ); | ||
| }; | ||
|
|
||
| const ParticipantBanner = ({ participantCount }: { participantCount: number }) => ( | ||
| const ParticipantBanner = ({ participantCount }: { participantCount: number | null }) => ( | ||
| <section className="mt-6 rounded-lg bg-secondary-100 px-5 py-3"> | ||
| <div className="flex items-center gap-4"> | ||
| <div className="flex size-10 items-center justify-center text-[34px]" aria-hidden> | ||
| 🔥 | ||
| </div> | ||
| <div className="min-w-0 text-k-800"> | ||
| <p className="typo-regular-4">솔리드 커넥션과 함께하고 있어요</p> | ||
| <p className="mt-0.5 typo-sb-9">총 {participantCount}명이 성적 공유 참여중!</p> | ||
| <p className="mt-0.5 typo-sb-9"> | ||
| {participantCount === null | ||
| ? "지원자 수를 불러오는 중이에요." | ||
| : `총 ${participantCount}명이 성적 공유 참여중!`} | ||
| </p> | ||
| </div> | ||
| </div> | ||
| </section> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a user opens a university shared by multiple home institutions, this hook now returns the raw global
/applicationsresponse because the home-university filtering was removed.ApplicationUniversityDetailContentstill callsuseGetApplicationsList()and merges entries solely bykoreanName, so the detail page can combine and display applicants outside the competitor scope shown on the preceding page. Migrate that remaining consumer touseGetCompetitors()or otherwise retain home-university scoping for detail data.Useful? React with 👍 / 👎.