Refactor: ReviewDetail API 응답 수정, 리뷰 리팩토링
This commit is contained in:
parent
69cead34b1
commit
d26fb5327d
@ -2,6 +2,8 @@ import { Briefcase, Tag, Box, Layers } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import useProjectQuery from '@/queries/projects/useProjectQuery';
|
||||
import useAuthStore from '@/stores/useAuthStore';
|
||||
import { cn } from '@/lib/utils';
|
||||
import formatDateTime from '@/utils/formatDateTime';
|
||||
|
||||
interface ReviewItemProps {
|
||||
title: string;
|
||||
@ -42,11 +44,11 @@ export default function ReviewItem({
|
||||
>
|
||||
<div className="flex h-[100px] w-full items-center justify-between border-b-[0.67px] border-[#ececef] bg-[#fbfafd] p-4">
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm font-semibold text-[#333238]">{title}</p>
|
||||
<p className="mt-1 text-xs text-[#737278]">by {creatorName}</p>
|
||||
<p className="text-sm font-semibold text-black">{title}</p>
|
||||
<p className="mt-1 text-xs text-gray-500">by {creatorName}</p>
|
||||
<div className="mt-1 flex items-center">
|
||||
<Briefcase className="h-3 w-3 text-[#737278]" />
|
||||
<p className="ml-1 text-xs text-[#737278]">{projectData?.title}</p>
|
||||
<Briefcase className="h-3 w-3 text-gray-500" />
|
||||
<p className="ml-1 text-xs text-gray-500">{projectData?.title}</p>
|
||||
</div>
|
||||
{type && (
|
||||
<div
|
||||
@ -59,8 +61,19 @@ export default function ReviewItem({
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-end gap-1">
|
||||
<div className="rounded-full bg-[#cbe2f9] px-3 py-0.5 text-center text-xs text-[#0b5cad]">{status}</div>
|
||||
<p className="text-xs text-[#737278]">Created at {createdTime}</p>
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-full px-3 py-0.5 text-center text-xs',
|
||||
status === 'APPROVED'
|
||||
? 'bg-green-100 text-green-600'
|
||||
: status === 'REJECTED'
|
||||
? 'bg-red-100 text-red-600'
|
||||
: 'bg-blue-100 text-blue-600'
|
||||
)}
|
||||
>
|
||||
{status}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">Created at {formatDateTime(createdTime)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
@ -4,8 +4,8 @@ import { ReviewResponse } from '@/types';
|
||||
|
||||
interface ReviewListProps {
|
||||
reviews: ReviewResponse[];
|
||||
activeTab: 'REQUESTED' | 'APPROVED' | 'REJECTED' | 'all';
|
||||
setActiveTab: React.Dispatch<React.SetStateAction<'REQUESTED' | 'APPROVED' | 'REJECTED' | 'all'>>;
|
||||
activeTab: 'REQUESTED' | 'APPROVED' | 'REJECTED' | 'ALL';
|
||||
setActiveTab: React.Dispatch<React.SetStateAction<'REQUESTED' | 'APPROVED' | 'REJECTED' | 'ALL'>>;
|
||||
setSearchQuery: React.Dispatch<React.SetStateAction<string>>;
|
||||
sortValue: string;
|
||||
setSortValue: React.Dispatch<React.SetStateAction<string>>;
|
||||
@ -22,18 +22,18 @@ export default function ReviewList({
|
||||
workspaceId,
|
||||
}: ReviewListProps) {
|
||||
return (
|
||||
<div className="relative w-full">
|
||||
<div className="relative w-full px-4">
|
||||
<div className="flex w-full items-center border-b-[0.67px] border-solid border-[#dcdcde]">
|
||||
{['REQUESTED', 'APPROVED', 'REJECTED', 'all'].map((tab) => (
|
||||
<div className="relative w-full">
|
||||
<div className="flex w-full items-center border-b-[1px] border-solid border-gray-300">
|
||||
{['REQUESTED', 'APPROVED', 'REJECTED', 'ALL'].map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
className={`flex h-12 w-[100px] items-center justify-between px-3 ${
|
||||
activeTab === tab ? 'shadow-[inset_0px_-2px_0px_#1f75cb]' : ''
|
||||
className={`flex h-12 w-[100px] items-center justify-center px-3 ${
|
||||
activeTab === tab ? 'border-b-[3px] border-blue-500' : 'border-b-[3px] border-transparent'
|
||||
}`}
|
||||
onClick={() => setActiveTab(tab as typeof activeTab)}
|
||||
>
|
||||
<span className={`text-sm ${activeTab === tab ? 'font-semibold' : 'font-normal'} text-[#333238]`}>
|
||||
<span className={`text-sm ${activeTab === tab ? 'font-semibold' : 'font-normal'} text-black`}>
|
||||
{tab === 'REQUESTED' ? '요청' : tab === 'APPROVED' ? '승인' : tab === 'REJECTED' ? '거부' : '전체'}
|
||||
</span>
|
||||
</button>
|
||||
@ -41,7 +41,7 @@ export default function ReviewList({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full px-4">
|
||||
<div className="relative w-full">
|
||||
<ReviewSearchInput
|
||||
onSearchChange={setSearchQuery}
|
||||
onSortChange={setSortValue}
|
||||
@ -49,7 +49,7 @@ export default function ReviewList({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full overflow-y-auto px-4">
|
||||
<div className="relative w-full overflow-y-auto">
|
||||
{reviews.length === 0 ? (
|
||||
<div className="py-4 text-center">리뷰가 없습니다.</div>
|
||||
) : (
|
||||
|
@ -10,12 +10,12 @@ export default function ProjectReviewList() {
|
||||
const profile = useAuthStore((state) => state.profile);
|
||||
const memberId = profile?.id || 0;
|
||||
|
||||
const [activeTab, setActiveTab] = useState<'REQUESTED' | 'APPROVED' | 'REJECTED' | 'all'>('REQUESTED');
|
||||
const [activeTab, setActiveTab] = useState<'REQUESTED' | 'APPROVED' | 'REJECTED' | 'ALL'>('REQUESTED');
|
||||
const [, setSearchQuery] = useState('');
|
||||
const [sortValue, setSortValue] = useState('latest');
|
||||
|
||||
const sortDirection = sortValue === 'latest' ? 0 : 1;
|
||||
const reviewStatus = activeTab !== 'all' ? activeTab : undefined;
|
||||
const reviewStatus = activeTab !== 'ALL' ? activeTab : undefined;
|
||||
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch } = useReviewByStatusQuery(
|
||||
Number(projectId),
|
||||
|
@ -60,25 +60,25 @@ export default function ReviewDetail(): JSX.Element {
|
||||
return (
|
||||
<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.author.nickname} ({reviewDetail.author.email})
|
||||
<h1 className="heading mb-2">{reviewDetail.title}</h1>
|
||||
<p className="body-small text-gray-500">
|
||||
작성자 : {reviewDetail.author.nickname} ({reviewDetail.author.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>
|
||||
<p className="body-small text-gray-500">작성일 : {new Date(reviewDetail.createdAt).toLocaleDateString()}</p>
|
||||
<p className="body-small text-gray-500">수정일 : {new Date(reviewDetail.updatedAt).toLocaleDateString()}</p>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full px-4">
|
||||
<div className="flex w-full items-center border-b-[0.67px] border-solid border-[#dcdcde]">
|
||||
<div className="relative w-full">
|
||||
<div className="flex w-full items-center border-b-[1px] border-solid border-gray-300">
|
||||
{['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]' : ''
|
||||
activeTab === tab ? 'border-b-[3px] border-blue-500' : 'border-b-[3px] border-transparent'
|
||||
}`}
|
||||
onClick={() => setActiveTab(tab as 'content' | 'images')}
|
||||
>
|
||||
<span className={`text-sm ${activeTab === tab ? 'font-semibold' : 'font-normal'} text-[#333238]`}>
|
||||
<span className={`text-sm ${activeTab === tab ? 'font-semibold' : 'font-normal'} text-black`}>
|
||||
{tab === 'content' ? '내용' : '이미지'}
|
||||
</span>
|
||||
</button>
|
||||
@ -126,7 +126,7 @@ export default function ReviewDetail(): JSX.Element {
|
||||
<p className="font-bold">{reviewDetail.reviewer.nickname}</p>
|
||||
<p className="text-gray-500">{reviewDetail.reviewer.email}</p>
|
||||
<p className="text-gray-500">
|
||||
{reviewDetail.reviewStatus === 'APPROVED' ? '승인한 사람:' : '거부한 사람:'}{' '}
|
||||
{reviewDetail.reviewStatus === 'APPROVED' ? '승인한 사람 : ' : '거부한 사람 : '}
|
||||
{reviewDetail.reviewer.nickname}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -10,12 +10,12 @@ export default function WorkspaceReviewList() {
|
||||
const profile = useAuthStore((state) => state.profile);
|
||||
const memberId = profile?.id || 0;
|
||||
|
||||
const [activeTab, setActiveTab] = useState<'REQUESTED' | 'APPROVED' | 'REJECTED' | 'all'>('REQUESTED');
|
||||
const [activeTab, setActiveTab] = useState<'REQUESTED' | 'APPROVED' | 'REJECTED' | 'ALL'>('REQUESTED');
|
||||
const [, setSearchQuery] = useState('');
|
||||
const [sortValue, setSortValue] = useState('latest');
|
||||
|
||||
const sortDirection = sortValue === 'latest' ? 0 : 1;
|
||||
const reviewStatus = activeTab !== 'all' ? activeTab : undefined;
|
||||
const reviewStatus = activeTab !== 'ALL' ? activeTab : undefined;
|
||||
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch } = useWorkspaceReviewsQuery(
|
||||
Number(workspaceId),
|
||||
|
@ -37,8 +37,8 @@ export interface ReviewDetailResponse {
|
||||
content: string;
|
||||
reviewStatus: 'REQUESTED' | 'APPROVED' | 'REJECTED';
|
||||
images: ReviewImageResponse[];
|
||||
createAt: string;
|
||||
updateAt: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
author: MemberResponse;
|
||||
reviewer: MemberResponse;
|
||||
}
|
||||
|
6
frontend/src/utils/formatDateTime.ts
Normal file
6
frontend/src/utils/formatDateTime.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export default function formatDateTime(dateTimeString: string): string {
|
||||
const [date, time] = dateTimeString.split('T');
|
||||
const [hours, minutes] = time.split(':');
|
||||
|
||||
return `${date} ${hours}:${minutes}`;
|
||||
}
|
Loading…
Reference in New Issue
Block a user