Fix: 리뷰요청 페이지 에러 수정 및 디자인 고침
This commit is contained in:
parent
71b5b2af2d
commit
72d465c3cf
@ -1,4 +1,3 @@
|
||||
// ImageSelection.tsx
|
||||
import { Label } from '@/components/ui/label';
|
||||
import useRecursiveSavedImages from '@/hooks/useRecursiveSavedImages';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@ -7,27 +6,44 @@ import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
interface ImageSelectionProps {
|
||||
projectId: string;
|
||||
selectedImages: number[];
|
||||
setSelectedImages: React.Dispatch<React.SetStateAction<number[]>>;
|
||||
setSelectedImages: (images: number[]) => void;
|
||||
}
|
||||
|
||||
export default function ImageSelection({ projectId, selectedImages, setSelectedImages }: ImageSelectionProps) {
|
||||
const { allSavedImages } = useRecursiveSavedImages(projectId, 0);
|
||||
|
||||
const handleImageSelect = (imageId: number) => {
|
||||
// 상태 업데이트 안전하게 관리
|
||||
setSelectedImages((prevSelectedImages) => {
|
||||
// 이미 선택된 이미지가 있는 경우 필터링하여 제거
|
||||
if (prevSelectedImages.includes(imageId)) {
|
||||
return prevSelectedImages.filter((id) => id !== imageId);
|
||||
const updatedImages = selectedImages.includes(imageId)
|
||||
? selectedImages.filter((id) => id !== imageId)
|
||||
: [...selectedImages, imageId];
|
||||
|
||||
setSelectedImages(updatedImages);
|
||||
};
|
||||
|
||||
const handleSelectAll = () => {
|
||||
if (allSavedImages) {
|
||||
if (selectedImages.length === allSavedImages.length) {
|
||||
setSelectedImages([]);
|
||||
} else {
|
||||
setSelectedImages(allSavedImages.map((image) => image.id));
|
||||
}
|
||||
}
|
||||
// 선택되지 않은 이미지를 배열에 추가
|
||||
return [...prevSelectedImages, imageId];
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<Label>이미지 선택 (파일 목록)</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleSelectAll}
|
||||
type="button"
|
||||
className="px-4 py-2"
|
||||
>
|
||||
{allSavedImages && selectedImages.length === allSavedImages.length ? '전체 선택 해제' : '전체 선택'}
|
||||
</Button>
|
||||
</div>
|
||||
<ScrollArea className="max-h-64 overflow-auto border p-2">
|
||||
<ul className="space-y-2">
|
||||
{allSavedImages && allSavedImages.length > 0 ? (
|
||||
@ -42,11 +58,12 @@ export default function ImageSelection({ projectId, selectedImages, setSelectedI
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant={selectedImages.includes(image.id) ? 'destructive' : 'outline'}
|
||||
size="xs"
|
||||
size="sm"
|
||||
onClick={() => handleImageSelect(image.id)}
|
||||
className="p-0"
|
||||
className="px-3 py-1"
|
||||
type="button"
|
||||
>
|
||||
{selectedImages.includes(image.id) ? '선택 해제' : '선택'}
|
||||
{selectedImages.includes(image.id) ? '해제' : '선택'}
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
|
@ -1,38 +1,63 @@
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import ImageSelection from '@/components/ImageSelection';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface ReviewFormProps {
|
||||
projects: { id: string; title: string }[];
|
||||
selectedProjectId: string | null;
|
||||
setSelectedProjectId: (id: string) => void;
|
||||
selectedImages: number[];
|
||||
setSelectedImages: React.Dispatch<React.SetStateAction<number[]>>;
|
||||
onSubmit: (data: { title: string; content: string }) => void;
|
||||
onSubmit: (data: ReviewFormData) => void;
|
||||
}
|
||||
|
||||
export default function ReviewForm({
|
||||
projects,
|
||||
selectedProjectId,
|
||||
setSelectedProjectId,
|
||||
selectedImages,
|
||||
setSelectedImages,
|
||||
onSubmit,
|
||||
}: ReviewFormProps): JSX.Element {
|
||||
const reviewFormSchema = z.object({
|
||||
projectId: z.string().min(1, '프로젝트를 선택해주세요.'),
|
||||
title: z.string().min(1, '제목을 입력해주세요.'),
|
||||
content: z.string().min(1, '내용을 입력해주세요.'),
|
||||
imageIds: z.array(z.number()),
|
||||
});
|
||||
|
||||
type ReviewFormData = z.infer<typeof reviewFormSchema>;
|
||||
|
||||
export default function ReviewForm({ projects, onSubmit }: ReviewFormProps): JSX.Element {
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<{ title: string; content: string }>();
|
||||
setValue,
|
||||
watch,
|
||||
} = useForm<ReviewFormData>({
|
||||
resolver: zodResolver(reviewFormSchema),
|
||||
defaultValues: {
|
||||
projectId: '',
|
||||
title: '',
|
||||
content: '',
|
||||
imageIds: [],
|
||||
},
|
||||
});
|
||||
|
||||
const selectedProjectId = watch('projectId');
|
||||
const selectedImages = watch('imageIds');
|
||||
|
||||
const setSelectedProjectId = (value: string) => {
|
||||
setValue('projectId', value);
|
||||
setValue('imageIds', []); // 프로젝트 변경 시 이미지 초기화
|
||||
};
|
||||
|
||||
const setSelectedImages = (images: number[]) => {
|
||||
setValue('imageIds', images);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="mb-4">
|
||||
<Label htmlFor="project">프로젝트 선택</Label>
|
||||
<Select onValueChange={(value) => setSelectedProjectId(value)}>
|
||||
<Select onValueChange={setSelectedProjectId}>
|
||||
<SelectTrigger id="project">
|
||||
<SelectValue placeholder="프로젝트를 선택하세요" />
|
||||
</SelectTrigger>
|
||||
@ -56,6 +81,7 @@ export default function ReviewForm({
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{errors.projectId && <p className="text-red-500">{errors.projectId.message}</p>}
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
@ -63,7 +89,7 @@ export default function ReviewForm({
|
||||
<Input
|
||||
id="title"
|
||||
placeholder="리뷰 제목을 입력하세요"
|
||||
{...register('title', { required: '제목을 입력해주세요.' })}
|
||||
{...register('title')}
|
||||
/>
|
||||
{errors.title && <p className="text-red-500">{errors.title.message}</p>}
|
||||
</div>
|
||||
@ -73,7 +99,7 @@ export default function ReviewForm({
|
||||
<Textarea
|
||||
id="content"
|
||||
placeholder="리뷰 내용을 입력하세요"
|
||||
{...register('content', { required: '내용을 입력해주세요.' })}
|
||||
{...register('content')}
|
||||
/>
|
||||
{errors.content && <p className="text-red-500">{errors.content.message}</p>}
|
||||
</div>
|
||||
@ -85,6 +111,23 @@ export default function ReviewForm({
|
||||
setSelectedImages={setSelectedImages}
|
||||
/>
|
||||
)}
|
||||
{errors.imageIds && errors.imageIds.message && <p className="text-red-500">{errors.imageIds.message}</p>}
|
||||
|
||||
<div className="actions mt-6 flex justify-end space-x-2">
|
||||
<Button
|
||||
variant="destructive"
|
||||
type="button"
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
type="submit"
|
||||
>
|
||||
리뷰 요청
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
@ -1,48 +1,34 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import useCreateReviewQuery from '@/queries/reviews/useCreateReviewQuery';
|
||||
import useAuthStore from '@/stores/useAuthStore';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import ReviewForm from '@/components/ReviewForm';
|
||||
import useReviewRequest from '@/hooks/useReviewRequest';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function ReviewRequest(): JSX.Element {
|
||||
const { profile } = useAuthStore((state) => state);
|
||||
const memberId = profile?.id ?? 0;
|
||||
const navigate = useNavigate();
|
||||
const { workspaceId } = useParams<{ workspaceId?: string }>();
|
||||
|
||||
const { projects, selectedProjectId, setSelectedProjectId, selectedImages, setSelectedImages } = useReviewRequest();
|
||||
|
||||
const handleSuccess = () => {
|
||||
navigate(`/admin/${workspaceId}/reviews`);
|
||||
};
|
||||
|
||||
const { projects } = useReviewRequest();
|
||||
const createReview = useCreateReviewQuery();
|
||||
|
||||
const [formData, setFormData] = useState<{ title: string; content: string } | null>(null);
|
||||
|
||||
const handleReviewSubmit = (data: { title: string; content: string }) => {
|
||||
setFormData(data);
|
||||
};
|
||||
|
||||
const handleButtonClick = () => {
|
||||
if (!formData) return;
|
||||
|
||||
const handleReviewSubmit = (data: { projectId: string; title: string; content: string; imageIds: number[] }) => {
|
||||
const reviewData = {
|
||||
title: formData.title,
|
||||
content: formData.content,
|
||||
imageIds: selectedImages,
|
||||
title: data.title,
|
||||
content: data.content,
|
||||
imageIds: data.imageIds,
|
||||
};
|
||||
|
||||
createReview.mutate(
|
||||
{
|
||||
projectId: Number(selectedProjectId),
|
||||
projectId: Number(data.projectId),
|
||||
memberId,
|
||||
reviewData,
|
||||
},
|
||||
{
|
||||
onSuccess: handleSuccess,
|
||||
onSuccess: () => navigate(`/admin/${workspaceId}/reviews`),
|
||||
}
|
||||
);
|
||||
};
|
||||
@ -53,29 +39,8 @@ export default function ReviewRequest(): JSX.Element {
|
||||
|
||||
<ReviewForm
|
||||
projects={projects.map((project) => ({ id: project.id.toString(), title: project.title }))}
|
||||
selectedProjectId={selectedProjectId}
|
||||
setSelectedProjectId={setSelectedProjectId}
|
||||
selectedImages={selectedImages}
|
||||
setSelectedImages={setSelectedImages}
|
||||
onSubmit={handleReviewSubmit}
|
||||
/>
|
||||
|
||||
<div className="actions mt-6 flex justify-end space-x-2">
|
||||
<Button
|
||||
variant="destructive"
|
||||
type="button"
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={handleButtonClick} // 버튼 클릭 시에만 mutate 실행
|
||||
disabled={!selectedProjectId || !formData}
|
||||
>
|
||||
리뷰 요청
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user