-
Notifications
You must be signed in to change notification settings - Fork 0
스터디 문의(기능 추가) 및 스터디 목록/상세 UI 개선 #401
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
Merged
Merged
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
6ef1591
fix : 참가자 항목 카운트 로직 수정
HA-SEUNG-JEONG c704aae
fix : 왕관 UI 수정, 남은 참가자 목록이 호버 시 열리도록 수정
HA-SEUNG-JEONG e185e96
fix : 스터디 상세 페이지 첫 렌더 시 신청하기 버튼 오표시 수정
HA-SEUNG-JEONG 1586b9b
fix : 종료된 스터디에 대한 UI 처리 변경
HA-SEUNG-JEONG a256e55
Merge branch 'develop' of https://github.com/code-zero-to-one/study-p…
HA-SEUNG-JEONG 2174eb6
fix : 네이밍 변경
HA-SEUNG-JEONG 24221aa
fix : 그룹스터디 미신청자는 문의하기 버튼 숨김
HA-SEUNG-JEONG e5c8e2f
feat : 스터디 상세 페이지에 커리큘럼 요약 추가
HA-SEUNG-JEONG dec628d
feat : 최소 길이 추가
HA-SEUNG-JEONG 9846154
fix : 오타 수정, studyType 추가
HA-SEUNG-JEONG 6f23805
fix : null 타입 제거, 인터페이스 네이밍 변경
HA-SEUNG-JEONG fc3c72c
feat : 문의 목록, 문의 상세 조회 추가
HA-SEUNG-JEONG 5e75338
fix : 토스트 뜨는 타이밍 수정
HA-SEUNG-JEONG 55cb27b
feat : 문의 상세 및 문의 목록에 조회 수 추가
HA-SEUNG-JEONG 1d1f46c
feat : 이미지 첨부 컴포넌트 추가 및 해당 컴포넌트 재사용하도록 수정
HA-SEUNG-JEONG caf8389
fix : alert를 toast로 대체
HA-SEUNG-JEONG 15b9b47
feat : 스터디 카드 카운트다운 뱃지 및 상세 전광판 추가
HA-SEUNG-JEONG 51f81a3
feat : 스터디 목록 경험 레벨 필터 추가
HA-SEUNG-JEONG f309c38
feat : 문의 API 타입 확장 (이미지, 조회수, 페이지네이션 필드 추가)
HA-SEUNG-JEONG b011867
style : 코드 포맷팅 수정
HA-SEUNG-JEONG bb513e6
fix : 팝오버 UI 수정
HA-SEUNG-JEONG dc1186b
delete : 주석 제거
HA-SEUNG-JEONG d15a718
fix : axios -> axiosV2로 부분 마이그레이션
HA-SEUNG-JEONG 8121a25
feat: 문의 답변 API 타입 확장 및 useCreateAnswer 훅 추가
HA-SEUNG-JEONG 9a698b5
feat: InquiryStatusBadge 컴포넌트 추가 및 문의 탭 섹션 분리
HA-SEUNG-JEONG ae6308b
feat: 문의 목록/상세 페이지 UI 개선 및 그룹 스터디 상세에 InquirySection 통합
HA-SEUNG-JEONG abdf09f
fix: DetailView UI 개선 및 불필요한 로그 제거
HA-SEUNG-JEONG 7715ac9
fix: 문의 관련 UI 개선 및 날짜 포맷 함수 통합
HA-SEUNG-JEONG d5bb32f
fix: InquiryDetailPage UI 개선 및 답변 등록 모달 제거
HA-SEUNG-JEONG da0e58b
fix: Inquiry 관련 UI 개선 및 InquiryListTable 컴포넌트 추가
HA-SEUNG-JEONG ccf330f
fix: ListView에서 문의하기 버튼 제거
HA-SEUNG-JEONG 664ca1f
fix: InquiryDetailPage에서 잘못된 접근 처리 메시지 추가 및 UI 개선
HA-SEUNG-JEONG f318a26
fix: 빈 줄 추가
HA-SEUNG-JEONG aa62da2
fix : 카테고리 텍스트 수정
HA-SEUNG-JEONG 0382ced
fix: InquiryDetailPage에서 오류 처리 메시지 추가 및 상태 관리 개선
HA-SEUNG-JEONG d7de619
fix: Toast 컴포넌트에 정보 메시지 추가 및 관련 수정
HA-SEUNG-JEONG 91b1ca9
fix: 날짜 문자열 유효성 검사 추가 및 포맷 함수 수정
HA-SEUNG-JEONG File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,7 @@ | ||
| import GlobalToast from '@/components/ui/global-toast'; | ||
|
|
||
| export default function GroupStudyLayout({ | ||
| children, | ||
| }: Readonly<{ | ||
| children: React.ReactNode; | ||
| }>) { | ||
| return ( | ||
| <> | ||
| <GlobalToast /> | ||
| {children} | ||
| </> | ||
| ); | ||
| return <>{children}</>; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,181 @@ | ||
| 'use client'; | ||
|
|
||
| import { ArrowLeft, Eye } from 'lucide-react'; | ||
| import Image from 'next/image'; | ||
| import { useRouter, useSearchParams } from 'next/navigation'; | ||
| import { use } from 'react'; | ||
| import InquiryStatusBadge from '@/components/ui/badge/inquiry-status-badge'; | ||
| import MoreMenu from '@/components/ui/dropdown/more-menu'; | ||
| import { CATEGORY_LABEL } from '@/features/study/group/model/question.schema'; | ||
| import { useGetQuestion } from '@/hooks/queries/question-api'; | ||
| import { useToastStore } from '@/stores/use-toast-store'; | ||
| import { formatDateTimeDot } from '@/utils/time'; | ||
|
|
||
| export default function InquiryDetailPage({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ questionId: string }>; | ||
| }) { | ||
| const { questionId: questionIdStr } = use(params); | ||
| const router = useRouter(); | ||
| const searchParams = useSearchParams(); | ||
| const groupStudyIdStr = searchParams.get('groupStudyId'); | ||
| const groupStudyId = groupStudyIdStr ? Number(groupStudyIdStr) : 0; | ||
| const studyType = searchParams.get('studyType') ?? 'group'; | ||
| const questionId = Number(questionIdStr); | ||
| const showToast = useToastStore((state) => state.showToast); | ||
|
|
||
| const { data, isLoading, isError } = useGetQuestion({ | ||
| groupStudyId, | ||
| questionId, | ||
| }); | ||
|
|
||
| const handleBack = () => { | ||
| router.push(`/inquiry?groupStudyId=${groupStudyId}&studyType=${studyType}`); | ||
| }; | ||
|
|
||
| const moreMenuOptions = [ | ||
| { | ||
| label: '수정하기', | ||
| value: 'edit', | ||
| onMenuClick: () => showToast('준비 중인 기능입니다.', 'info'), | ||
| }, | ||
| { | ||
| label: '삭제하기', | ||
| value: 'delete', | ||
| onMenuClick: () => showToast('준비 중인 기능입니다.', 'info'), | ||
| }, | ||
| ]; | ||
|
|
||
| if (!groupStudyId) { | ||
| return ( | ||
| <div className="mx-auto w-full max-w-7xl px-400 py-600"> | ||
| <div className="text-text-subtle py-800 text-center"> | ||
| 잘못된 접근입니다. 스터디 문의 목록에서 다시 접근해주세요. | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| if (isLoading) { | ||
| return ( | ||
| <div className="mx-auto w-full max-w-7xl px-400 py-600"> | ||
| <div className="text-text-subtle py-800 text-center">로딩 중...</div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| if (isError || (!isLoading && !data)) { | ||
| return ( | ||
| <div className="mx-auto w-full max-w-7xl px-400 py-600"> | ||
| <div className="text-text-subtle py-800 text-center"> | ||
| 문의를 불러오는 중 오류가 발생했습니다. 다시 시도해주세요. | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <div className="mx-auto w-full max-w-7xl px-400 py-600"> | ||
| <div className="mb-400"> | ||
| <button | ||
| onClick={handleBack} | ||
| className="text-text-subtle hover:text-text-default font-designer-14r flex items-center gap-100 transition-colors" | ||
| > | ||
| <ArrowLeft size={16} /> | ||
| 목록으로 | ||
| </button> | ||
| </div> | ||
|
|
||
| {data && ( | ||
HA-SEUNG-JEONG marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <div className="border-border-default rounded-100 border"> | ||
| {/* 문의 헤더 */} | ||
| <div className="px-600 py-400"> | ||
| <div className="mb-200 flex items-start justify-between"> | ||
| <div className="flex flex-col gap-200"> | ||
| {data.category && ( | ||
| <span className="bg-background-accent-gray-subtle text-background-accent-gray-strong font-designer-12m rounded-50 inline-flex w-fit px-100 py-50"> | ||
| {CATEGORY_LABEL[data.category] ?? data.category} | ||
| </span> | ||
| )} | ||
| <h1 className="font-designer-24b text-text-strong"> | ||
| {data.title} | ||
| </h1> | ||
| </div> | ||
| <MoreMenu options={moreMenuOptions} iconSize={20} /> | ||
| </div> | ||
|
|
||
| <div className="font-designer-13r text-text-subtle border-border-default grid grid-cols-2 gap-y-100 border-b pb-300"> | ||
| <div className="flex gap-200"> | ||
| <span className="text-text-subtle w-[60px]">작성자</span> | ||
| <span className="text-text-default">{data.authorNickname}</span> | ||
| </div> | ||
| <div className="flex items-center gap-200"> | ||
| <Eye size={14} className="text-text-subtle" /> | ||
| <span className="text-text-default">{data.viewCount}</span> | ||
| </div> | ||
| <div className="flex gap-200"> | ||
| <span className="text-text-subtle w-[60px]">작성일</span> | ||
| <span className="text-text-default"> | ||
| {formatDateTimeDot(data.createdAt)} | ||
| </span> | ||
| </div> | ||
| <div className="flex items-center gap-200"> | ||
| <InquiryStatusBadge status={data.status} /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* 구분선 */} | ||
| <div className="px-600"> | ||
| <hr className="border-border-default" /> | ||
| </div> | ||
|
|
||
| {/* 문의 내용 */} | ||
| <div className="px-600 py-400"> | ||
| <p className="font-designer-16r text-text-default whitespace-pre-wrap"> | ||
| {data.content} | ||
| </p> | ||
| {data.questionImage?.resizedImages?.[0]?.resizedImageUrl && ( | ||
| <Image | ||
| src={data.questionImage.resizedImages[0].resizedImageUrl} | ||
| alt="문의 이미지" | ||
| width={800} | ||
| height={600} | ||
| className="mt-400 w-full object-contain" | ||
| style={{ height: 'auto' }} | ||
| /> | ||
| )} | ||
| </div> | ||
|
|
||
| {/* 구분선 */} | ||
| <div className="px-600"> | ||
| <hr className="border-border-default" /> | ||
| </div> | ||
|
|
||
| {/* 답변 섹션 */} | ||
| <div className="px-600 py-400"> | ||
| <h2 className="font-designer-16b text-text-strong mb-300">답변</h2> | ||
| {data.answer ? ( | ||
| <div className="flex flex-col gap-200"> | ||
| <div className="font-designer-13r text-text-subtle flex items-center gap-200"> | ||
| <span>{data.answererNickname}</span> | ||
| <span>{formatDateTimeDot(data.answeredAt ?? '')}</span> | ||
| </div> | ||
| <p className="font-designer-14r text-text-default whitespace-pre-wrap"> | ||
| {data.answer} | ||
| </p> | ||
| </div> | ||
| ) : ( | ||
| <div className="border-border-default rounded-200 flex items-center justify-center border bg-white py-500"> | ||
| <p className="font-designer-14r text-text-subtle"> | ||
| 아직 답변이 등록되지 않았습니다. | ||
| </p> | ||
| </div> | ||
| )} | ||
| </div> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| 'use client'; | ||
|
|
||
| import { useRouter, useSearchParams } from 'next/navigation'; | ||
| import { useEffect, useState } from 'react'; | ||
| import InquiryListTable from '@/components/lists/inquiry-list-table'; | ||
| import QuestionModal from '@/components/modals/question-modal'; | ||
| import Button from '@/components/ui/button'; | ||
| import { useGetQuestions } from '@/hooks/queries/question-api'; | ||
|
|
||
| const PAGE_SIZE = 15; | ||
|
|
||
| export default function InquiryPage() { | ||
| const router = useRouter(); | ||
| const searchParams = useSearchParams(); | ||
| const groupStudyIdStr = searchParams.get('groupStudyId'); | ||
| const groupStudyId = groupStudyIdStr ? Number(groupStudyIdStr) : null; | ||
| const studyType = (searchParams.get('studyType') ?? 'group') as | ||
| | 'group' | ||
| | 'premium'; | ||
| const isPremium = studyType === 'premium'; | ||
|
|
||
| const [page, setPage] = useState(1); | ||
| const [isModalOpen, setIsModalOpen] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| if (!groupStudyId) { | ||
| router.replace('/group-study'); | ||
| } | ||
| }, [groupStudyId, router]); | ||
|
|
||
| const handleItemClick = (questionId: number) => { | ||
| router.push( | ||
| `/inquiry/${questionId}?groupStudyId=${groupStudyId}&studyType=${studyType}`, | ||
| ); | ||
| }; | ||
|
|
||
| const { data, isLoading } = useGetQuestions({ | ||
| groupStudyId: groupStudyId ?? 0, | ||
| page, | ||
| pageSize: PAGE_SIZE, | ||
| }); | ||
|
|
||
| if (!groupStudyId) return null; | ||
|
|
||
| const items = data?.content ?? []; | ||
| const totalPages = data?.totalPages ?? 1; | ||
| const totalElements = data?.totalElements ?? 0; | ||
|
|
||
| return ( | ||
| <div className="mx-auto w-full max-w-7xl px-400 py-600"> | ||
| {/* 헤더 */} | ||
| <div className="mb-400 flex items-start justify-between"> | ||
| <div className="flex flex-col gap-75"> | ||
| <h1 className="font-designer-24b text-text-strong"> | ||
| 문의 게시판{' '} | ||
| <span className="font-designer-20b text-text-subtle"> | ||
| {totalElements}개 | ||
| </span> | ||
| </h1> | ||
| <p className="font-designer-14r text-text-subtle"> | ||
| 스터디 관련 문의사항을 남겨주세요 | ||
| </p> | ||
| <p className="font-designer-14r text-text-subtle"> | ||
| 비공개 문의는 작성자, {isPremium ? '멘토' : '리더'}, 관리자만 확인할 | ||
| 수 있어요. | ||
| </p> | ||
| </div> | ||
| <Button color="primary" onClick={() => setIsModalOpen(true)}> | ||
| 문의하기 | ||
| </Button> | ||
| </div> | ||
|
|
||
| {/* 표 */} | ||
| <InquiryListTable | ||
| items={items} | ||
| totalElements={totalElements} | ||
| totalPages={totalPages} | ||
| page={page} | ||
| isLoading={isLoading} | ||
| onPageChange={setPage} | ||
| onItemClick={(item) => handleItemClick(item.questionId)} | ||
| /> | ||
|
|
||
| {/* 문의하기 모달 */} | ||
| <QuestionModal | ||
| open={isModalOpen} | ||
| onOpenChange={setIsModalOpen} | ||
| studyId={groupStudyId} | ||
| studyType={studyType} | ||
| /> | ||
| </div> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,7 @@ | ||
| import GlobalToast from '@/components/ui/global-toast'; | ||
|
|
||
| export default function PremiumStudyLayout({ | ||
| children, | ||
| }: Readonly<{ | ||
| children: React.ReactNode; | ||
| }>) { | ||
| return ( | ||
| <> | ||
| <GlobalToast /> | ||
| {children} | ||
| </> | ||
| ); | ||
| return <>{children}</>; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.