Feat: 리뷰 디테일 구현, test 못함
This commit is contained in:
parent
745a1198d8
commit
ea344dca19
@ -1,86 +1,152 @@
|
||||
import useReviewDetailQuery from '@/queries/reviews/useReviewDetailQuery';
|
||||
import useUpdateReviewQuery from '@/queries/reviews/useUpdateReviewQuery';
|
||||
import useDeleteReviewQuery from '@/queries/reviews/useDeleteReviewQuery';
|
||||
import { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import Slider from 'react-slick';
|
||||
import useReviewDetailQuery from '@/queries/reviews/useReviewDetailQuery';
|
||||
import useUpdateReviewStatusQuery from '@/queries/reviews/useUpdateReviewStatusQuery';
|
||||
import useProjectMembersQuery from '@/queries/projects/useProjectMembersQuery';
|
||||
import useAuthStore from '@/stores/useAuthStore';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import 'slick-carousel/slick/slick.css';
|
||||
import 'slick-carousel/slick/slick-theme.css';
|
||||
|
||||
export default function ReviewDetail() {
|
||||
export default function ReviewDetail(): JSX.Element {
|
||||
const { projectId, reviewId } = useParams<{ projectId: string; reviewId: string }>();
|
||||
const memberId = 1;
|
||||
const profile = useAuthStore((state) => state.profile);
|
||||
const memberId = profile?.id || 0;
|
||||
|
||||
const { data: reviewDetail } = useReviewDetailQuery(Number(projectId), Number(reviewId), memberId);
|
||||
const updateReview = useUpdateReviewQuery();
|
||||
const deleteReview = useDeleteReviewQuery();
|
||||
const { data: projectMembers } = useProjectMembersQuery(Number(projectId), memberId);
|
||||
|
||||
const handleUpdate = () => {
|
||||
updateReview.mutate({
|
||||
const updateReviewStatus = useUpdateReviewStatusQuery();
|
||||
const [activeTab, setActiveTab] = useState<'content' | 'images'>('content');
|
||||
|
||||
const handleApprove = () => {
|
||||
updateReviewStatus.mutate({
|
||||
projectId: Number(projectId),
|
||||
reviewId: Number(reviewId),
|
||||
memberId,
|
||||
reviewData: {
|
||||
title: reviewDetail.title,
|
||||
content: reviewDetail.content,
|
||||
imageIds: reviewDetail.images.map((image) => image.id),
|
||||
},
|
||||
reviewStatus: 'APPROVED',
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteReview.mutate({
|
||||
const handleReject = () => {
|
||||
updateReviewStatus.mutate({
|
||||
projectId: Number(projectId),
|
||||
reviewId: Number(reviewId),
|
||||
memberId,
|
||||
reviewStatus: 'REJECTED',
|
||||
});
|
||||
};
|
||||
|
||||
if (!reviewDetail) return <p>Loading...</p>;
|
||||
|
||||
const { title, content, reviewStatus, images } = reviewDetail;
|
||||
const settings = {
|
||||
dots: true,
|
||||
infinite: true,
|
||||
speed: 500,
|
||||
slidesToShow: 1,
|
||||
slidesToScroll: 1,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 bg-white p-6">
|
||||
<header className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold text-[#333238]">{title}</h1>
|
||||
<div className="rounded-full bg-[#cbe2f9] px-3 py-0.5 text-xs text-[#0b5cad]">{reviewStatus}</div>
|
||||
</header>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-sm text-[#737278]">by 김용수</p>
|
||||
<p className="text-sm text-[#737278]">|</p>
|
||||
<p className="text-sm text-[#737278]">8 hours ago</p>
|
||||
<div className="review-detail-container p-4">
|
||||
<div className="header mb-4">
|
||||
<h1 className="text-2xl font-bold">{reviewDetail.title}</h1>
|
||||
<p className="text-sm text-gray-500">
|
||||
작성자: {reviewDetail.nickname} ({reviewDetail.email})
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">작성일: {new Date(reviewDetail.createAt).toLocaleDateString()}</p>
|
||||
<p className="text-sm text-gray-500">수정일: {new Date(reviewDetail.updateAt).toLocaleDateString()}</p>
|
||||
</div>
|
||||
|
||||
<div className="border-b pb-4">
|
||||
<h2 className="text-xl font-semibold">내용</h2>
|
||||
<p className="mt-2 text-sm text-[#333238]">{content}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<h2 className="text-xl font-semibold">이미지 목록</h2>
|
||||
<ul className="mt-2 list-inside list-disc">
|
||||
{images.map((image) => (
|
||||
<li
|
||||
key={image.id}
|
||||
className="text-sm text-[#737278]"
|
||||
<div className="relative w-full px-4">
|
||||
<div className="flex w-full items-center border-b-[0.67px] border-solid border-[#dcdcde]">
|
||||
{['content', 'images'].map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
className={`flex h-12 w-[100px] items-center justify-center px-3 ${
|
||||
activeTab === tab ? 'shadow-[inset_0px_-2px_0px_#1f75cb]' : ''
|
||||
}`}
|
||||
onClick={() => setActiveTab(tab as 'content' | 'images')}
|
||||
>
|
||||
{image.imageTitle} (status: {image.status})
|
||||
<span className={`text-sm ${activeTab === tab ? 'font-semibold' : 'font-normal'} text-[#333238]`}>
|
||||
{tab === 'content' ? '내용' : '이미지'}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="content mt-4">
|
||||
{activeTab === 'content' ? (
|
||||
<p className="text-gray-700">{reviewDetail.content}</p>
|
||||
) : (
|
||||
<div className="images mt-4">
|
||||
{reviewDetail.images.length > 0 ? (
|
||||
<Slider {...settings}>
|
||||
{reviewDetail.images.map((image) => (
|
||||
<div key={image.id}>
|
||||
<img
|
||||
src={image.imagePath}
|
||||
alt="리뷰 이미지"
|
||||
className="h-auto w-full rounded"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Slider>
|
||||
) : (
|
||||
<p className="text-gray-500">이미지가 없습니다.</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{reviewDetail.reviewStatus === 'APPROVED' && (
|
||||
<div className="reviewer-info mt-6">
|
||||
<h2 className="text-lg font-semibold">리뷰어</h2>
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={reviewDetail.reviewerProfileImage}
|
||||
alt="리뷰어 프로필"
|
||||
className="h-10 w-10 rounded-full"
|
||||
/>
|
||||
<div className="ml-4">
|
||||
<p className="font-bold">{reviewDetail.reviewerNickname}</p>
|
||||
<p className="text-gray-500">{reviewDetail.reviewerEmail}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="meta-info mt-6">
|
||||
<h2 className="text-lg font-semibold">프로젝트 멤버</h2>
|
||||
<ul className="list-disc pl-6">
|
||||
{projectMembers.map((member) => (
|
||||
<li
|
||||
key={member.memberId}
|
||||
className="text-gray-700"
|
||||
>
|
||||
{member.nickname} - {member.privilegeType}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={handleUpdate}
|
||||
className="rounded-lg bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
|
||||
<div className="actions mt-6 flex justify-end space-x-2">
|
||||
{reviewDetail.reviewStatus !== 'APPROVED' && (
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={handleApprove}
|
||||
>
|
||||
수정하기
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="rounded-lg bg-red-500 px-4 py-2 text-white hover:bg-red-600"
|
||||
승인
|
||||
</Button>
|
||||
)}
|
||||
{reviewDetail.reviewStatus !== 'REJECTED' && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleReject}
|
||||
>
|
||||
삭제하기
|
||||
</button>
|
||||
거부
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user