Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion src/app/mocks/repositories/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
DashboardSummary,
ApplicantsApiResponse,
ApplicantData,
InterviewSchedule,
} from '@/pages/admin/Dashboard/types/dashboard';

const MOCK_DASHBOARD_SUMMARY: DashboardSummary = {
Expand All @@ -11,6 +12,25 @@ const MOCK_DASHBOARD_SUMMARY: DashboardSummary = {
endDay: '2024-03-31',
};

const MOCK_INTERVIEW_SCHEDULE: InterviewSchedule[] = [
{
date: '2026-09-02',
slots: [
{ time: '14:00', assignedCount: 1 },
{ time: '15:00', assignedCount: 1 },
{ time: '16:00', assignedCount: 0 },
],
},
{
date: '2026-09-03',
slots: [
{ time: '10:00', assignedCount: 1 },
{ time: '11:00', assignedCount: 0 },
{ time: '14:00', assignedCount: 0 },
],
},
];

const MOCK_APPLICANTS: ApplicantData[] = [
{
applicantId: 1,
Expand All @@ -19,7 +39,15 @@ const MOCK_APPLICANTS: ApplicantData[] = [
department: '컴퓨터공학과',
phoneNumber: '010-1234-5678',
email: 'test1@example.com',
status: 'PENDING',
status: 'APPROVED',
confirmedTime: '2026-09-02T15:00:00',
interviewInfo: [
{
interviewDate: '2026-09-02',
availableTimes: ['14:00', '15:00', '16:00'],
},
],
interviewSchedule: MOCK_INTERVIEW_SCHEDULE,
},
{
applicantId: 2,
Expand All @@ -29,6 +57,14 @@ const MOCK_APPLICANTS: ApplicantData[] = [
phoneNumber: '010-2345-6789',
email: 'test2@example.com',
status: 'APPROVED',
confirmedTime: undefined,
interviewInfo: [
{
interviewDate: '2026-09-02',
availableTimes: ['10:00', '11:00'],
},
],
interviewSchedule: MOCK_INTERVIEW_SCHEDULE,
},
{
applicantId: 3,
Expand All @@ -38,6 +74,13 @@ const MOCK_APPLICANTS: ApplicantData[] = [
phoneNumber: '010-3456-7890',
email: 'test3@example.com',
status: 'REJECTED',
confirmedTime: '2026-09-03T10:00:00',
interviewInfo: [
{
interviewDate: '2026-09-03',
availableTimes: ['10:00', '11:00', '14:00'],
},
],
},
];

Expand All @@ -49,6 +92,7 @@ export const dashboardRepository = {
getApplicants: (): ApplicantsApiResponse => {
return {
applicants: MOCK_APPLICANTS,
interviewSchedule: MOCK_INTERVIEW_SCHEDULE,
message: null,
};
},
Expand Down
1 change: 0 additions & 1 deletion src/pages/admin/ApplicationDetail/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { ApplicantProfileSection } from './components/ApplicantProfileSection/in
import { ApplicantQuestionSection } from './components/ApplicationQuestionSection';
import { CommentSection } from './components/CommentSection';
import { useDetailApplications } from './hooks/useDetailApplication';

import * as S from './index.styled';

export const ApplicationDetailPage = () => {
Expand Down
4 changes: 2 additions & 2 deletions src/pages/admin/Dashboard/api/sentMessage.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { apiInstance } from '@/app/api/initInstance';
import { stageMap } from '../utils/stageMap';
import { STAGE_LABEL } from '../utils/labelMap';
import type { ApplicationStage } from '@/pages/admin/Dashboard/types/dashboard';

export const sentMessage = async (
clubId: number,
message: string,
stage: ApplicationStage,
): Promise<void> => {
const apiStage = stageMap[stage];
const apiStage = STAGE_LABEL[stage];

try {
await apiInstance.patch(`/clubs/${clubId}/club-apply-form/result?stage=${apiStage}`, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from '@emotion/styled';
import { useApplicants } from '@/pages/admin/Dashboard/hooks/useApplicants';
import { stageMap } from '@/pages/admin/Dashboard/utils/stageMap';
import { STAGE_LABEL } from '@/pages/admin/Dashboard/utils/labelMap';
import { ApplicantFilterButton } from './ApplicationFilterButton';

import type {
Expand All @@ -16,7 +16,7 @@ export type Props = {
};

export const ApplicationStatusFilter = ({ option, onOptionChange, stage, clubId }: Props) => {
const apiStage = stageMap[stage];
const apiStage = STAGE_LABEL[stage];
const { counts } = useApplicants(clubId, apiStage);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const Container = styled.div({

export const ApplicantInfoCategoryList = styled.div`
display: grid;
grid-template-columns: 1fr 1fr 1.5fr 1.5fr 2fr 1fr;
grid-template-columns: 1fr 1fr 1fr 1.2fr 1.5fr 1fr 1.2fr;
background-color: #f9fbfc;
border-bottom: 1.8px solid ${({ theme }) => theme.colors.gray100};
padding: 1.7rem 0 1.5rem 0;
Expand All @@ -16,29 +16,34 @@ export const ApplicantInfoCategoryList = styled.div`
}

@media (max-width: 1200px) {
grid-template-columns: 1fr 1fr 1.5fr 1.5fr 1fr;
& > div:nth-of-type(5) {
grid-template-columns: 1fr 1fr 1fr 1.5fr 1fr 1.2fr;

& > div:nth-of-type(4) {
display: none;
}
}

@media (max-width: ${({ theme }) => theme.breakpoints.web}) {
grid-template-columns: 1fr 1fr 1.5fr 1fr;
& > div:nth-of-type(4) {
grid-template-columns: 1fr 1fr 1.5fr 1fr 1.2fr;

& > div:nth-of-type(3) {
display: none;
}
}

@media (max-width: 768px) {
grid-template-columns: 1fr 1fr 1fr;
& > div:nth-of-type(3) {
grid-template-columns: 1fr 1.5fr 1fr 1.2fr;

& > div:nth-of-type(2) {
display: none;
}
}

@media (max-width: ${({ theme }) => theme.breakpoints.mobile}) {
grid-template-columns: 1.5fr 1fr;
& > div:nth-of-type(2) {
grid-template-columns: 1fr 1fr;

& > div:nth-of-type(5),
& > div:nth-of-type(7) {
display: none;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useCallback } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useApplicants } from '@/pages/admin/Dashboard/hooks/useApplicants';
import { stageMap } from '@/pages/admin/Dashboard/utils/stageMap';
import { STAGE_LABEL } from '@/pages/admin/Dashboard/utils/labelMap';
import { LoadingSpinner } from '@/shared/components/LoadingSpinner';
import { ApplicantListItem } from '../ApplicantListItem';
import * as S from './index.styled';
import type {
ApplicateInfoCategory,
ApplicationFilterOption,
ApplicationStage,
} from '@/pages/admin/Dashboard/types/dashboard';
Expand All @@ -15,22 +16,22 @@ type Props = {
stage: ApplicationStage;
};

type ApplicateInfoCategory = '이름' | '학번' | '학과' | '전화번호' | '이메일' | '결과';
const INFO_CATEGORY: ApplicateInfoCategory[] = [
'이름',
'학번',
'학과',
'전화번호',
'이메일',
'결과',
'면접 시간',
];

export const ApplicantList = ({ filterOption, stage }: Props) => {
const { clubId } = useParams();

const navigate = useNavigate();

const apiStage = stageMap[stage];
const apiStage = STAGE_LABEL[stage];

const {
data: applicants,
Expand Down Expand Up @@ -67,6 +68,9 @@ export const ApplicantList = ({ filterOption, stage }: Props) => {
phoneNumber={applicant.phoneNumber}
email={applicant.email}
status={applicant.status}
confirmedTime={applicant.confirmedTime}
interviewInfo={applicant.interviewInfo}
interviewSchedule={applicant.interviewSchedule}
onClick={handleItemClick}
/>
))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import styled from '@emotion/styled';

export const Container = styled.div({
display: 'flex',
flexDirection: 'column',
gap: '1.8rem',
});

export const Section = styled.div({
display: 'flex',
flexDirection: 'column',
gap: '1rem',
});

export const SectionTitle = styled.h3(({ theme }) => ({
fontSize: theme.font.size.sm,
fontWeight: 600,
color: theme.colors.gray700,
paddingBottom: '0.5rem',
borderBottom: `1px solid ${theme.colors.gray200}`,
}));

export const ScheduleRow = styled.div({
display: 'flex',
alignItems: 'flex-start',
gap: '1rem',
});
export const ScheduleDateLabel = styled.span(({ theme }) => ({
fontSize: theme.font.size.sm,
fontWeight: 600,
color: theme.colors.gray700,
minWidth: '2.5rem',
paddingTop: '0.5rem',
}));

export const DateLabel = styled.span(({ theme }) => ({
fontSize: theme.font.size.sm,
fontWeight: 600,
color: theme.colors.gray700,
minWidth: '2.5rem',
}));

export const SlotsContainer = styled.div({
display: 'flex',
flexWrap: 'wrap',
gap: '0.5rem',
flex: 1,
});

export const TimeSlot = styled.button<{ $selected?: boolean }>(({ theme, $selected }) => ({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '0.5rem 0.75rem',
borderRadius: theme.radius.md,
border: `1px solid ${$selected ? theme.colors.primary : theme.colors.gray300}`,
backgroundColor: $selected ? theme.colors.primary00 : theme.colors.bg,
cursor: 'pointer',
transition: 'all 0.2s',
minWidth: '4rem',

'&:hover': {
borderColor: theme.colors.primary,
backgroundColor: theme.colors.primary00,
},
}));

export const SlotTime = styled.span(({ theme }) => ({
fontSize: theme.font.size.sm,
fontWeight: 500,
color: theme.colors.gray800,
}));

export const SlotCount = styled.span(({ theme }) => ({
fontSize: theme.font.size.xs,
color: theme.colors.gray500,
marginTop: '0.125rem',
}));

export const AvailableTimesRow = styled.div({
display: 'flex',
alignItems: 'center',
gap: '1rem',
});

export const AvailableTimes = styled.span(({ theme }) => ({
fontSize: theme.font.size.sm,
color: theme.colors.gray600,
}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { formatDateWithoutYear } from '@/shared/utils/dateUtils';
import * as S from './InterviewTimeContentModal.styled';
import type { InterviewInfo, InterviewSchedule } from '@/pages/admin/Dashboard/types/dashboard';

type Props = {
interviewInfo?: InterviewInfo[];
interviewSchedule?: InterviewSchedule[];
};

export const InterviewTimeContentModal = ({ interviewInfo, interviewSchedule }: Props) => {
return (
<S.Container>
<S.Section>
{interviewSchedule?.map((schedule) => (
<S.ScheduleRow key={schedule.date}>
<S.ScheduleDateLabel>{formatDateWithoutYear(schedule.date)}</S.ScheduleDateLabel>
<S.SlotsContainer>
{schedule.slots.map((slot) => (
<S.TimeSlot key={slot.time}>
<S.SlotTime>{slot.time}</S.SlotTime>
<S.SlotCount>({slot.assignedCount}명 선택)</S.SlotCount>
</S.TimeSlot>
))}
</S.SlotsContainer>
</S.ScheduleRow>
))}
</S.Section>

<S.Section>
<S.SectionTitle>지원자 면접 희망 시간대</S.SectionTitle>
{interviewInfo?.map((info) => (
<S.AvailableTimesRow key={info.interviewDate}>
<S.DateLabel>{formatDateWithoutYear(info.interviewDate)}</S.DateLabel>
<S.AvailableTimes>{info.availableTimes.join(', ')}</S.AvailableTimes>
</S.AvailableTimesRow>
))}
</S.Section>
</S.Container>
);
};
Loading
Loading