summaryrefslogtreecommitdiff
path: root/lib/techsales-rfq/vendor-response/detail/communication-tab.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/techsales-rfq/vendor-response/detail/communication-tab.tsx')
-rw-r--r--lib/techsales-rfq/vendor-response/detail/communication-tab.tsx292
1 files changed, 143 insertions, 149 deletions
diff --git a/lib/techsales-rfq/vendor-response/detail/communication-tab.tsx b/lib/techsales-rfq/vendor-response/detail/communication-tab.tsx
index 0332232c..3f2a5280 100644
--- a/lib/techsales-rfq/vendor-response/detail/communication-tab.tsx
+++ b/lib/techsales-rfq/vendor-response/detail/communication-tab.tsx
@@ -1,21 +1,22 @@
"use client"
import * as React from "react"
-import { useState } from "react"
+import { useState, useEffect } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
-import { Textarea } from "@/components/ui/textarea"
import { Badge } from "@/components/ui/badge"
import { ScrollArea } from "@/components/ui/scroll-area"
-import { Separator } from "@/components/ui/separator"
-import { Avatar, AvatarFallback } from "@/components/ui/avatar"
-import { Send, MessageCircle } from "lucide-react"
-import { formatDateTime } from "@/lib/utils"
-import { toast } from "sonner"
+import { Skeleton } from "@/components/ui/skeleton"
+import { MessageSquare, Paperclip } from "lucide-react"
+import { fetchTechSalesVendorCommentsClient, TechSalesComment } from "../buyer-communication-drawer"
+import { BuyerCommunicationDrawer } from "../buyer-communication-drawer"
interface CommunicationTabProps {
quotation: {
id: number
+ rfqId: number
+ vendorId: number
+ quotationCode: string | null
rfq: {
id: number
rfqCode: string | null
@@ -31,100 +32,73 @@ interface CommunicationTabProps {
}
}
-// 임시 코멘트 데이터 (실제로는 API에서 가져와야 함)
-const MOCK_COMMENTS = [
- {
- id: 1,
- content: "안녕하세요. 해당 자재에 대한 견적 요청 드립니다. 납기일은 언제까지 가능한지 문의드립니다.",
- createdAt: new Date("2024-01-15T09:00:00"),
- author: {
- name: "김구매",
- email: "buyer@company.com",
- role: "구매담당자"
- }
- },
- {
- id: 2,
- content: "안녕하세요. 견적 요청 확인했습니다. 해당 자재의 경우 약 2주 정도의 제작 기간이 필요합니다. 상세한 견적은 내일까지 제출하겠습니다.",
- createdAt: new Date("2024-01-15T14:30:00"),
- author: {
- name: "이벤더",
- email: "vendor@supplier.com",
- role: "벤더"
- }
- },
- {
- id: 3,
- content: "감사합니다. 추가로 품질 인증서도 함께 제출 가능한지 확인 부탁드립니다.",
- createdAt: new Date("2024-01-16T10:15:00"),
- author: {
- name: "김구매",
- email: "buyer@company.com",
- role: "구매담당자"
- }
- }
-]
-
export function CommunicationTab({ quotation }: CommunicationTabProps) {
- const [newComment, setNewComment] = useState("")
- const [isLoading, setIsLoading] = useState(false)
- const [comments, setComments] = useState(MOCK_COMMENTS)
+ const [comments, setComments] = useState<TechSalesComment[]>([]);
+ const [unreadCount, setUnreadCount] = useState(0);
+ const [loadingComments, setLoadingComments] = useState(false);
+ const [communicationDrawerOpen, setCommunicationDrawerOpen] = useState(false);
- const handleSendComment = async () => {
- if (!newComment.trim()) {
- toast.error("메시지를 입력해주세요.")
- return
+ // 컴포넌트 마운트 시 메시지 미리 로드
+ useEffect(() => {
+ if (quotation) {
+ loadCommunicationData();
}
+ }, [quotation]);
- setIsLoading(true)
+ // 메시지 데이터 로드 함수
+ const loadCommunicationData = async () => {
try {
- // TODO: API 호출로 코멘트 전송
- const newCommentData = {
- id: comments.length + 1,
- content: newComment,
- createdAt: new Date(),
- author: {
- name: "현재사용자", // 실제로는 세션에서 가져와야 함
- email: "current@user.com",
- role: "벤더"
- }
- }
-
- setComments([...comments, newCommentData])
- setNewComment("")
- toast.success("메시지가 전송되었습니다.")
- } catch {
- toast.error("메시지 전송 중 오류가 발생했습니다.")
+ setLoadingComments(true);
+ const commentsData = await fetchTechSalesVendorCommentsClient(quotation.rfqId, quotation.vendorId);
+ setComments(commentsData);
+
+ // 읽지 않은 메시지 수 계산 (구매자가 보낸 메시지 중 읽지 않은 것)
+ const unread = commentsData.filter(
+ comment => !comment.isVendorComment && !comment.isRead
+ ).length;
+ setUnreadCount(unread);
+ } catch (error) {
+ console.error("메시지 데이터 로드 오류:", error);
} finally {
- setIsLoading(false)
+ setLoadingComments(false);
}
- }
+ };
- const getAuthorInitials = (name: string) => {
- return name
- .split(" ")
- .map(word => word[0])
- .join("")
- .toUpperCase()
- .slice(0, 2)
- }
-
- const getRoleBadgeVariant = (role: string) => {
- return role === "구매담당자" ? "default" : "secondary"
- }
+ // 커뮤니케이션 드로어가 닫힐 때 데이터 새로고침
+ const handleCommunicationDrawerChange = (open: boolean) => {
+ setCommunicationDrawerOpen(open);
+ if (!open) {
+ loadCommunicationData(); // 드로어가 닫힐 때 데이터 새로고침
+ }
+ };
return (
<div className="h-full flex flex-col">
{/* 헤더 */}
<Card className="mb-4">
- <CardHeader>
- <CardTitle className="flex items-center gap-2">
- <MessageCircle className="h-5 w-5" />
- 커뮤니케이션
- </CardTitle>
- <CardDescription>
- RFQ {quotation.rfq?.rfqCode || "미할당"}에 대한 구매담당자와의 커뮤니케이션
- </CardDescription>
+ <CardHeader className="flex flex-row items-center justify-between">
+ <div>
+ <CardTitle className="flex items-center gap-2">
+ <MessageSquare className="h-5 w-5" />
+ 커뮤니케이션
+ {unreadCount > 0 && (
+ <Badge variant="destructive" className="ml-2">
+ 새 메시지 {unreadCount}
+ </Badge>
+ )}
+ </CardTitle>
+ <CardDescription>
+ RFQ {quotation.rfq?.rfqCode || "미할당"}에 대한 구매담당자와의 커뮤니케이션
+ </CardDescription>
+ </div>
+ <Button
+ onClick={() => setCommunicationDrawerOpen(true)}
+ variant="outline"
+ size="sm"
+ >
+ <MessageSquare className="h-4 w-4 mr-2" />
+ {unreadCount > 0 ? "새 메시지 확인" : "메시지 보내기"}
+ </Button>
</CardHeader>
<CardContent>
<div className="flex items-center gap-4 text-sm text-muted-foreground">
@@ -135,81 +109,101 @@ export function CommunicationTab({ quotation }: CommunicationTabProps) {
</CardContent>
</Card>
- {/* 메시지 목록 */}
+ {/* 메시지 미리보기 */}
<Card className="flex-1 flex flex-col min-h-0">
<CardHeader>
<CardTitle className="text-lg">메시지 ({comments.length})</CardTitle>
</CardHeader>
- <CardContent className="flex-1 flex flex-col min-h-0">
- <ScrollArea className="flex-1 pr-4">
- <div className="space-y-4">
- {comments.length === 0 ? (
- <div className="text-center py-8 text-muted-foreground">
- <MessageCircle className="h-12 w-12 mx-auto mb-4 opacity-50" />
- <p>아직 메시지가 없습니다.</p>
- <p className="text-sm">첫 번째 메시지를 보내보세요.</p>
+ <CardContent>
+ {loadingComments ? (
+ <div className="flex items-center justify-center p-8">
+ <div className="text-center">
+ <Skeleton className="h-4 w-32 mx-auto mb-2" />
+ <Skeleton className="h-4 w-48 mx-auto" />
+ </div>
+ </div>
+ ) : comments.length === 0 ? (
+ <div className="min-h-[200px] flex flex-col items-center justify-center text-center p-8">
+ <div className="max-w-md">
+ <div className="mx-auto bg-primary/10 rounded-full w-12 h-12 flex items-center justify-center mb-4">
+ <MessageSquare className="h-6 w-6 text-primary" />
</div>
- ) : (
- comments.map((comment) => (
- <div key={comment.id} className="flex gap-3">
- <Avatar className="h-8 w-8 mt-1">
- <AvatarFallback className="text-xs">
- {getAuthorInitials(comment.author.name)}
- </AvatarFallback>
- </Avatar>
- <div className="flex-1 space-y-2">
- <div className="flex items-center gap-2">
- <span className="font-medium text-sm">{comment.author.name}</span>
- <Badge variant={getRoleBadgeVariant(comment.author.role)} className="text-xs">
- {comment.author.role}
- </Badge>
+ <h3 className="text-lg font-medium mb-2">아직 메시지가 없습니다</h3>
+ <p className="text-muted-foreground mb-4">
+ 견적서에 대한 질문이나 의견이 있으신가요? 구매자와 메시지를 주고받으세요.
+ </p>
+ <Button
+ onClick={() => setCommunicationDrawerOpen(true)}
+ className="mx-auto"
+ >
+ 메시지 보내기
+ </Button>
+ </div>
+ </div>
+ ) : (
+ <div className="space-y-4">
+ {/* 최근 메시지 3개 미리보기 */}
+ <div className="space-y-2">
+ <h3 className="text-sm font-medium">최근 메시지</h3>
+ <ScrollArea className="h-[250px] rounded-md border p-4">
+ {comments.slice(-3).map(comment => (
+ <div
+ key={comment.id}
+ className={`p-3 mb-3 rounded-lg ${!comment.isVendorComment && !comment.isRead
+ ? 'bg-primary/10 border-l-4 border-primary'
+ : 'bg-muted/50'
+ }`}
+ >
+ <div className="flex justify-between items-center mb-1">
+ <span className="text-sm font-medium">
+ {comment.isVendorComment
+ ? '나'
+ : comment.userName || '구매 담당자'}
+ </span>
<span className="text-xs text-muted-foreground">
- {formatDateTime(comment.createdAt)}
+ {new Date(comment.createdAt).toLocaleDateString()}
</span>
</div>
- <div className="bg-muted p-3 rounded-lg text-sm">
- {comment.content}
- </div>
+ <p className="text-sm line-clamp-2">{comment.content}</p>
+ {comment.attachments.length > 0 && (
+ <div className="mt-1 text-xs text-muted-foreground">
+ <Paperclip className="h-3 w-3 inline mr-1" />
+ 첨부파일 {comment.attachments.length}개
+ </div>
+ )}
</div>
- </div>
- ))
- )}
- </div>
- </ScrollArea>
-
- <Separator className="my-4" />
+ ))}
+ </ScrollArea>
+ </div>
- {/* 새 메시지 입력 */}
- <div className="space-y-3">
- <Textarea
- placeholder="메시지를 입력하세요..."
- value={newComment}
- onChange={(e) => setNewComment(e.target.value)}
- rows={3}
- className="resize-none"
- onKeyDown={(e) => {
- if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
- e.preventDefault()
- handleSendComment()
- }
- }}
- />
- <div className="flex justify-between items-center">
- <div className="text-xs text-muted-foreground">
- Ctrl + Enter로 빠른 전송
+ <div className="flex justify-center">
+ <Button
+ onClick={() => setCommunicationDrawerOpen(true)}
+ className="w-full"
+ >
+ 전체 메시지 보기 ({comments.length}개)
+ </Button>
</div>
- <Button
- onClick={handleSendComment}
- disabled={isLoading || !newComment.trim()}
- size="sm"
- >
- <Send className="h-4 w-4 mr-2" />
- 전송
- </Button>
</div>
- </div>
+ )}
</CardContent>
</Card>
+
+ {/* 커뮤니케이션 드로어 */}
+ <BuyerCommunicationDrawer
+ open={communicationDrawerOpen}
+ onOpenChange={handleCommunicationDrawerChange}
+ quotation={{
+ id: quotation.id,
+ rfqId: quotation.rfqId,
+ vendorId: quotation.vendorId,
+ quotationCode: quotation.quotationCode,
+ rfq: quotation.rfq ? {
+ rfqCode: quotation.rfq.rfqCode
+ } : undefined
+ }}
+ onSuccess={loadCommunicationData}
+ />
</div>
)
} \ No newline at end of file