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
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import ApplicantToggle, {
export interface ApplicantSliderHeaderProps {
name: string;
current: number;
onViewApplication: () => void;
onViewApplication?: () => void;
isOtherUser?: boolean;
applicants: ApplicantItem[];
currentId: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const countBadge = style({
justifyContent: 'center',
border: `1px solid ${vars.colors.grayscale20}`,
marginLeft: '-12px',
zIndex: 1,
//zIndex: 1,
flexShrink: 0,
...fontStyles.md2_text_medium
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as styles from './ApplicantToggle.css';
import { Profile } from '@repo/ui/Profile';
import { Divider, Flex, Text } from '@repo/ui';
import { IcArrowDown } from '@repo/ui/icons/mono';
import { style } from '@vanilla-extract/css';

export interface ApplicantItem {
id: number;
Expand Down Expand Up @@ -75,11 +76,14 @@ const ApplicantToggle: React.FC<ApplicantToggleProps> = ({
>
<div className={styles.headerContent}>
<div className={styles.avatarWrapper}>
<div style={{zIndex: 2}}>
<Profile
size={32}
src={displayTarget.imageUrl}
alt={displayTarget.name}
/>
</div>


{otherCount > 0 && (
<div className={styles.countBadge}>+{otherCount}</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ import { Applicant } from '@web/constants/timetable';
import { getOriginalFileName } from '@web/components/FileUpload/FileUpload';
import { useRecruitmentDetailQuery } from '@web/store/query/useRecruitmentDetailQuery';
import { ApplicantItem } from '@web/app/(main)/interview-management/_components/ApplicantToggle/ApplicantToggle';
import { useApplicationDetailQuery } from '@web/store/query/useApplicationDetailQuery';
import { CompletedEvaluator } from '@web/constants/document';
import * as styles from './page.css';
import { RelationCard } from '@web/app/(main)/apply-management/[tab]/[id]/_components/RelationCard/RelationCard';
import { EvaluationCommentCard } from '@web/app/(main)/apply-management/[tab]/[id]/_components/EvaluationCommentCard/EvaluationCommentCard';
import { EvaluationScoreCard } from '@web/app/(main)/apply-management/[tab]/[id]/_components/EvaluationScoreCard/EvaluationScoreCard';
import ApplicantDetail from '@web/app/(main)/apply-management/[tab]/[id]/_components/ApplicantDetail/ApplicantDetail';

export default function ApplicantDetailClient() {
const router = useRouter();
Expand All @@ -24,92 +31,176 @@ export default function ApplicantDetailClient() {

const timeSlotId = Number(sp.get('timeSlotId'));
const recruitmentId = Number(sp.get('recruitmentId'));
const { data: apps } = useTimeSlotApplicationsQuery({ timeSlotId });
const { data: applicants = [], isLoading: listLoading } =
useTimeSlotApplicationsQuery({ timeSlotId });

const { data: recruitmentDetail } = useRecruitmentDetailQuery({
recruitmentId,
});
const [current, setCurrent] = useState(0);

const [current, setCurrent] = useState(0);
useEffect(() => {
setCurrent(0);
}, [timeSlotId]);

useEffect(() => {
setCurrent(0);
}, [date, rawTime, timeSlotId, apps?.length]);
const applicant = applicants[current];
if (!applicant) return null;

const applicationId = applicant.applicationId;

// 2지원서 상세
const {
data: data,
isLoading: detailLoading,
isError,
} = useApplicationDetailQuery({ applicationId });

// 모집 정보
const { data: rec, isLoading: recLoading } =
useRecruitmentDetailQuery({ recruitmentId });

if (listLoading || detailLoading || recLoading) return null;
if (!data || !rec || isError) return null;

const documentCompletedForCard: CompletedEvaluator[] =
(data.documentCompleted ?? []).map((c) => ({
evaluator: {
userId: c.evaluator.userId,
name: c.evaluator.name,
profileColor: c.evaluator.profileColor,
profileImageUrl: c.evaluator.profileImageUrl ?? null,
},
totalScore: c.totalScore,
}));

const interviewCompletedForCard: CompletedEvaluator[] =
(data.interviewCompleted ?? []).map((c) => ({
evaluator: {
userId: c.evaluator.userId,
name: c.evaluator.name,
profileColor: c.evaluator.profileColor,
profileImageUrl: c.evaluator.profileImageUrl ?? null,
},
totalScore: c.totalScore,
}));

const documentEvalStatus = [
...(data.documentPending ?? []).map((p) => ({
evaluator: p.name,
status: 'pending' as const,
score: null,
color: p.profileColor,
})),
...(data.documentCompleted ?? []).map((p) => ({
evaluator: p.evaluator.name,
status: 'complete' as const,
score: null,
color: p.evaluator.profileColor,
})),
];

const interviewEvalStatus = [
...(data.interviewPending ?? []).map((p) => ({
evaluator: p.name,
status: 'pending' as const,
score: null,
color: p.profileColor,
})),
...(data.interviewCompleted ?? []).map((p) => ({
evaluator: p.evaluator.name,
status: 'complete' as const,
score: null,
color: p.evaluator.profileColor,
})),
];

const handleSelectApplicant = (id: number) => {
const index = applicants.findIndex((a) => a.applicationId === id);
if (index !== -1) setCurrent(index);
};

if (!apps || apps.length === 0) {
return (
<Text variant="xl_title_semibold" color="black">
이 회차에 지원자가 없습니다.
</Text>
);
}

const applications = apps;
const app = applications[current];

if (!app) {
return null;
}

const toggleApplicants: ApplicantItem[] = applications.map((a) => {
const imgUrl =
(a as any).profileImageUrl || 'https://via.placeholder.com/150?text=User';

return {
id: a.applicationId,
name: a.name,
imageUrl: imgUrl,
};
});

const handleToggleSelect = (selectedId: number) => {
const index = applications.findIndex((a) => a.applicationId === selectedId);
if (index !== -1) {
setCurrent(index);
}
};
const portfolioAnswer = app.documentAnswers.find((d) => !!d.fileUrl);
const portfolioUrl = portfolioAnswer?.fileUrl ?? '';
const portfolioFile = portfolioUrl
? {
name: getOriginalFileName(portfolioUrl, true),
size: portfolioAnswer?.fileSize ?? 0,
downloadUrl: portfolioUrl,
}
: undefined;

const detail: Applicant = {
id: app.applicationId.toString(),
name: app.name,
selfIntroductionContent: { title: '자기소개서', content: [] },
...(portfolioFile && { portfolioFile }),
interviewQuestions: [],
docsComments: [],
interviewComments: [],
interviewContent: { title: '면접 평가', content: [] },
};

return (
<div className={pageContainer}>
<div className={styles.container1}>
<Text variant="xl_title_semibold" color="black">
{date.slice(5).replace('-', '/')} | {startTime}~{endTime} |{' '}
{applications.map((a) => a.name).join(', ')}
{applicants.map((a) => a.name).join(', ')}
</Text>

<Flex direction="column" align="center" gap="2.4rem" width="100%">
<ApplicantSliderHeader
name={app.name}
current={current + 1}
onViewApplication={() => {
router.push(
`/apply-management/interviews/${app.applicationId}?recruitmentId=${recruitmentId}`
);
}}
applicants={toggleApplicants}
currentId={app.applicationId}
onSelect={handleToggleSelect}
<ApplicantSliderHeader
name={applicant.name}
current={current + 1}
isOtherUser={false}
applicants={applicants.map((a) => ({
id: a.applicationId,
name: a.name,
imageUrl: '',
}))}
currentId={applicant.applicationId}
onSelect={handleSelectApplicant}
/>

<Flex gap="2rem" width="100%" marginTop="2.4rem">
{/* 좌측: 지원자 상세 */}
<ApplicantDetail
application={data}
scheduleMap={{}}
interviewDuration={rec.interviewDuration}
applicantMap={{}}
questions={rec.applicationQuestions}
/>
<ApplicantDetailContent detail={detail} />

{/* 우측: 카드 영역 (완전히 동일) */}
<div className={styles.rightSection}>
<EvaluationScoreCard
evaluationType="document"
averageScore={data.documentAverageScore}
evaluation={documentEvalStatus}
completed={documentCompletedForCard}
/>

<EvaluationScoreCard
evaluationType="interview"
averageScore={data.interviewAverageScore}
evaluation={interviewEvalStatus}
completed={interviewCompletedForCard}
/>

<EvaluationCommentCard
type="DOCUMENT_COMMENT"
comments={data.documentComments.map((c) => ({
evaluator: c.user.name,
comment: c.content,
profileColor: c.user.profileColor,
profileUrl: c.user.profileImageUrl,
}))}
/>

<EvaluationCommentCard
type="INTERVIEW_COMMENT"
comments={data.interviewComments.map((c) => ({
evaluator: c.user.name,
comment: c.content,
profileColor: c.user.profileColor,
profileUrl: c.user.profileImageUrl,
}))}
/>

<EvaluationCommentCard
type="INTERVIEW_QUESTION"
comments={data.interviewQuestions.map((q) => ({
evaluator: q.user.name,
comment: q.content,
profileColor: q.user.profileColor,
profileUrl: q.user.profileImageUrl,
}))}
/>

<RelationCard
relations={data.acquaintances.map((a) => ({
name: a.name,
profileColor: a.profileColor,
profileUrl: a.profileImageUrl ?? null,
}))}
/>
</div>
</Flex>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
import { vars } from "@repo/theme";
import { style } from "@vanilla-extract/css";

export const pageContainer = style({
width: '100%'
})
})


export const container1 = style({
width: '100%',
backgroundColor: vars.colors.bg,
display: 'flex',
//gap: '4rem',
flexDirection: 'column',
});

export const rightSection = style({
display: 'flex',
flexDirection: 'column',
gap: '2rem',
});