diff options
Diffstat (limited to 'lib/tbe-last/vendor/vendor-comment-dialog.tsx')
| -rw-r--r-- | lib/tbe-last/vendor/vendor-comment-dialog.tsx | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/lib/tbe-last/vendor/vendor-comment-dialog.tsx b/lib/tbe-last/vendor/vendor-comment-dialog.tsx new file mode 100644 index 00000000..8aa8d97c --- /dev/null +++ b/lib/tbe-last/vendor/vendor-comment-dialog.tsx @@ -0,0 +1,313 @@ +// lib/vendor-rfq-response/vendor-tbe-table/vendor-qa-dialog.tsx + +"use client" + +import * as React from "react" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from "@/components/ui/dialog" +import { Button } from "@/components/ui/button" +import { Label } from "@/components/ui/label" +import { Textarea } from "@/components/ui/textarea" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { Badge } from "@/components/ui/badge" +import { ScrollArea } from "@/components/ui/scroll-area" +import { toast } from "sonner" +import { + MessageSquare, + Send, + Loader2, + Clock, + CheckCircle, + AlertCircle +} from "lucide-react" +import { formatDate } from "@/lib/utils" + +interface VendorQuestion { + id: string + category: string + question: string + askedAt: string + askedBy: number + askedByName?: string + answer?: string + answeredAt?: string + answeredBy?: number + answeredByName?: string + status: "open" | "answered" | "closed" + priority?: "high" | "normal" | "low" +} + +interface VendorQADialogProps { + open: boolean + onOpenChange: (open: boolean) => void + sessionId: number | null + sessionDetail: any + onQuestionSubmit: () => void +} + +export function VendorQADialog({ + open, + onOpenChange, + sessionId, + sessionDetail, + onQuestionSubmit +}: VendorQADialogProps) { + + const [category, setCategory] = React.useState("general") + const [priority, setPriority] = React.useState("normal") + const [question, setQuestion] = React.useState("") + const [isSubmitting, setIsSubmitting] = React.useState(false) + const [questions, setQuestions] = React.useState<VendorQuestion[]>([]) + const [isLoading, setIsLoading] = React.useState(false) + + // Load questions when dialog opens + React.useEffect(() => { + if (open && sessionId) { + loadQuestions() + } + }, [open, sessionId]) + + const loadQuestions = async () => { + if (!sessionId) return + + setIsLoading(true) + try { + const response = await fetch(`/api/tbe/sessions/${sessionId}/vendor-questions`) + if (response.ok) { + const data = await response.json() + setQuestions(data) + } + } catch (error) { + console.error("Failed to load questions:", error) + } finally { + setIsLoading(false) + } + } + + // Submit question + const handleSubmit = async () => { + if (!sessionId || !question.trim()) { + toast.error("질문을 입력해주세요") + return + } + + setIsSubmitting(true) + + try { + const response = await fetch(`/api/tbe/sessions/${sessionId}/vendor-questions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + category, + question, + priority + }) + }) + + if (!response.ok) throw new Error("Failed to submit question") + + toast.success("질문이 제출되었습니다") + + // Reset form + setCategory("general") + setPriority("normal") + setQuestion("") + + // Reload questions + await loadQuestions() + + // Callback + onQuestionSubmit() + + } catch (error) { + console.error("Question submission error:", error) + toast.error("질문 제출 중 오류가 발생했습니다") + } finally { + setIsSubmitting(false) + } + } + + // Get status icon + const getStatusIcon = (status: string) => { + switch (status) { + case "answered": + return <CheckCircle className="h-4 w-4 text-green-600" /> + case "closed": + return <CheckCircle className="h-4 w-4 text-gray-600" /> + default: + return <Clock className="h-4 w-4 text-orange-600" /> + } + } + + // Get priority color + const getPriorityColor = (priority?: string) => { + switch (priority) { + case "high": + return "destructive" + case "low": + return "secondary" + default: + return "default" + } + } + + return ( + <Dialog open={open} onOpenChange={onOpenChange}> + <DialogContent className="max-w-3xl max-h-[80vh]"> + <DialogHeader> + <DialogTitle>Q&A with Buyer</DialogTitle> + <DialogDescription> + {sessionDetail?.session?.sessionCode} - 구매자에게 질문하기 + </DialogDescription> + </DialogHeader> + + <div className="space-y-4"> + {/* Previous Questions */} + {questions.length > 0 && ( + <div> + <Label className="text-sm font-medium mb-2">Previous Q&A</Label> + <ScrollArea className="h-[200px] border rounded-lg p-3"> + <div className="space-y-3"> + {questions.map(q => ( + <div key={q.id} className="border-b pb-3 last:border-0"> + <div className="flex items-start justify-between mb-2"> + <div className="flex items-center gap-2"> + {getStatusIcon(q.status)} + <Badge variant="outline" className="text-xs"> + {q.category} + </Badge> + {q.priority && q.priority !== "normal" && ( + <Badge variant={getPriorityColor(q.priority)} className="text-xs"> + {q.priority} + </Badge> + )} + </div> + <span className="text-xs text-muted-foreground"> + {formatDate(q.askedAt, "KR")} + </span> + </div> + + <div className="space-y-2"> + <div className="text-sm"> + <strong>Q:</strong> {q.question} + </div> + + {q.answer && ( + <div className="text-sm text-muted-foreground ml-4"> + <strong>A:</strong> {q.answer} + <span className="text-xs ml-2"> + ({formatDate(q.answeredAt!, "KR")}) + </span> + </div> + )} + </div> + </div> + ))} + </div> + </ScrollArea> + </div> + )} + + {/* New Question Form */} + <div className="space-y-3"> + <div className="grid grid-cols-2 gap-3"> + <div> + <Label htmlFor="category">Category</Label> + <Select value={category} onValueChange={setCategory}> + <SelectTrigger> + <SelectValue /> + </SelectTrigger> + <SelectContent> + <SelectItem value="general">일반 문의</SelectItem> + <SelectItem value="technical">기술 관련</SelectItem> + <SelectItem value="commercial">상업 조건</SelectItem> + <SelectItem value="delivery">납기 관련</SelectItem> + <SelectItem value="quality">품질 관련</SelectItem> + <SelectItem value="document">문서 관련</SelectItem> + <SelectItem value="clarification">명확화 요청</SelectItem> + </SelectContent> + </Select> + </div> + + <div> + <Label htmlFor="priority">Priority</Label> + <Select value={priority} onValueChange={setPriority}> + <SelectTrigger> + <SelectValue /> + </SelectTrigger> + <SelectContent> + <SelectItem value="high">High</SelectItem> + <SelectItem value="normal">Normal</SelectItem> + <SelectItem value="low">Low</SelectItem> + </SelectContent> + </Select> + </div> + </div> + + <div> + <Label htmlFor="question">Your Question</Label> + <Textarea + id="question" + value={question} + onChange={(e) => setQuestion(e.target.value)} + placeholder="구매자에게 질문할 내용을 입력하세요..." + className="min-h-[100px]" + disabled={isSubmitting} + /> + </div> + + <div className="flex justify-end gap-2"> + <Button + variant="outline" + onClick={() => onOpenChange(false)} + disabled={isSubmitting} + > + 취소 + </Button> + <Button + onClick={handleSubmit} + disabled={!question.trim() || isSubmitting} + > + {isSubmitting ? ( + <> + <Loader2 className="h-4 w-4 mr-2 animate-spin" /> + 제출 중... + </> + ) : ( + <> + <Send className="h-4 w-4 mr-2" /> + 질문 제출 + </> + )} + </Button> + </div> + </div> + + {/* Info Box */} + <div className="p-3 bg-muted/50 rounded-lg"> + <div className="flex items-start gap-2"> + <AlertCircle className="h-4 w-4 text-muted-foreground mt-0.5" /> + <p className="text-xs text-muted-foreground"> + 제출된 질문은 구매담당자가 확인 후 답변을 제공합니다. + 긴급한 질문은 Priority를 High로 설정해주세요. + </p> + </div> + </div> + </div> + </DialogContent> + </Dialog> + ) +}
\ No newline at end of file |
