diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-28 19:03:21 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-28 19:03:21 +0000 |
| commit | 5036cf2908792cef45f06256e71f10920f647f49 (patch) | |
| tree | 3116e7419e872d45025d1d48e6ddaffe2ba2dd38 /lib/techsales-rfq/vendor-response/detail/communication-tab.tsx | |
| parent | 7ae037e9c2fc0be1fe68cecb461c5e1e837cb0da (diff) | |
(김준회) 기술영업 조선 RFQ (SHI/벤더)
Diffstat (limited to 'lib/techsales-rfq/vendor-response/detail/communication-tab.tsx')
| -rw-r--r-- | lib/techsales-rfq/vendor-response/detail/communication-tab.tsx | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/lib/techsales-rfq/vendor-response/detail/communication-tab.tsx b/lib/techsales-rfq/vendor-response/detail/communication-tab.tsx new file mode 100644 index 00000000..0332232c --- /dev/null +++ b/lib/techsales-rfq/vendor-response/detail/communication-tab.tsx @@ -0,0 +1,215 @@ +"use client" + +import * as React from "react" +import { useState } 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" + +interface CommunicationTabProps { + quotation: { + id: number + rfq: { + id: number + rfqCode: string | null + createdByUser?: { + id: number + name: string | null + email: string | null + } | null + } | null + vendor: { + vendorName: string + } | null + } +} + +// 임시 코멘트 데이터 (실제로는 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 handleSendComment = async () => { + if (!newComment.trim()) { + toast.error("메시지를 입력해주세요.") + return + } + + setIsLoading(true) + 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("메시지 전송 중 오류가 발생했습니다.") + } finally { + setIsLoading(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" + } + + 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> + <CardContent> + <div className="flex items-center gap-4 text-sm text-muted-foreground"> + <span>구매담당자: {quotation.rfq?.createdByUser?.name || "N/A"}</span> + <span>•</span> + <span>벤더: {quotation.vendor?.vendorName}</span> + </div> + </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> + </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> + <span className="text-xs text-muted-foreground"> + {formatDateTime(comment.createdAt)} + </span> + </div> + <div className="bg-muted p-3 rounded-lg text-sm"> + {comment.content} + </div> + </div> + </div> + )) + )} + </div> + </ScrollArea> + + <Separator className="my-4" /> + + {/* 새 메시지 입력 */} + <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> + <Button + onClick={handleSendComment} + disabled={isLoading || !newComment.trim()} + size="sm" + > + <Send className="h-4 w-4 mr-2" /> + 전송 + </Button> + </div> + </div> + </CardContent> + </Card> + </div> + ) +}
\ No newline at end of file |
