diff --git a/api/interview.ts b/api/interview.ts index 4e8770b..b08f40a 100644 --- a/api/interview.ts +++ b/api/interview.ts @@ -8,6 +8,11 @@ import { ApiResponseMemberInterviewStatusDTO, endInterviewRequestDTO, ApiResponseInterviewEndResultDTO, + InterviewOptionUpdateDTO, + ApiResponseInterviewOptionUpdateResponseDTO, + ApiResponseListInterviewGroupCardDTO, + ApiResponseGroupInterviewDetailDTO, + ApiResponseInterviewStartResponseDTO, } from "./types/interview-types"; // 면접 생성 @@ -29,6 +34,39 @@ export async function createMemberInterview( >(`/interviews/${interviewId}`, data); } +// 면접 옵션 수정 +export async function updateInterviewOption( + interviewId: number, + memberId: number, + data: InterviewOptionUpdateDTO +) { + return serverFetch.patch< + ApiResponseInterviewOptionUpdateResponseDTO, + InterviewOptionUpdateDTO + >(`/interviews/${interviewId}/option`, data, { memberId }); +} + +// 1대다 면접 모집글 리스트 조회 +export async function getGroupInterviewCards() { + return serverFetch.get( + "/interviews/group" + ); +} + +// 1대다 면접 모집글 상세 조회 +export async function getGroupInterviewDetail(interviewId: number) { + return serverFetch.get( + `/interviews/group/${interviewId}` + ); +} + +// 면접 시작 API +export async function startInterview(interviewId: number) { + return serverFetch.get( + `/interviews/${interviewId}/start` + ); +} + // 대기실 내 사용자 상태 변경 export async function changeParticipantsStatus( interviewId: number, diff --git a/api/types/interview-types.ts b/api/types/interview-types.ts index 275d8d7..5d37fe3 100644 --- a/api/types/interview-types.ts +++ b/api/types/interview-types.ts @@ -105,3 +105,138 @@ export interface InterviewEndResultDTO { interviewId?: number; endedAt?: string; } + +export interface InterviewOptionUpdateDTO { + voiceType: + | "MALE20" + | "MALE30" + | "MALE40" + | "MALE50" + | "FEMALE20" + | "FEMALE30" + | "FEMALE40" + | "FEMALE50"; + questionNumber: number; + answerTime: number; +} + +export interface ApiResponseInterviewOptionUpdateResponseDTO { + isSuccess?: boolean; + code?: string; + message?: string; + result?: InterviewOptionUpdateResponseDTO; +} + +export interface InterviewOptionUpdateResponseDTO { + interviewId?: number; + interviewOptionId?: number; + voiceType?: + | "MALE20" + | "MALE30" + | "MALE40" + | "MALE50" + | "FEMALE20" + | "FEMALE30" + | "FEMALE40" + | "FEMALE50"; + questionNumber?: number; + answerTime?: number; +} + +// 1대다(그룹) 면접 관련 타입 +export interface ApiResponseListInterviewGroupCardDTO { + isSuccess?: boolean; + code?: string; + message?: string; + result?: InterviewGroupCardDTO[]; +} + +export interface InterviewGroupCardDTO { + interviewId?: number; + name?: string; + description?: string; + sessionName?: string; + jobName?: string; + interviewType?: "PERSONALITY" | "TECHNICAL"; + currentParticipants?: number; + maxParticipants?: number; + startedAt?: string; +} + +export interface ApiResponseGroupInterviewDetailDTO { + isSuccess?: boolean; + code?: string; + message?: string; + result?: GroupInterviewDetailDTO; +} + +export interface GroupInterviewDetailDTO { + interviewId?: number; + name?: string; + description?: string; + sessionName?: string; + jobName?: string; + interviewType?: "PERSONALITY" | "TECHNICAL"; + maxParticipants?: number; + currentParticipants?: number; + startedAt?: string; + hostName?: string; + groupInterviewParticipants?: GroupInterviewParticipantDTO[]; +} + +export interface GroupInterviewParticipantDTO { + memberId?: number; + name?: string; + submitted?: boolean; + host?: boolean; +} + +// 면접 시작 관련 타입 +export interface ApiResponseInterviewStartResponseDTO { + isSuccess?: boolean; + code?: string; + message?: string; + result?: InterviewStartResponseDTO; +} + +export interface InterviewStartResponseDTO { + interviewId?: number; + interview?: InterviewDTO; + options?: InterviewOptionDTO; + participants?: ParticipantDTO[]; +} + +export interface InterviewDTO { + interviewId?: number; + corporateName?: string; + jobName?: string; + startType?: "NOW" | "SCHEDULED"; + participantCount?: number; +} + +export interface InterviewOptionDTO { + interviewFormat?: "INDIVIDUAL" | "GROUP"; + interviewType?: "PERSONALITY" | "TECHNICAL"; + voiceType?: + | "MALE20" + | "MALE30" + | "MALE40" + | "MALE50" + | "FEMALE20" + | "FEMALE30" + | "FEMALE40" + | "FEMALE50"; + questionNumber?: number; + answerTime?: number; +} + +export interface ParticipantDTO { + memberInterviewId?: number; + resumeDTO?: ResumeSimpleDTO; + coverLetterDTO?: any; // CoverletterDetailDTO 등 실제 타입에 맞게 수정 필요 +} + +export interface ResumeSimpleDTO { + resumeId?: number; + fileUrl?: string; +} diff --git a/app/login/page.tsx b/app/login/page.tsx index d08fbfd..703b232 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -41,7 +41,17 @@ export default function LoginPage() { - diff --git a/app/workspace/interview/group/community/[id]/join/page.tsx b/app/workspace/interview/group/community/[id]/join/page.tsx index 81ca13e..e9f3c6a 100644 --- a/app/workspace/interview/group/community/[id]/join/page.tsx +++ b/app/workspace/interview/group/community/[id]/join/page.tsx @@ -1,49 +1,88 @@ "use client" -import { useState } from "react" +import { useState, use as usePromise } from "react" import Link from "next/link" import { Button } from "@/components/ui/button" import { ArrowLeft, ArrowRight, Info } from "lucide-react" import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Label } from "@/components/ui/label" +import { useQuery, useMutation } from '@tanstack/react-query' +import { getGroupInterviewDetail } from '@/api/interview' +import { getResumeList } from '@/api/resume' +import { findMyCoverletter } from '@/api/coverletter' +import { createMemberInterview } from '@/api/interview' +import { useMemberSession } from '@/components/member-session-context' -export default function JoinGroupInterviewPage({ params }: { params: { id: string } }) { - const postId = params.id +export default function JoinGroupInterviewPage({ params }: { params: Promise<{ id: string }> }) { + const { id: postId } = usePromise(params) + const { memberId } = useMemberSession() const [selectedResume, setSelectedResume] = useState("") const [selectedCoverLetter, setSelectedCoverLetter] = useState("") - // Mock data - const interviewInfo = { - title: "삼성전자 상반기 공채 대비 모의면접", - date: "2023년 6월 10일 오후 2:00", - host: "김지원", - participants: 2, - maxParticipants: 4, - } + // 면접 상세정보 + const { data: interviewData, isLoading: interviewLoading, error: interviewError } = + useQuery({ + queryKey: ['groupInterviewDetail', postId], + queryFn: () => getGroupInterviewDetail(Number(postId)), + }) + const post = interviewData?.result - const resumes = [ - { id: "1", name: "신입 개발자 이력서.pdf" }, - { id: "2", name: "포트폴리오_2023.pdf" }, - { id: "3", name: "경력기술서_최종.docx" }, - ] + // 이력서 리스트 + const { data: resumeData, isLoading: resumeLoading, error: resumeError } = + useQuery({ + queryKey: ['resumeList', memberId], + queryFn: () => getResumeList(memberId!), + enabled: !!memberId, + }) + const resumes = resumeData?.result?.resumes || [] - const coverLetters = [ - { id: "1", name: "삼성전자 자기소개서.pdf" }, - { id: "2", name: "네이버 지원 자소서.docx" }, - { id: "3", name: "카카오 인턴십 자소서" }, - { id: "4", name: "현대자동차 공채 지원서.pdf" }, - ] + // 자기소개서 리스트 + const { data: coverletterData, isLoading: coverletterLoading, error: coverletterError } = + useQuery({ + queryKey: ['coverletterList', memberId], + queryFn: () => findMyCoverletter(memberId!), + enabled: !!memberId, + }) + const coverLetters = coverletterData?.result?.coverletters || [] - const handleSubmit = () => { - if (!selectedResume || !selectedCoverLetter) { - alert("이력서와 자기소개서를 모두 선택해주세요.") - return - } + // 본인 신청 여부 확인 + const isAlreadyApplied = post?.groupInterviewParticipants?.some( + (p) => p.memberId === memberId + ) + + // 신청 mutation + const mutation = useMutation({ + mutationFn: async () => { + if (!memberId) throw new Error('로그인이 필요합니다.') + if (!selectedResume || !selectedCoverLetter) throw new Error('이력서와 자기소개서를 모두 선택해주세요.') + return createMemberInterview(Number(postId), { + memberId, + resumeId: Number(selectedResume), + coverletterId: Number(selectedCoverLetter), + }) + }, + onSuccess: () => { + alert('면접 참여 신청이 완료되었습니다. 면접은 예정된 시간에 자동으로 시작됩니다.') + window.location.href = `/workspace/interview/group/community/${postId}` + }, + onError: (err: any) => { + alert(err?.message || '면접 참여 신청에 실패했습니다.') + }, + }) - // 면접 참여 신청 처리 - alert("면접 참여 신청이 완료되었습니다. 면접은 예정된 시간에 자동으로 시작됩니다.") - window.location.href = `/workspace/interview/group/community/${postId}` + // 에러/호스트 본인 체크 후 뒤로 이동 처리 + if (!interviewLoading && !resumeLoading && !coverletterLoading) { + if (interviewError || resumeError || coverletterError || !post) { + alert('데이터를 불러오지 못했습니다.') + if (typeof window !== 'undefined') window.history.back() + return null + } + if (memberId && post.hostName && String(memberId) === post.hostName) { + alert('본인은 자신의 면접에 참여할 수 없습니다.') + if (typeof window !== 'undefined') window.history.back() + return null + } } return ( @@ -58,7 +97,7 @@ export default function JoinGroupInterviewPage({ params }: { params: { id: strin 모집글로 돌아가기

면접 참여 신청

-

{interviewInfo.title}

+

{post?.name ?? ''}

{/* Main Content */} @@ -73,7 +112,7 @@ export default function JoinGroupInterviewPage({ params }: { params: { id: strin

면접 참여 안내

- 면접에 사용할 이력서와 자기소개서를 선택해주세요. 면접은 {interviewInfo.date}에 자동으로 시작됩니다. + 면접에 사용할 이력서와 자기소개서를 선택해주세요. 면접은 {post?.startedAt && new Date(post?.startedAt).toLocaleString('ko-KR')}에 자동으로 시작됩니다.

@@ -85,20 +124,20 @@ export default function JoinGroupInterviewPage({ params }: { params: { id: strin
면접 제목 - {interviewInfo.title} + {post?.name ?? ''}
면접 일시 - {interviewInfo.date} + {post?.startedAt && new Date(post?.startedAt).toLocaleString('ko-KR')}
호스트 - {interviewInfo.host} + {post?.hostName ?? ''}
참가자 - {interviewInfo.participants}/{interviewInfo.maxParticipants}명 + {post?.currentParticipants ?? 0}/{post?.maxParticipants ?? 0}명
@@ -112,11 +151,15 @@ export default function JoinGroupInterviewPage({ params }: { params: { id: strin - {resumes.map((resume) => ( - - {resume.name} - - ))} + {resumes.length === 0 ? ( +
이력서가 없습니다
+ ) : ( + resumes.map((resume) => ( + + {resume.fileName} + + )) + )}
@@ -128,11 +171,15 @@ export default function JoinGroupInterviewPage({ params }: { params: { id: strin - {coverLetters.map((letter) => ( - - {letter.name} - - ))} + {coverLetters.length === 0 ? ( +
자기소개서가 없습니다
+ ) : ( + coverLetters.map((letter) => ( + + {letter.corporateName} {letter.jobName} + + )) + )}
@@ -143,14 +190,24 @@ export default function JoinGroupInterviewPage({ params }: { params: { id: strin - +
+ {isAlreadyApplied ? ( + + ) : ( + + )} +
diff --git a/app/workspace/interview/group/community/[id]/page.tsx b/app/workspace/interview/group/community/[id]/page.tsx index ecd6aa2..95fa577 100644 --- a/app/workspace/interview/group/community/[id]/page.tsx +++ b/app/workspace/interview/group/community/[id]/page.tsx @@ -8,79 +8,49 @@ import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/componen import { Badge } from "@/components/ui/badge" import { Avatar, AvatarFallback } from "@/components/ui/avatar" import { HeaderWithNotifications } from '@/components/header-with-notifications' +import { getGroupInterviewDetail } from '@/api/interview' +import type { GroupInterviewDetailDTO } from '@/api/types/interview-types' +import { useQuery } from '@tanstack/react-query' +import { useMemberSession } from '@/components/member-session-context' +import { use as usePromise } from "react" -export default function InterviewPostDetailPage({ params }: { params: { id: string } }) { - const postId = params.id - const [timeLeft, setTimeLeft] = useState("") - const [isHost, setIsHost] = useState(true) // For demo purposes, set to true +export default function InterviewPostDetailPage({ params }: { params: Promise<{ id: string }> }) { + const { id: postId } = usePromise(params) - // Mock data for the interview post - const post = { - id: Number.parseInt(postId), - title: "삼성전자 상반기 공채 대비 모의면접", - field: "개발", - date: "2023-06-10T14:00:00", - currentParticipants: 2, - maxParticipants: 4, - type: "technical", - description: - "삼성전자 개발직군 공채를 준비하는 분들과 함께 모의면접을 진행하려고 합니다. 알고리즘, 자료구조, CS 기초 지식 등을 중점적으로 다룰 예정입니다. 각자 준비한 자기소개서를 바탕으로 질문을 주고받을 예정이니 참여하실 분들은 자기소개서를 미리 준비해주세요.", - host: { - id: 1, - name: "김지원", - }, - participants: [ - { - id: 1, - name: "김지원", - isHost: true, - hasSubmittedDocs: true, - }, - { - id: 2, - name: "이민수", - isHost: false, - hasSubmittedDocs: false, - }, - ], - } + const { data, isLoading, error } = useQuery({ + queryKey: ['groupInterviewDetail', postId], + queryFn: () => getGroupInterviewDetail(Number(postId)), + }) + const post: GroupInterviewDetailDTO | null = data?.result ?? null - // Mock data for recommended posts - const recommendedPosts = [ - { - id: 3, - title: "금융권 취업 준비 모임", - field: "금융", - date: "2023-06-15T10:00:00", - currentParticipants: 3, - maxParticipants: 5, - }, - { - id: 5, - title: "IT 대기업 기술면접 대비", - field: "개발", - date: "2023-06-14T16:00:00", - currentParticipants: 2, - maxParticipants: 5, - }, - ] + // 로그인 유저 정보 + const { memberId } = useMemberSession() + // 호스트 판별: hostName(닉네임/이름) 비교만 사용 + const isHost = !!( + memberId && post && post.hostName && String(memberId) === post.hostName + ) + + const [timeLeft, setTimeLeft] = useState("") - // Calculate time left until the interview + // 본인 신청 여부 확인 + const isAlreadyApplied = post?.groupInterviewParticipants?.some( + (p) => p.memberId === memberId + ) + + // 면접까지 남은 시간 계산 useEffect(() => { + if (!post?.startedAt) return const calculateTimeLeft = () => { - const interviewDate = new Date(post.date) + const interviewDate = new Date(post.startedAt!) const now = new Date() const difference = interviewDate.getTime() - now.getTime() - if (difference <= 0) { setTimeLeft("면접 시간이 지났습니다") return } - const days = Math.floor(difference / (1000 * 60 * 60 * 24)) const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)) - if (days > 0) { setTimeLeft(`${days}일 ${hours}시간 남음`) } else if (hours > 0) { @@ -89,15 +59,13 @@ export default function InterviewPostDetailPage({ params }: { params: { id: stri setTimeLeft(`${minutes}분 남음`) } } - calculateTimeLeft() - const timer = setInterval(calculateTimeLeft, 60000) // Update every minute - + const timer = setInterval(calculateTimeLeft, 60000) return () => clearInterval(timer) - }, [post.date]) + }, [post?.startedAt]) - // Format date for display - function formatDate(dateString: string) { + function formatDate(dateString?: string) { + if (!dateString) return "" const options: Intl.DateTimeFormatOptions = { year: "numeric", month: "long", @@ -109,15 +77,42 @@ export default function InterviewPostDetailPage({ params }: { params: { id: stri } const handleJoin = () => { - // 면접 참여 신청 페이지로 이동 window.location.href = `/workspace/interview/group/community/${postId}/join` } - const handleCreateInterview = () => { - // 호스트만 면접 생성 및 수정 가능 window.location.href = `/workspace/interview/group/community/create` } + // 추천 모집글 mock 유지 + const recommendedPosts = [ + { + id: 3, + title: "금융권 취업 준비 모임", + field: "금융", + date: "2023-06-15T10:00:00", + currentParticipants: 3, + maxParticipants: 5, + }, + { + id: 5, + title: "IT 대기업 기술면접 대비", + field: "개발", + date: "2023-06-14T16:00:00", + currentParticipants: 2, + maxParticipants: 5, + }, + ] + + if (isLoading) return
로딩 중...
+ if (error || !post) { + let msg = '데이터 없음' + if (error) { + if (typeof error === 'string') msg = error + else if (error instanceof Error) msg = error.message + } + return
{msg}
+ } + return ( <> @@ -131,21 +126,25 @@ export default function InterviewPostDetailPage({ params }: { params: { id: stri > 다대다 면접 모집으로 돌아가기 -

{post.title}

+

{post.name}

- {post.type === "technical" ? "기술 면접" : "인성 면접"} + {post.interviewType === "TECHNICAL" ? "기술 면접" : "인성 면접"} - {post.field} - - • 작성자: {post.host.name} - + {post.jobName && ( + {post.jobName} + )} + {post.hostName && ( + + • 작성자: {post.hostName} + + )}
@@ -162,7 +161,7 @@ export default function InterviewPostDetailPage({ params }: { params: { id: stri

면접 일시

-

{formatDate(post.date)}

+

{formatDate(post.startedAt)}

@@ -194,9 +193,11 @@ export default function InterviewPostDetailPage({ params }: { params: { id: stri {isHost ? ( - ) : post.currentParticipants < post.maxParticipants ? ( + ) : isAlreadyApplied ? ( + + ) : post.currentParticipants && post.maxParticipants && post.currentParticipants < post.maxParticipants ? ( @@ -213,26 +214,31 @@ export default function InterviewPostDetailPage({ params }: { params: { id: stri
- {post.participants.map((participant) => ( -
-
- - - {participant.name.charAt(0)} - - - {participant.name} -
-
- {participant.hasSubmittedDocs ? ( - 자료 제출 완료 - ) : ( - 자료 미제출 - )} - {participant.isHost && 호스트} + {post.groupInterviewParticipants && post.groupInterviewParticipants.length > 0 ? ( + post.groupInterviewParticipants.map((participant, idx) => ( +
+
+ + + {participant.name?.charAt(0) || '?'} + + + {participant.name} +
+
+ {/* 제출 여부는 submitted로 표시 */} + {participant.submitted ? ( + 자료 제출 완료 + ) : ( + 자료 미제출 + )} + {participant.host && 호스트} +
-
- ))} + )) + ) : ( +
아직 참여자가 없습니다.
+ )}
@@ -254,15 +260,17 @@ export default function InterviewPostDetailPage({ params }: { params: { id: stri {timeLeft}

- 면접은 {formatDate(post.date)}에 자동으로 시작됩니다. + 면접은 {formatDate(post.startedAt)}에 자동으로 시작됩니다.

{isHost ? ( - ) : post.currentParticipants < post.maxParticipants ? ( + ) : isAlreadyApplied ? ( + + ) : post.currentParticipants && post.maxParticipants && post.currentParticipants < post.maxParticipants ? ( diff --git a/app/workspace/interview/group/community/page.tsx b/app/workspace/interview/group/community/page.tsx index e697ef9..501fe35 100644 --- a/app/workspace/interview/group/community/page.tsx +++ b/app/workspace/interview/group/community/page.tsx @@ -10,92 +10,48 @@ import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { HeaderWithNotifications } from "@/components/header-with-notifications" import { CommunityLayout } from "@/components/community-layout" +import { getGroupInterviewCards } from "@/api/interview" +import { useQuery } from "@tanstack/react-query" export default function InterviewCommunityPage() { + const { data, isLoading, error } = useQuery({ + queryKey: ["interviewPosts"], + queryFn: () => getGroupInterviewCards(), + }) + + // API 데이터 + const posts = data?.result || [] + const [dateFilter, setDateFilter] = useState("all") const [fieldFilter, setFieldFilter] = useState("all") const [typeFilter, setTypeFilter] = useState("all") const [searchQuery, setSearchQuery] = useState("") - // Mock data for interview posts - const interviewPosts = [ - { - id: 1, - title: "삼성전자 상반기 공채 대비 모의면접", - field: "개발", - date: "2023-06-10T14:00:00", - currentParticipants: 2, - maxParticipants: 4, - type: "technical", - }, - { - id: 2, - title: "마케팅 직무 인성 면접 스터디", - field: "마케팅", - date: "2023-06-12T15:30:00", - currentParticipants: 1, - maxParticipants: 3, - type: "personality", - }, - { - id: 3, - title: "금융권 취업 준비 모임", - field: "금융", - date: "2023-06-15T10:00:00", - currentParticipants: 3, - maxParticipants: 5, - type: "technical", - }, - { - id: 4, - title: "디자인 포트폴리오 리뷰 및 모의면접", - field: "디자인", - date: "2023-06-11T13:00:00", - currentParticipants: 2, - maxParticipants: 4, - type: "personality", - }, - { - id: 5, - title: "IT 대기업 기술면접 대비", - field: "개발", - date: "2023-06-14T16:00:00", - currentParticipants: 2, - maxParticipants: 5, - type: "technical", - }, - { - id: 6, - title: "공기업 인적성 및 면접 준비", - field: "공공기관", - date: "2023-06-16T11:00:00", - currentParticipants: 1, - maxParticipants: 5, - type: "personality", - }, - ] - - // Filter posts based on selected filters and search query - const filteredPosts = interviewPosts.filter((post) => { - const matchesDate = dateFilter === "all" || isDateMatch(post.date, dateFilter) - const matchesField = fieldFilter === "all" || post.field === fieldFilter - const matchesType = typeFilter === "all" || post.type === typeFilter + const filteredPosts = posts.filter((post) => { + // 날짜 필터 + const matchesDate = + dateFilter === "all" || (post.startedAt ? isDateMatch(post.startedAt, dateFilter) : false) + // 유형 필터 + const matchesType = + typeFilter === "all" || + (typeFilter === "technical" && post.interviewType === "TECHNICAL") || + (typeFilter === "personality" && post.interviewType === "PERSONALITY") + // 검색 (제목/직무) const matchesSearch = searchQuery === "" || - post.title.toLowerCase().includes(searchQuery.toLowerCase()) || - post.field.toLowerCase().includes(searchQuery.toLowerCase()) + (post.name?.toLowerCase().includes(searchQuery.toLowerCase()) || + post.jobName?.toLowerCase().includes(searchQuery.toLowerCase())) - return matchesDate && matchesField && matchesType && matchesSearch + return matchesDate && matchesType && matchesSearch }) - // Helper function to check if a date matches the selected filter function isDateMatch(dateString: string, filter: string) { const postDate = new Date(dateString) const today = new Date() const tomorrow = new Date(today) - tomorrow.setDate(tomorrow.getDate() + 1) + tomorrow.setDate(today.getDate() + 1) const nextWeek = new Date(today) - nextWeek.setDate(nextWeek.getDate() + 7) + nextWeek.setDate(today.getDate() + 7) if (filter === "today") { return ( @@ -116,7 +72,8 @@ export default function InterviewCommunityPage() { } // Format date for display - function formatDate(dateString: string) { + function formatDate(dateString?: string) { + if (!dateString) return "" const options: Intl.DateTimeFormatOptions = { month: "long", day: "numeric", @@ -207,27 +164,33 @@ export default function InterviewCommunityPage() { {/* Interview Posts */}
- {filteredPosts.length > 0 ? ( + {isLoading ? ( +
로딩 중...
+ ) : error ? ( +
모집글을 불러오지 못했습니다.
+ ) : filteredPosts.length > 0 ? ( filteredPosts.map((post) => ( - +
- {post.type === "technical" ? "기술 면접" : "인성 면접"} + {post.interviewType === "TECHNICAL" ? "기술 면접" : "인성 면접"} - {post.field} + {post.jobName && ( + {post.jobName} + )}
-

{post.title}

+

{post.name}

- {formatDate(post.date)} + {formatDate(post.startedAt)}
@@ -241,12 +204,11 @@ export default function InterviewCommunityPage() { variant="ghost" size="sm" className="text-[#8FD694] hover:text-white hover:bg-[#8FD694]" - onClick={(e) => { - e.preventDefault() - window.location.href = `/workspace/interview/group/community/${post.id}` - }} + asChild > - 상세보기 + + 상세보기 +
diff --git a/components/interview/steps/Step4Group.tsx b/components/interview/steps/Step4Group.tsx index d635c07..9f0fae9 100644 --- a/components/interview/steps/Step4Group.tsx +++ b/components/interview/steps/Step4Group.tsx @@ -89,7 +89,12 @@ export default function Step4Group({ form, setForm }: Props) { if (!d) return const today = new Date() today.setHours(0, 0, 0, 0) - if (d >= today) patch({ scheduledDate: d }) + if (d >= today) { + patch({ + startType: "scheduled", + scheduledDate: d, + }) + } }} disabled={(d) => { const today = new Date()