summaryrefslogtreecommitdiff
path: root/lib/techsales-rfq/vendor-response/buyer-communication-drawer.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/techsales-rfq/vendor-response/buyer-communication-drawer.tsx')
-rw-r--r--lib/techsales-rfq/vendor-response/buyer-communication-drawer.tsx165
1 files changed, 128 insertions, 37 deletions
diff --git a/lib/techsales-rfq/vendor-response/buyer-communication-drawer.tsx b/lib/techsales-rfq/vendor-response/buyer-communication-drawer.tsx
index 69ba0363..c8a0efc2 100644
--- a/lib/techsales-rfq/vendor-response/buyer-communication-drawer.tsx
+++ b/lib/techsales-rfq/vendor-response/buyer-communication-drawer.tsx
@@ -38,31 +38,30 @@ import {
} from "@/components/ui/dialog"
import { formatDateTime, formatFileSize } from "@/lib/utils"
import { useSession } from "next-auth/react"
-import { fetchBuyerVendorComments } from "../services"
// 타입 정의
-interface Comment {
- id: number;
- rfqId: number;
- vendorId: number | null // null 허용으로 변경
- userId?: number | null // null 허용으로 변경
- content: string;
- isVendorComment: boolean | null; // null 허용으로 변경
- createdAt: Date;
- updatedAt: Date;
- userName?: string | null // null 허용으로 변경
- vendorName?: string | null // null 허용으로 변경
- attachments: Attachment[];
- isRead: boolean | null // null 허용으로 변경
+export interface TechSalesAttachment {
+ id: number
+ fileName: string
+ fileSize: number
+ fileType: string | null
+ filePath: string
+ uploadedAt: Date
}
-interface Attachment {
- id: number;
- fileName: string;
- fileSize: number;
- fileType: string | null; // null 허용으로 변경
- filePath: string;
- uploadedAt: Date;
+export interface TechSalesComment {
+ id: number
+ rfqId: number
+ vendorId: number | null
+ userId?: number | null
+ content: string
+ isVendorComment: boolean | null
+ createdAt: Date
+ updatedAt: Date
+ userName?: string | null
+ vendorName?: string | null
+ attachments: TechSalesAttachment[]
+ isRead: boolean | null
}
// 프롭스 정의
@@ -73,15 +72,61 @@ interface BuyerCommunicationDrawerProps {
id: number;
rfqId: number;
vendorId: number;
- quotationCode: string;
+ quotationCode: string | null;
rfq?: {
- rfqCode: string;
+ rfqCode: string | null;
};
} | null;
onSuccess?: () => void;
}
-
+// 클라이언트에서 API를 통해 코멘트를 가져오는 함수
+export async function fetchTechSalesVendorCommentsClient(rfqId: number, vendorId: number): Promise<TechSalesComment[]> {
+ const response = await fetch(`/api/tech-sales-rfqs/${rfqId}/vendors/${vendorId}/comments`);
+
+ if (!response.ok) {
+ throw new Error(`API 요청 실패: ${response.status}`);
+ }
+
+ const result = await response.json();
+
+ if (!result.success) {
+ throw new Error(result.message || '코멘트 조회 중 오류가 발생했습니다');
+ }
+
+ // API 응답 타입 정의
+ interface ApiComment {
+ id: number;
+ rfqId: number;
+ vendorId: number | null;
+ userId?: number | null;
+ content: string;
+ isVendorComment: boolean | null;
+ createdAt: string;
+ updatedAt: string;
+ userName?: string | null;
+ vendorName?: string | null;
+ isRead: boolean | null;
+ attachments: Array<{
+ id: number;
+ fileName: string;
+ fileSize: number;
+ fileType: string | null;
+ filePath: string;
+ uploadedAt: string;
+ }>;
+ }
+
+ return result.data.map((comment: ApiComment) => ({
+ ...comment,
+ createdAt: new Date(comment.createdAt),
+ updatedAt: new Date(comment.updatedAt),
+ attachments: comment.attachments.map((att) => ({
+ ...att,
+ uploadedAt: new Date(att.uploadedAt)
+ }))
+ }));
+}
// 벤더 코멘트 전송 함수
export function sendVendorCommentClient(params: {
@@ -89,7 +134,7 @@ export function sendVendorCommentClient(params: {
vendorId: number;
content: string;
attachments?: File[];
-}): Promise<Comment> {
+}): Promise<TechSalesComment> {
// 폼 데이터 생성 (파일 첨부를 위해)
const formData = new FormData();
formData.append('rfqId', params.rfqId.toString());
@@ -104,8 +149,10 @@ export function sendVendorCommentClient(params: {
});
}
- // API 엔드포인트 구성 (벤더 API 경로)
- const url = `/api/procurement-rfqs/${params.rfqId}/vendors/${params.vendorId}/comments`;
+ // API 엔드포인트 구성 (techsales API 경로)
+ const url = `/api/tech-sales-rfqs/${params.rfqId}/vendors/${params.vendorId}/comments`;
+
+ console.log("API 요청 시작:", { url, params });
// API 호출
return fetch(url, {
@@ -113,22 +160,65 @@ export function sendVendorCommentClient(params: {
body: formData, // multipart/form-data 형식 사용
})
.then(response => {
+ console.log("API 응답 상태:", response.status);
+
if (!response.ok) {
return response.text().then(text => {
+ console.error("API 에러 응답:", text);
throw new Error(`API 요청 실패: ${response.status} ${text}`);
});
}
return response.json();
})
.then(result => {
+ console.log("API 응답 데이터:", result);
+
if (!result.success || !result.data) {
throw new Error(result.message || '코멘트 전송 중 오류가 발생했습니다');
}
- return result.data.comment;
+
+ // API 응답 타입 정의
+ interface ApiAttachment {
+ id: number;
+ fileName: string;
+ fileSize: number;
+ fileType: string | null;
+ filePath: string;
+ uploadedAt: string;
+ }
+
+ interface ApiCommentResponse {
+ id: number;
+ rfqId: number;
+ vendorId: number | null;
+ userId?: number | null;
+ content: string;
+ isVendorComment: boolean | null;
+ createdAt: string;
+ updatedAt: string;
+ userName?: string | null;
+ isRead: boolean | null;
+ attachments: ApiAttachment[];
+ }
+
+ const commentData = result.data.comment as ApiCommentResponse;
+
+ return {
+ ...commentData,
+ createdAt: new Date(commentData.createdAt),
+ updatedAt: new Date(commentData.updatedAt),
+ attachments: commentData.attachments.map((att) => ({
+ ...att,
+ uploadedAt: new Date(att.uploadedAt)
+ }))
+ };
+ })
+ .catch(error => {
+ console.error("클라이언트 API 호출 에러:", error);
+ throw error;
});
}
-
export function BuyerCommunicationDrawer({
open,
onOpenChange,
@@ -139,7 +229,7 @@ export function BuyerCommunicationDrawer({
const { data: session } = useSession();
// 상태 관리
- const [comments, setComments] = useState<Comment[]>([]);
+ const [comments, setComments] = useState<TechSalesComment[]>([]);
const [newComment, setNewComment] = useState("");
const [attachments, setAttachments] = useState<File[]>([]);
const [isLoading, setIsLoading] = useState(false);
@@ -149,7 +239,7 @@ export function BuyerCommunicationDrawer({
// 첨부파일 관련 상태
const [previewDialogOpen, setPreviewDialogOpen] = useState(false);
- const [selectedAttachment, setSelectedAttachment] = useState<Attachment | null>(null);
+ const [selectedAttachment, setSelectedAttachment] = useState<TechSalesAttachment | null>(null);
// 드로어가 열릴 때 데이터 로드
useEffect(() => {
@@ -173,7 +263,7 @@ export function BuyerCommunicationDrawer({
setIsLoading(true);
// API를 사용하여 코멘트 데이터 가져오기
- const commentsData = await fetchBuyerVendorComments(quotation.rfqId, quotation.vendorId);
+ const commentsData = await fetchTechSalesVendorCommentsClient(quotation.rfqId, quotation.vendorId);
setComments(commentsData);
// 읽음 상태 처리는 API 측에서 처리되는 것으로 가정
@@ -239,19 +329,20 @@ export function BuyerCommunicationDrawer({
};
// 첨부파일 미리보기
- const handleAttachmentPreview = (attachment: Attachment) => {
+ const handleAttachmentPreview = (attachment: TechSalesAttachment) => {
setSelectedAttachment(attachment);
setPreviewDialogOpen(true);
};
// 첨부파일 다운로드
- const handleAttachmentDownload = (attachment: Attachment) => {
+ const handleAttachmentDownload = (attachment: TechSalesAttachment) => {
// 실제 다운로드 구현
window.open(attachment.filePath, '_blank');
};
// 파일 아이콘 선택
- const getFileIcon = (fileType: string) => {
+ const getFileIcon = (fileType: string | null) => {
+ if (!fileType) return <File className="h-5 w-5 text-gray-500" />;
if (fileType.startsWith("image/")) return <ImageIcon className="h-5 w-5 text-blue-500" />;
if (fileType.includes("pdf")) return <FileText className="h-5 w-5 text-red-500" />;
if (fileType.includes("spreadsheet") || fileType.includes("excel"))
@@ -265,8 +356,8 @@ export function BuyerCommunicationDrawer({
const renderAttachmentPreviewDialog = () => {
if (!selectedAttachment) return null;
- const isImage = selectedAttachment.fileType.startsWith("image/");
- const isPdf = selectedAttachment.fileType.includes("pdf");
+ const isImage = selectedAttachment.fileType?.startsWith("image/") || false;
+ const isPdf = selectedAttachment.fileType?.includes("pdf") || false;
return (
<Dialog open={previewDialogOpen} onOpenChange={setPreviewDialogOpen}>