summaryrefslogtreecommitdiff
path: root/lib/tbe-last/vendor/vendor-comment-dialog.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-09-15 14:41:01 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-09-15 14:41:01 +0000
commit4ee8b24cfadf47452807fa2af801385ed60ab47c (patch)
treee1d1fb029f0cf5519c517494bf9a545505c35700 /lib/tbe-last/vendor/vendor-comment-dialog.tsx
parent265859d691a01cdcaaf9154f93c38765bc34df06 (diff)
(대표님) 작업사항 - rfqLast, tbeLast, pdfTron, userAuth
Diffstat (limited to 'lib/tbe-last/vendor/vendor-comment-dialog.tsx')
-rw-r--r--lib/tbe-last/vendor/vendor-comment-dialog.tsx313
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