From 229a3419ca495fc9473baf99f73e1bc64796f4e8 Mon Sep 17 00:00:00 2001 From: lepitaaar Date: Mon, 28 Jul 2025 19:01:16 +0900 Subject: [PATCH 01/16] =?UTF-8?q?feat:=20=EC=A7=80=EC=9B=90=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=ED=83=AD=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EC=82=AC=EC=9D=B4=EB=93=9C=EB=B0=94=20=EB=9D=BC=EB=B2=A8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 5 +++++ frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4807b07e3..937e72eba 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -17,6 +17,7 @@ import LoginTab from '@/pages/AdminPage/auth/LoginTab/LoginTab'; import PrivateRoute from '@/pages/AdminPage/auth/PrivateRoute/PrivateRoute'; import PhotoEditTab from '@/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab'; import ApplicationFormPage from './pages/ApplicationFormPage/ApplicationFormPage'; +import ApplicantsTab from './pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab'; const queryClient = new QueryClient(); @@ -76,6 +77,10 @@ const App = () => { path='application-edit' element={} /> + } + /> diff --git a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx index f160cd25a..c92bdef2b 100644 --- a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx +++ b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx @@ -14,7 +14,8 @@ const tabs = [ { label: '기본 정보 수정', path: '/admin/club-info' }, { label: '모집 정보 수정', path: '/admin/recruit-edit' }, { label: '활동 사진 수정', path: '/admin/photo-edit' }, - { label: '지원 관리', path: '/admin/application-edit' }, + { label: '지원서 관리', path: '/admin/application-edit' }, + { label: '지원 관리', path: '/admin/applicants' }, { label: '계정 관리', path: '/admin/account-edit' }, ]; From b29c835b5061ac3cddf9c8f1a5ab7bd5d92b9076 Mon Sep 17 00:00:00 2001 From: lepitaaar Date: Mon, 28 Jul 2025 19:01:29 +0900 Subject: [PATCH 02/16] =?UTF-8?q?feat:=20=ED=81=B4=EB=9F=BD=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90=EC=9E=90=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=EB=8A=94=20API=20=EB=B0=8F=20=ED=9B=85=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/apis/applicants/getClubApplicants.ts | 20 +++++++++++++++++++ .../queries/applicants/useGetApplicants.ts | 10 ++++++++++ 2 files changed, 30 insertions(+) create mode 100644 frontend/src/apis/applicants/getClubApplicants.ts create mode 100644 frontend/src/hooks/queries/applicants/useGetApplicants.ts diff --git a/frontend/src/apis/applicants/getClubApplicants.ts b/frontend/src/apis/applicants/getClubApplicants.ts new file mode 100644 index 000000000..4d71c43ec --- /dev/null +++ b/frontend/src/apis/applicants/getClubApplicants.ts @@ -0,0 +1,20 @@ +import API_BASE_URL from '@/constants/api'; +import { secureFetch } from '../auth/secureFetch'; + +const getClubApplicants = async (clubId: string) => { + try { + const response = await secureFetch(`${API_BASE_URL}/api/club/${clubId}/apply/info`); + if (!response.ok) { + console.error(`Failed to fetch: ${response.statusText}`) + throw new Error((await response.json()).message); + } + + const result = await response.json(); + return result.data; + } catch (error) { + console.error('Error fetching club applicants', error); + throw error; + } +}; + +export default getClubApplicants; diff --git a/frontend/src/hooks/queries/applicants/useGetApplicants.ts b/frontend/src/hooks/queries/applicants/useGetApplicants.ts new file mode 100644 index 000000000..38c7da2f6 --- /dev/null +++ b/frontend/src/hooks/queries/applicants/useGetApplicants.ts @@ -0,0 +1,10 @@ +import getClubApplicants from "@/apis/applicants/getClubApplicants" +import { useQuery } from "@tanstack/react-query" + +export const useGetApplicants = (clubId: string) => { + return useQuery({ + queryKey: ['clubApplicants', clubId], + queryFn: () => getClubApplicants(clubId), + retry: false, + }) +} \ No newline at end of file From 78b34024287afb5cdf40cf68d27b7bbba2ebe472 Mon Sep 17 00:00:00 2001 From: lepitaaar Date: Mon, 28 Jul 2025 19:01:37 +0900 Subject: [PATCH 03/16] =?UTF-8?q?feat:=20=EC=A7=80=EC=9B=90=EC=9E=90=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=B0=8F=20=EC=A0=95=EB=B3=B4=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=A0=95=EC=9D=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/types/applicants.ts | 47 ++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 frontend/src/types/applicants.ts diff --git a/frontend/src/types/applicants.ts b/frontend/src/types/applicants.ts new file mode 100644 index 000000000..8b047db54 --- /dev/null +++ b/frontend/src/types/applicants.ts @@ -0,0 +1,47 @@ +import { AnswerItem } from "./application"; + +export enum ApplicationStatus { + DRAFT = 'DRAFT', // 작성 중 + SUBMITTED = 'SUBMITTED', // 제출 완료 + SCREENING = 'SCREENING', // 서류 심사 중 + SCREENING_PASSED = 'SCREENING_PASSED', // 서류 통과 + SCREENING_FAILED = 'SCREENING_FAILED', // 서류 탈락 + INTERVIEW_SCHEDULED = 'INTERVIEW_SCHEDULED', // 면접 일정 확정 + INTERVIEW_IN_PROGRESS = 'INTERVIEW_IN_PROGRESS', // 면접 진행 중 + INTERVIEW_PASSED = 'INTERVIEW_PASSED', // 면접 통과 + INTERVIEW_FAILED = 'INTERVIEW_FAILED', // 면접 탈락 + OFFERED = 'OFFERED', // 최종 합격 제안 + ACCEPTED = 'ACCEPTED', // 제안 수락 + DECLINED = 'DECLINED', // 제안 거절 + CANCELED_BY_APPLICANT = 'CANCELED_BY_APPLICANT', // 지원자 자진 철회 +} + +export const APPLICATION_STATUS_KR: Record = { + [ApplicationStatus.DRAFT]: '작성 중', + [ApplicationStatus.SUBMITTED]: '제출 완료', + [ApplicationStatus.SCREENING]: '서류 심사 중', + [ApplicationStatus.SCREENING_PASSED]: '서류 통과', + [ApplicationStatus.SCREENING_FAILED]: '서류 탈락', + [ApplicationStatus.INTERVIEW_SCHEDULED]: '면접 일정 확정', + [ApplicationStatus.INTERVIEW_IN_PROGRESS]: '면접 진행 중', + [ApplicationStatus.INTERVIEW_PASSED]: '면접 통과', + [ApplicationStatus.INTERVIEW_FAILED]: '면접 탈락', + [ApplicationStatus.OFFERED]: '최종 합격 제안', + [ApplicationStatus.ACCEPTED]: '제안 수락', + [ApplicationStatus.DECLINED]: '제안 거절', + [ApplicationStatus.CANCELED_BY_APPLICANT]: '지원자 자진 철회', +}; + +export interface ApplicantsInfo { + total: number; + reviewRequired: number; + scheduledInterview: number; + accepted: number; + applicants: Applicant[] +} + +export interface Applicant { + questionId: number; + status: ApplicationStatus; + answers: AnswerItem[] +} \ No newline at end of file From 73c3381eac5c135b870b67cd7f502801d168ac4d Mon Sep 17 00:00:00 2001 From: lepitaaar Date: Mon, 28 Jul 2025 19:01:44 +0900 Subject: [PATCH 04/16] =?UTF-8?q?feat:=20=EC=A7=80=EC=9B=90=EC=9E=90=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EB=B0=8F=20=ED=86=B5=EA=B3=84=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20UI=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ApplicantsTab/ApplicantsTab.styles.tsx | 133 ++++++++++++++++++ .../tabs/ApplicantsTab/ApplicantsTab.tsx | 110 +++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx create mode 100644 frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx new file mode 100644 index 000000000..d624134d6 --- /dev/null +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx @@ -0,0 +1,133 @@ +import styled from "styled-components"; + +export const TopBar = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 32px; +`; + +export const PageTitle = styled.h1` + font-size: 28px; + font-weight: 700; + margin: 0; +`; + +export const SemesterSelect = styled.select` + padding: 8px 16px; + border-radius: 8px; + border: 1px solid #ddd; + background: #fff; + font-size: 16px; +`; + +// 통계 카드 스타일 +export const SummaryContainer = styled.div` + display: flex; + gap: 12px; + margin-bottom: 40px; +`; + +export const SummaryCard = styled.div<{ bg: string }>` + flex: 1; + background: ${({ bg }) => bg}; + border-radius: 10px; + padding: 32px 0; + text-align: center; +`; + +export const SummaryLabel = styled.div` + font-size: 18px; + color: #888; +`; + +export const SummaryValue = styled.div` + font-size: 40px; + font-weight: 700; + margin-top: 8px; + span { + font-size: 20px; + font-weight: 400; + margin-left: 2px; + } +`; + +// 지원자 목록 스타일 +export const ApplicantsSection = styled.div``; + +export const Title = styled.h2` + font-size: 28px; + font-weight: 700; + margin-bottom: 24px; +`; + +export const FilterRow = styled.div` + display: flex; + align-items: center; + margin-bottom: 16px; + gap: 8px; +`; + +export const FilterSelect = styled.select` + padding: 8px 16px; + border-radius: 8px; + border: 1px solid #ddd; + background: #fff; + font-size: 16px; +`; + +export const SearchInput = styled.input` + margin-left: auto; + padding: 8px 16px; + border-radius: 8px; + border: 1px solid #ddd; + width: 240px; + font-size: 16px; +`; + +export const Table = styled.table` + width: 100%; + border-collapse: collapse; + background: #fff; +`; + +export const Thead = styled.thead` + background: #fafafa; +`; + +export const Th = styled.th` + padding: 12px 8px; + font-size: 16px; + font-weight: 500; + color: #888; + text-align: left; +`; + +export const Tr = styled.tr` + border-bottom: 1px solid #f0f0f0; + &:hover { + background: #f7faff; + } +`; + +export const Td = styled.td` + padding: 12px 8px; + font-size: 16px; +`; + +export const StatusBadge = styled.span<{ status: string }>` + display: inline-block; + border-radius: 8px; + padding: 4px 12px; + font-weight: 500; + font-size: 15px; + background: ${({ status }) => + status === "서류검토" + ? "#E6F4FB" + : status === "면접예정" + ? "#E6FBF0" + : status === "합격" + ? "#F5F5F5" + : "#eee"}; + color: ${({ status }) => (status === "합격" ? "#888" : "#222")}; +`; \ No newline at end of file diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx new file mode 100644 index 000000000..1c554b8a0 --- /dev/null +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx @@ -0,0 +1,110 @@ +import { useAdminClubContext } from "@/context/AdminClubContext"; +import { useGetApplicants } from "@/hooks/queries/applicants/useGetApplicants"; +import { Applicant, ApplicantsInfo, APPLICATION_STATUS_KR } from "@/types/applicants"; +import React, { useEffect, useState } from "react"; +import { ApplicantsSection, FilterRow, FilterSelect, PageTitle, SearchInput, StatusBadge, SummaryCard, SummaryContainer, SummaryLabel, SummaryValue, Table, Td, Th, Thead, Title, TopBar, Tr } from "./ApplicantsTab.styles"; + +const ApplicantsTab = () => { + const { clubId } = useAdminClubContext(); + if (!clubId) return null; + + const { data, isLoading, isError } = useGetApplicants(clubId); + const [applicantsData, setApplicantsData] = useState(); + + useEffect(() => { + if (data) { + setApplicantsData(data); + } + }, [data]); + + return ( + <> + {/* 상단 타이틀/학기선택 */} + + 지원 현황 + {/* */} + {/* */} + {/* ...다른 학기 */} + {/* */} + + + {/* 통계 카드 */} + + + 전체 지원자 수 + + {applicantsData?.total} + + + + + 서류 검토 필요 + + {applicantsData?.reviewRequired} + + + + + 면접 예정 + + {applicantsData?.scheduledInterview} + + + + + 합격 + + {applicantsData?.accepted} + + + + + + {/* 지원자 목록 */} + + 지원자 목록 + + + + + + + + + + + + + + + + + + + + + { + applicantsData?.applicants.map((item: Applicant, index: number) => ( + + + + + + + + )) + } + +
현재상태이름메모제출날짜
+ + + {APPLICATION_STATUS_KR[item.status]} + {item.answers[0].value} +

메모

+
날짜
+
+ + ); +}; + +export default ApplicantsTab; \ No newline at end of file From 808d00ecc572dd390aebb15de0b3b83bc20e6389 Mon Sep 17 00:00:00 2001 From: lepitaaar Date: Wed, 30 Jul 2025 18:15:23 +0900 Subject: [PATCH 05/16] =?UTF-8?q?feat:=20=EC=A7=80=EC=9B=90=EC=9E=90=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EA=B4=80=EB=A6=AC=EC=9E=90=20=ED=81=B4?= =?UTF-8?q?=EB=9F=BD=20=EC=BB=A8=ED=85=8D=EC=8A=A4=ED=8A=B8=EC=97=90=20?= =?UTF-8?q?=EC=A7=80=EC=9B=90=EC=9E=90=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EA=B4=80=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 5 +++++ frontend/src/context/AdminClubContext.tsx | 8 ++++++-- .../AdminPage/auth/PrivateRoute/PrivateRoute.tsx | 14 ++++++++++++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 937e72eba..71c0e5e0d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -18,6 +18,7 @@ import PrivateRoute from '@/pages/AdminPage/auth/PrivateRoute/PrivateRoute'; import PhotoEditTab from '@/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab'; import ApplicationFormPage from './pages/ApplicationFormPage/ApplicationFormPage'; import ApplicantsTab from './pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab'; +import ApplicantDetailPage from './pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage'; const queryClient = new QueryClient(); @@ -81,6 +82,10 @@ const App = () => { path='applicants' element={} /> + } + /> diff --git a/frontend/src/context/AdminClubContext.tsx b/frontend/src/context/AdminClubContext.tsx index 23c526eb6..aaf87f2cc 100644 --- a/frontend/src/context/AdminClubContext.tsx +++ b/frontend/src/context/AdminClubContext.tsx @@ -1,12 +1,15 @@ import { createContext, useContext, useState } from 'react'; +import { ApplicantsInfo } from '@/types/applicants'; interface AdminClubContextType { clubId: string | null; setClubId: (id: string | null) => void; + applicantsData: ApplicantsInfo | null; + setApplicantsData: (data: ApplicantsInfo | null) => void; } const AdminClubContext = createContext( - undefined, + undefined ); export const AdminClubProvider = ({ @@ -15,9 +18,10 @@ export const AdminClubProvider = ({ children: React.ReactNode; }) => { const [clubId, setClubId] = useState(null); + const [applicantsData, setApplicantsData] = useState(null); return ( - + {children} ); diff --git a/frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsx b/frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsx index 2c787f3f0..79563ed5e 100644 --- a/frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsx +++ b/frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsx @@ -3,15 +3,25 @@ import useAuth from '@/hooks/useAuth'; import { Navigate } from 'react-router-dom'; import { useAdminClubContext } from '@/context/AdminClubContext'; import Spinner from '@/components/common/Spinner/Spinner'; +import { useGetApplicants } from '@/hooks/queries/applicants/useGetApplicants'; const PrivateRoute = ({ children }: { children: React.ReactNode }) => { const { isLoading, isAuthenticated, clubId } = useAuth(); - const { setClubId } = useAdminClubContext(); + const { setClubId, setApplicantsData } = useAdminClubContext(); + const { data: applicantsData } = useGetApplicants(clubId ?? ''); useEffect(() => { - if (clubId) setClubId(clubId); + if (clubId) { + setClubId(clubId); + } }, [clubId, setClubId]); + useEffect(() => { + if (clubId && applicantsData) { + setApplicantsData(applicantsData); + } + }, [clubId, applicantsData]); + if (isLoading) return ; if (!isAuthenticated) return ; From 6f7264e0b33bd2b82f92fe2da18b459443d217f3 Mon Sep 17 00:00:00 2001 From: lepitaaar Date: Wed, 30 Jul 2025 18:15:36 +0900 Subject: [PATCH 06/16] =?UTF-8?q?feat:=20=EC=A7=80=EC=9B=90=EC=9E=90=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=ED=83=AD=EC=97=90=20=EC=A7=80=EC=9B=90?= =?UTF-8?q?=EC=9E=90=20=EC=83=81=EC=84=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=82=AC=EC=9D=B4=EB=93=9C?= =?UTF-8?q?=EB=B0=94=20=EB=9D=BC=EB=B2=A8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminPage/components/SideBar/SideBar.tsx | 2 +- .../ApplicantsTab/ApplicantDetailPage.tsx | 74 +++++++++ .../ApplicantsTab/ApplicantsTab.styles.tsx | 4 +- .../tabs/ApplicantsTab/ApplicantsTab.tsx | 148 +++++++++--------- 4 files changed, 150 insertions(+), 78 deletions(-) create mode 100644 frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx diff --git a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx index c92bdef2b..0e250eccc 100644 --- a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx +++ b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx @@ -15,7 +15,7 @@ const tabs = [ { label: '모집 정보 수정', path: '/admin/recruit-edit' }, { label: '활동 사진 수정', path: '/admin/photo-edit' }, { label: '지원서 관리', path: '/admin/application-edit' }, - { label: '지원 관리', path: '/admin/applicants' }, + { label: '지원자 관리', path: '/admin/applicants' }, { label: '계정 관리', path: '/admin/account-edit' }, ]; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx new file mode 100644 index 000000000..fc428f5fe --- /dev/null +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { useAdminClubContext } from '@/context/AdminClubContext'; +import Header from '@/components/common/Header/Header'; +import { PageContainer } from '@/styles/PageContainer.styles'; +import * as Styled from '@/pages/ApplicationFormPage/ApplicationFormPage.styles'; +import QuestionContainer from '@/pages/ApplicationFormPage/components/QuestionContainer/QuestionContainer'; +import QuestionAnswerer from '@/pages/ApplicationFormPage/components/QuestionAnswerer/QuestionAnswerer'; +import { useGetApplication } from '@/hooks/queries/application/useGetApplication'; +import Spinner from '@/components/common/Spinner/Spinner'; +import backButtonIcon from '@/assets/images/icons/back_button_icon.svg'; + + +const ApplicantDetailPage = () => { + const { questionId } = useParams<{ questionId: string }>(); + const navigate = useNavigate(); + const { applicantsData, clubId } = useAdminClubContext(); + + // 지원서 질문 목록 fetch + const { data: formData, isLoading, isError } = useGetApplication(clubId!); + + if (!applicantsData) { + return
지원자 데이터를 불러올 수 없습니다.
; + } + if (isLoading) return ; + if (isError || !formData) return
지원서 정보를 불러올 수 없습니다.
; + + // questionId로 지원자 찾기 + const applicant = applicantsData.applicants.find( + (a) => String(a.questionId) === String(questionId) + ); + if (!applicant) { + return
해당 지원자를 찾을 수 없습니다.
; + } + + // 답변 매핑 함수 + const getAnswerByQuestionId = (qId: number) => { + return applicant.answers + .filter((ans) => ans.id === qId) + .map((ans) => ans.value); + }; + + return ( + <> +
+ + {/* FormTitle과 백아이콘을 한 줄에 배치 */} +
+ +
+ {/* 커서 고정 */} + + {formData.questions.map((q: import('@/types/application').Question, i: number) => ( + + {}} + /> + + ))} + +
+ + ); +}; + +export default ApplicantDetailPage; \ No newline at end of file diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx index d624134d6..991b40505 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx @@ -7,7 +7,7 @@ export const TopBar = styled.div` margin-bottom: 32px; `; -export const PageTitle = styled.h1` +export const PageTitle = styled.h2` font-size: 28px; font-weight: 700; margin: 0; @@ -22,7 +22,7 @@ export const SemesterSelect = styled.select` `; // 통계 카드 스타일 -export const SummaryContainer = styled.div` +export const SummaryWrapper = styled.div` display: flex; gap: 12px; margin-bottom: 40px; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx index 1c554b8a0..3cc5c3ec4 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx @@ -1,108 +1,106 @@ import { useAdminClubContext } from "@/context/AdminClubContext"; import { useGetApplicants } from "@/hooks/queries/applicants/useGetApplicants"; -import { Applicant, ApplicantsInfo, APPLICATION_STATUS_KR } from "@/types/applicants"; +import { Applicant, ApplicantsInfo } from "@/types/applicants"; import React, { useEffect, useState } from "react"; -import { ApplicantsSection, FilterRow, FilterSelect, PageTitle, SearchInput, StatusBadge, SummaryCard, SummaryContainer, SummaryLabel, SummaryValue, Table, Td, Th, Thead, Title, TopBar, Tr } from "./ApplicantsTab.styles"; +import * as styled from "./ApplicantsTab.styles"; +import { useNavigate } from "react-router-dom"; const ApplicantsTab = () => { - const { clubId } = useAdminClubContext(); + const { clubId, applicantsData } = useAdminClubContext(); if (!clubId) return null; - - const { data, isLoading, isError } = useGetApplicants(clubId); - const [applicantsData, setApplicantsData] = useState(); - - useEffect(() => { - if (data) { - setApplicantsData(data); - } - }, [data]); + const navigate = useNavigate(); return ( <> - {/* 상단 타이틀/학기선택 */} - - 지원 현황 - {/* */} + + 지원 현황 + {/* */} {/* */} {/* ...다른 학기 */} - {/* */} - + {/* */} + - {/* 통계 카드 */} - - - 전체 지원자 수 - + + + 전체 지원자 수 + {applicantsData?.total} - - - - 서류 검토 필요 - + + + + 서류 검토 필요 + {applicantsData?.reviewRequired} - - - - 면접 예정 - + + + + 면접 예정 + {applicantsData?.scheduledInterview} - - - - 합격 - + + + + 합격 + {applicantsData?.accepted} - - - + + + - {/* 지원자 목록 */} - - 지원자 목록 - - + + 지원자 목록 + + - - + + - - - - - - - - - - - - - + + + + + + + + 현재상태 + 이름 + 메모 + 제출날짜 + + { applicantsData?.applicants.map((item: Applicant, index: number) => ( - - - - - - - + + 날짜 + )) } -
현재상태이름메모제출날짜
- - - {APPLICATION_STATUS_KR[item.status]} - {item.answers[0].value} + navigate(`/admin/applicants/${item.questionId}`)} + > + + ) => e.stopPropagation()} + /> + + + {/* {APPLICATION_STATUS_KR[item.status]} */} + + {item.answers[0].value} +

메모

-
날짜
-
+ + ); }; From 25193819213beed1d49685b0378e5f2e3932977d Mon Sep 17 00:00:00 2001 From: lepitaaar Date: Wed, 30 Jul 2025 18:15:41 +0900 Subject: [PATCH 07/16] =?UTF-8?q?refactor:=20=EC=A7=80=EC=9B=90=EC=9E=90?= =?UTF-8?q?=20=EC=83=81=ED=83=9C=20=EA=B4=80=EB=A0=A8=20=ED=95=9C=EA=B5=AD?= =?UTF-8?q?=EC=96=B4=20=EC=83=81=EC=88=98=20=EC=A0=95=EC=9D=98=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/types/applicants.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/frontend/src/types/applicants.ts b/frontend/src/types/applicants.ts index 8b047db54..99c9f7181 100644 --- a/frontend/src/types/applicants.ts +++ b/frontend/src/types/applicants.ts @@ -16,22 +16,6 @@ export enum ApplicationStatus { CANCELED_BY_APPLICANT = 'CANCELED_BY_APPLICANT', // 지원자 자진 철회 } -export const APPLICATION_STATUS_KR: Record = { - [ApplicationStatus.DRAFT]: '작성 중', - [ApplicationStatus.SUBMITTED]: '제출 완료', - [ApplicationStatus.SCREENING]: '서류 심사 중', - [ApplicationStatus.SCREENING_PASSED]: '서류 통과', - [ApplicationStatus.SCREENING_FAILED]: '서류 탈락', - [ApplicationStatus.INTERVIEW_SCHEDULED]: '면접 일정 확정', - [ApplicationStatus.INTERVIEW_IN_PROGRESS]: '면접 진행 중', - [ApplicationStatus.INTERVIEW_PASSED]: '면접 통과', - [ApplicationStatus.INTERVIEW_FAILED]: '면접 탈락', - [ApplicationStatus.OFFERED]: '최종 합격 제안', - [ApplicationStatus.ACCEPTED]: '제안 수락', - [ApplicationStatus.DECLINED]: '제안 거절', - [ApplicationStatus.CANCELED_BY_APPLICANT]: '지원자 자진 철회', -}; - export interface ApplicantsInfo { total: number; reviewRequired: number; From 2b6f5cdc512329d000bcbea282df05cc362ba8b7 Mon Sep 17 00:00:00 2001 From: seongwon030 <105052068+seongwon030@users.noreply.github.com> Date: Wed, 30 Jul 2025 19:35:31 +0900 Subject: [PATCH 08/16] =?UTF-8?q?refactor:=20=EC=A7=80=EC=9B=90=EC=84=9C?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=ED=83=AD=20=EC=8A=A4=ED=83=80=EC=9D=BC?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ApplicantsTab/ApplicantsTab.styles.tsx | 64 +++---- .../tabs/ApplicantsTab/ApplicantsTab.tsx | 175 ++++++++++-------- 2 files changed, 128 insertions(+), 111 deletions(-) diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx index 991b40505..9d8502d80 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx @@ -1,13 +1,13 @@ -import styled from "styled-components"; +import styled from 'styled-components'; -export const TopBar = styled.div` +export const ApplicationHeader = styled.div` display: flex; align-items: center; justify-content: space-between; margin-bottom: 32px; `; -export const PageTitle = styled.h2` +export const ApplicationTitle = styled.h2` font-size: 28px; font-weight: 700; margin: 0; @@ -21,16 +21,16 @@ export const SemesterSelect = styled.select` font-size: 16px; `; -// 통계 카드 스타일 +// 지원현황 export const SummaryWrapper = styled.div` display: flex; gap: 12px; margin-bottom: 40px; `; -export const SummaryCard = styled.div<{ bg: string }>` +export const SummaryCard = styled.div<{ bgColor: string }>` flex: 1; - background: ${({ bg }) => bg}; + background: ${({ bgColor }) => bgColor}; border-radius: 10px; padding: 32px 0; text-align: center; @@ -45,30 +45,31 @@ export const SummaryValue = styled.div` font-size: 40px; font-weight: 700; margin-top: 8px; - span { - font-size: 20px; - font-weight: 400; - margin-left: 2px; - } +`; + +export const SummaryPeople = styled.span` + font-size: 20px; + font-weight: 400; + margin-left: 2px; `; // 지원자 목록 스타일 -export const ApplicantsSection = styled.div``; +export const ApplicantListWrapper = styled.div``; -export const Title = styled.h2` +export const ApplicantListTitle = styled.h2` font-size: 28px; font-weight: 700; margin-bottom: 24px; `; -export const FilterRow = styled.div` +export const ApplicantListHeader = styled.div` display: flex; align-items: center; margin-bottom: 16px; gap: 8px; `; -export const FilterSelect = styled.select` +export const ApplicantFilterSelect = styled.select` padding: 8px 16px; border-radius: 8px; border: 1px solid #ddd; @@ -76,7 +77,7 @@ export const FilterSelect = styled.select` font-size: 16px; `; -export const SearchInput = styled.input` +export const ApplicantSearchBox = styled.input` margin-left: auto; padding: 8px 16px; border-radius: 8px; @@ -85,17 +86,18 @@ export const SearchInput = styled.input` font-size: 16px; `; -export const Table = styled.table` +export const ApplicantTable = styled.table` width: 100%; border-collapse: collapse; background: #fff; `; -export const Thead = styled.thead` +export const ApplicantTableHeaderWrapper = styled.thead` background: #fafafa; `; -export const Th = styled.th` +export const ApplicantTableHeader = styled.th` + background: #fafafa; padding: 12px 8px; font-size: 16px; font-weight: 500; @@ -103,31 +105,31 @@ export const Th = styled.th` text-align: left; `; -export const Tr = styled.tr` +export const ApplicantTableRow = styled.tr` border-bottom: 1px solid #f0f0f0; &:hover { background: #f7faff; } `; -export const Td = styled.td` +export const ApplicantTableCol = styled.td` padding: 12px 8px; font-size: 16px; `; -export const StatusBadge = styled.span<{ status: string }>` +export const ApplicantStatusBadge = styled.span<{ status: string }>` display: inline-block; border-radius: 8px; padding: 4px 12px; font-weight: 500; font-size: 15px; background: ${({ status }) => - status === "서류검토" - ? "#E6F4FB" - : status === "면접예정" - ? "#E6FBF0" - : status === "합격" - ? "#F5F5F5" - : "#eee"}; - color: ${({ status }) => (status === "합격" ? "#888" : "#222")}; -`; \ No newline at end of file + status === '서류검토' + ? '#E6F4FB' + : status === '면접예정' + ? '#E6FBF0' + : status === '합격' + ? '#F5F5F5' + : '#eee'}; + color: ${({ status }) => (status === '합격' ? '#888' : '#222')}; +`; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx index 3cc5c3ec4..cbbddcbfe 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx @@ -1,108 +1,123 @@ -import { useAdminClubContext } from "@/context/AdminClubContext"; -import { useGetApplicants } from "@/hooks/queries/applicants/useGetApplicants"; -import { Applicant, ApplicantsInfo } from "@/types/applicants"; -import React, { useEffect, useState } from "react"; -import * as styled from "./ApplicantsTab.styles"; -import { useNavigate } from "react-router-dom"; +import { useAdminClubContext } from '@/context/AdminClubContext'; +import { useGetApplicants } from '@/hooks/queries/applicants/useGetApplicants'; +import { Applicant, ApplicantsInfo } from '@/types/applicants'; +import React, { useEffect, useState } from 'react'; +import * as Styled from './ApplicantsTab.styles'; +import { useNavigate } from 'react-router-dom'; const ApplicantsTab = () => { + const navigate = useNavigate(); const { clubId, applicantsData } = useAdminClubContext(); if (!clubId) return null; - const navigate = useNavigate(); return ( <> - - 지원 현황 - {/* */} - {/* */} - {/* ...다른 학기 */} - {/* */} - + + 지원 현황 + {/* + + ...다른 학기 */ + /*{' '} + */} + - - - 전체 지원자 수 - + + + 전체 지원자 수 + {applicantsData?.total} - - - - - 서류 검토 필요 - + + + + + 서류 검토 필요 + {applicantsData?.reviewRequired} - - - - - 면접 예정 - + + + + + 면접 예정 + {applicantsData?.scheduledInterview} - - - - - 합격 - + + + + + 합격 + {applicantsData?.accepted} - - - - + + + + - - 지원자 목록 - - + + 지원자 목록 + + - - + + - - - - - - - - 현재상태 - 이름 - 메모 - 제출날짜 - - + + + + + + + + + 현재상태 + + + 이름 + + 메모 + + 제출날짜 + + + - { - applicantsData?.applicants.map((item: Applicant, index: number) => ( - ( + navigate(`/admin/applicants/${item.questionId}`)} + style={{ cursor: 'pointer' }} + onClick={() => + navigate(`/admin/applicants/${item.questionId}`) + } > - + ) => e.stopPropagation()} + onClick={(e: React.MouseEvent) => + e.stopPropagation() + } /> - - + + {/* {APPLICATION_STATUS_KR[item.status]} */} - - {item.answers[0].value} - + + + {item.answers[0].value} + +

메모

-
- 날짜 -
- )) - } + + 날짜 + + ), + )} -
-
+ + ); }; -export default ApplicantsTab; \ No newline at end of file +export default ApplicantsTab; From cf6cf255ed8cf747b872bfbc4cae3f27f732f96a Mon Sep 17 00:00:00 2001 From: seongwon030 <105052068+seongwon030@users.noreply.github.com> Date: Sun, 3 Aug 2025 01:01:14 +0900 Subject: [PATCH 09/16] =?UTF-8?q?feat:=20snsLink=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EC=A0=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/SnsLinkIcons/SnsLinkIcons.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx b/frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx index 103cb50f0..9488e1165 100644 --- a/frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx +++ b/frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx @@ -2,12 +2,15 @@ import React from 'react'; import * as Styled from './SnsLinkIcons.styles'; import { SNS_CONFIG } from '@/constants/snsConfig'; import { SNSPlatform } from '@/types/club'; +import useMixpanelTrack from '@/hooks/useMixpanelTrack'; interface SnsLinkIconsProps { apiSocialLinks: Partial>; } const SnsLinkIcons = ({ apiSocialLinks }: SnsLinkIconsProps) => { + const trackEvent = useMixpanelTrack(); + if (!apiSocialLinks) return null; return ( @@ -22,7 +25,11 @@ const SnsLinkIcons = ({ apiSocialLinks }: SnsLinkIconsProps) => { target='_blank' rel='noreferrer' > - + trackEvent('sns링크 버튼 클릭', { platform })} + /> ); })} From 95a600541f45602ad0fb07b174c10654226e42b0 Mon Sep 17 00:00:00 2001 From: seongwon030 <105052068+seongwon030@users.noreply.github.com> Date: Sun, 3 Aug 2025 01:50:03 +0900 Subject: [PATCH 10/16] =?UTF-8?q?feat:=20=EA=B2=80=EC=83=89=20=EC=8B=9C=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EB=8F=99=EC=95=84=EB=A6=AC=20=EA=B8=B0?= =?UTF-8?q?=EC=A4=80=EC=9C=BC=EB=A1=9C=20=EA=B2=80=EC=83=89=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 검색 시 카테고리 필터 무시하고 전체 동아리에서 검색 - SearchContext에 isSearching 상태 추가 - 검색과 카테고리 필터링 로직 분리 --- .../src/components/common/SearchBox/SearchBox.tsx | 12 +++++++++--- frontend/src/context/SearchContext.tsx | 8 +++++++- frontend/src/pages/MainPage/MainPage.tsx | 5 +++-- .../CategoryButtonList/CategoryButtonList.tsx | 4 ++-- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/common/SearchBox/SearchBox.tsx b/frontend/src/components/common/SearchBox/SearchBox.tsx index 5d98a9091..be9c5917f 100644 --- a/frontend/src/components/common/SearchBox/SearchBox.tsx +++ b/frontend/src/components/common/SearchBox/SearchBox.tsx @@ -1,5 +1,6 @@ import { useRef, useState } from 'react'; import { useSearch } from '@/context/SearchContext'; +import { useCategory } from '@/context/CategoryContext'; import useMixpanelTrack from '@/hooks/useMixpanelTrack'; import * as Styled from './SearchBox.styles'; import SearchIcon from '@/assets/images/icons/search_button_icon.svg'; @@ -7,7 +8,8 @@ import { useLocation, useNavigate } from 'react-router-dom'; const SearchBox = () => { const [isSearchBoxClicked, setIsSearchBoxClicked] = useState(false); - const { setKeyword, inputValue, setInputValue } = useSearch(); + const { setKeyword, inputValue, setInputValue, setIsSearching } = useSearch(); + const { setSelectedCategory } = useCategory(); const trackEvent = useMixpanelTrack(); const navigate = useNavigate(); const location = useLocation(); @@ -23,6 +25,8 @@ const SearchBox = () => { const handleSearch = () => { redirectToHome(); setKeyword(inputValue); + setSelectedCategory('all'); + setIsSearching(true); inputRef.current?.blur(); @@ -40,7 +44,8 @@ const SearchBox = () => { return ( + onSubmit={handleSubmit} + > { + aria-label='검색' + > Search Button diff --git a/frontend/src/context/SearchContext.tsx b/frontend/src/context/SearchContext.tsx index d20af1f39..24296326f 100644 --- a/frontend/src/context/SearchContext.tsx +++ b/frontend/src/context/SearchContext.tsx @@ -5,6 +5,8 @@ interface SearchContextType { setKeyword: (keyword: string) => void; inputValue: string; setInputValue: (value: string) => void; + isSearching: boolean; + setIsSearching: (isSearching: boolean) => void; } interface SearchProviderProps { @@ -16,6 +18,7 @@ const SearchContext = createContext(undefined); export const SearchProvider = ({ children }: SearchProviderProps) => { const [keyword, setKeyword] = useState(''); const [inputValue, setInputValue] = useState(''); + const [isSearching, setIsSearching] = useState(false); return ( { setKeyword, inputValue, setInputValue, - }}> + isSearching, + setIsSearching, + }} + > {children} ); diff --git a/frontend/src/pages/MainPage/MainPage.tsx b/frontend/src/pages/MainPage/MainPage.tsx index c49dae1be..0f8857380 100644 --- a/frontend/src/pages/MainPage/MainPage.tsx +++ b/frontend/src/pages/MainPage/MainPage.tsx @@ -21,15 +21,16 @@ const MainPage = () => { const [isFilterActive, setIsFilterActive] = useState(false); const { selectedCategory, setSelectedCategory } = useCategory(); - const { keyword } = useSearch(); + const { keyword, isSearching } = useSearch(); const recruitmentStatus = isFilterActive ? 'OPEN' : 'all'; const division = 'all'; + const searchCategory = isSearching ? 'all' : selectedCategory; const { data: clubs, error, isLoading, - } = useGetCardList(keyword, recruitmentStatus, division, selectedCategory); + } = useGetCardList(keyword, recruitmentStatus, division, searchCategory); const isEmpty = !isLoading && (!clubs || clubs.length === 0); const hasData = clubs && clubs.length > 0; diff --git a/frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx b/frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx index cdaeafaff..b5a568108 100644 --- a/frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx +++ b/frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx @@ -58,7 +58,7 @@ const clubCategories: Category[] = [ ]; const CategoryButtonList = () => { - const { setKeyword, setInputValue } = useSearch(); + const { setKeyword, setInputValue, setIsSearching } = useSearch(); const { setSelectedCategory } = useCategory(); const handleCategoryClick = (category: Category) => { @@ -71,9 +71,9 @@ const CategoryButtonList = () => { setKeyword(''); setInputValue(''); + setIsSearching(false); setSelectedCategory(category.id); - }; return ( From b507b1d05f9ed75bbc3e48fdeb196c89706b0d85 Mon Sep 17 00:00:00 2001 From: seongwon030 <105052068+seongwon030@users.noreply.github.com> Date: Sun, 3 Aug 2025 15:05:13 +0900 Subject: [PATCH 11/16] =?UTF-8?q?refactor:=20SNS=20=EB=A7=81=ED=81=AC=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=EC=97=90=EC=84=9C=20=EB=A7=81?= =?UTF-8?q?=ED=81=AC=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SnsIcon의 onClick을 SnsLink로 이동하여 이벤트 전파 문제 해결 - 링크 클릭과 이벤트 추적이 독립적으로 동작하도록 개선 --- .../components/SnsLinkIcons/SnsLinkIcons.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx b/frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx index 9488e1165..ce71270a0 100644 --- a/frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx +++ b/frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx @@ -24,12 +24,14 @@ const SnsLinkIcons = ({ apiSocialLinks }: SnsLinkIconsProps) => { href={url} target='_blank' rel='noreferrer' + onClick={() => + trackEvent('sns링크 버튼 클릭', { + platform, + clubName, + }) + } > - trackEvent('sns링크 버튼 클릭', { platform })} - /> + ); })} From 14a4bcdf165870242da530f61537b60a8155b231 Mon Sep 17 00:00:00 2001 From: seongwon030 <105052068+seongwon030@users.noreply.github.com> Date: Sun, 3 Aug 2025 15:06:00 +0900 Subject: [PATCH 12/16] =?UTF-8?q?feat:=20SnsLinkIcons=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=EC=97=90=20clubName=20props=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SNS 링크 클릭 시 동아리 이름과 플랫폼 정보 추적 --- .../pages/ClubDetailPage/components/InfoBox/InfoBox.tsx | 7 ++++++- .../components/SnsLinkIcons/SnsLinkIcons.tsx | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx b/frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx index a922e1194..104ffe404 100644 --- a/frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx +++ b/frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx @@ -40,7 +40,12 @@ const InfoBox = ({ sectionRefs, clubDetail }: InfoBoxProps) => { { label: '전화번호', value: clubDetail.presidentPhoneNumber }, { label: 'SNS', - render: , + render: ( + + ), }, ], refIndex: INFOTABS_SCROLL_INDEX.CLUB_INFO_TAB, diff --git a/frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx b/frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx index ce71270a0..4ca845bc1 100644 --- a/frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx +++ b/frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx @@ -6,9 +6,10 @@ import useMixpanelTrack from '@/hooks/useMixpanelTrack'; interface SnsLinkIconsProps { apiSocialLinks: Partial>; + clubName?: string; } -const SnsLinkIcons = ({ apiSocialLinks }: SnsLinkIconsProps) => { +const SnsLinkIcons = ({ apiSocialLinks, clubName }: SnsLinkIconsProps) => { const trackEvent = useMixpanelTrack(); if (!apiSocialLinks) return null; From 7853a0c173e8419894a92e1f47e5fcdd26b13fd8 Mon Sep 17 00:00:00 2001 From: seongwon030 <105052068+seongwon030@users.noreply.github.com> Date: Sun, 3 Aug 2025 15:06:08 +0900 Subject: [PATCH 13/16] =?UTF-8?q?refactor:=20ShareButton=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=9D=B4=EB=A6=84=EC=9D=84=20'=EA=B3=B5?= =?UTF-8?q?=EC=9C=A0=ED=95=98=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD'=EC=9C=BC=EB=A1=9C=20=ED=86=B5=EC=9D=BC=ED=95=98?= =?UTF-8?q?=EA=B3=A0=20clubName=EC=9D=84=20=ED=95=84=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/ClubDetailPage/components/ShareButton/ShareButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx b/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx index eea5ea29e..935ab62ba 100644 --- a/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx +++ b/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx @@ -43,7 +43,7 @@ const ShareButton = ({ clubId }: ShareButtonProps) => { }, ], }); - trackEvent(`${clubDetail.name} 공유하기 버튼 클릭`); + trackEvent('공유하기 버튼 클릭', { clubName: clubDetail.name }); }; return ( From cbc1850b05db2e82701607d873a14e15260df2c9 Mon Sep 17 00:00:00 2001 From: lepitaaar Date: Wed, 6 Aug 2025 23:52:01 +0900 Subject: [PATCH 14/16] =?UTF-8?q?refactor:=20=EC=A7=80=EC=9B=90=EC=9E=90?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=A7=88=EB=AC=B8=20ID=EB=A5=BC=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90=EC=9E=90=20ID=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=98?= =?UTF-8?q?=EA=B3=A0,=20=EC=A7=80=EC=9B=90=EC=9E=90=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=20=EB=A7=A4=ED=95=91=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ApplicantsTab/ApplicantDetailPage.tsx | 15 ++++++- .../ApplicantsTab/ApplicantsTab.styles.tsx | 3 +- .../tabs/ApplicantsTab/ApplicantsTab.tsx | 44 ++++++++++++++++--- frontend/src/types/applicants.ts | 4 +- 4 files changed, 55 insertions(+), 11 deletions(-) diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx index fc428f5fe..32395e9b7 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx @@ -27,7 +27,7 @@ const ApplicantDetailPage = () => { // questionId로 지원자 찾기 const applicant = applicantsData.applicants.find( - (a) => String(a.questionId) === String(questionId) + (a) => a.id === questionId ); if (!applicant) { return
해당 지원자를 찾을 수 없습니다.
; @@ -45,7 +45,18 @@ const ApplicantDetailPage = () => {
{/* FormTitle과 백아이콘을 한 줄에 배치 */} -
+