summaryrefslogtreecommitdiff
path: root/components/investigation
diff options
context:
space:
mode:
Diffstat (limited to 'components/investigation')
-rw-r--r--components/investigation/supplement-request-dialog.tsx336
-rw-r--r--components/investigation/supplement-response-dialog.tsx483
2 files changed, 819 insertions, 0 deletions
diff --git a/components/investigation/supplement-request-dialog.tsx b/components/investigation/supplement-request-dialog.tsx
new file mode 100644
index 00000000..c0af36c7
--- /dev/null
+++ b/components/investigation/supplement-request-dialog.tsx
@@ -0,0 +1,336 @@
+"use client"
+
+import * as React from "react"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import { Button } from "@/components/ui/button"
+import { Textarea } from "@/components/ui/textarea"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Badge } from "@/components/ui/badge"
+import { useToast } from "@/hooks/use-toast"
+import {
+ requestSupplementReinspectionAction,
+ requestSupplementDocumentAction
+} from "@/lib/vendor-investigation/service"
+
+interface SupplementRequestDialogProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ investigationId: number
+ investigationMethod: string
+ vendorName: string
+}
+
+export function SupplementRequestDialog({
+ open,
+ onOpenChange,
+ investigationId,
+ investigationMethod,
+ vendorName
+}: SupplementRequestDialogProps) {
+ const { toast } = useToast()
+ const [isSubmitting, setIsSubmitting] = React.useState(false)
+ const [requestType, setRequestType] = React.useState<"REINSPECT" | "DOCUMENT">("REINSPECT")
+
+ // 재실사 요청 데이터
+ const [reinspectData, setReinspectData] = React.useState({
+ inspectionDuration: 1.0,
+ requestedStartDate: "",
+ requestedEndDate: "",
+ additionalRequests: ""
+ })
+
+ // 서류제출 요청 데이터
+ const [documentData, setDocumentData] = React.useState({
+ requiredDocuments: [""],
+ additionalRequests: ""
+ })
+
+ // 보완 요청이 가능한 실사 방법인지 확인
+ const canRequestSupplement = investigationMethod === "PRODUCT_INSPECTION" ||
+ investigationMethod === "SITE_VISIT_EVAL"
+
+ const handleSubmit = async () => {
+ if (!canRequestSupplement) {
+ toast({
+ title: "보완 요청 불가",
+ description: "현재 실사 방법에서는 보완 요청을 할 수 없습니다.",
+ variant: "destructive"
+ })
+ return
+ }
+
+ try {
+ setIsSubmitting(true)
+
+ if (requestType === "REINSPECT") {
+ const result = await requestSupplementReinspectionAction({
+ investigationId,
+ siteVisitData: {
+ inspectionDuration: reinspectData.inspectionDuration,
+ requestedStartDate: reinspectData.requestedStartDate ? new Date(reinspectData.requestedStartDate) : undefined,
+ requestedEndDate: reinspectData.requestedEndDate ? new Date(reinspectData.requestedEndDate) : undefined,
+ additionalRequests: reinspectData.additionalRequests
+ }
+ })
+
+ if (result.success) {
+ toast({
+ title: "보완-재실사 요청 완료",
+ description: "재실사 요청이 성공적으로 생성되었습니다.",
+ })
+ onOpenChange(false)
+ } else {
+ toast({
+ title: "요청 실패",
+ description: result.error || "재실사 요청 중 오류가 발생했습니다.",
+ variant: "destructive"
+ })
+ }
+ } else {
+ const result = await requestSupplementDocumentAction({
+ investigationId,
+ documentRequests: {
+ requiredDocuments: documentData.requiredDocuments.filter(doc => doc.trim() !== ""),
+ additionalRequests: documentData.additionalRequests
+ }
+ })
+
+ if (result.success) {
+ toast({
+ title: "보완-서류제출 요청 완료",
+ description: "서류제출 요청이 성공적으로 생성되었습니다.",
+ })
+ onOpenChange(false)
+ } else {
+ toast({
+ title: "요청 실패",
+ description: result.error || "서류제출 요청 중 오류가 발생했습니다.",
+ variant: "destructive"
+ })
+ }
+ }
+ } catch (error) {
+ console.error("보완 요청 오류:", error)
+ toast({
+ title: "요청 실패",
+ description: "보완 요청 중 오류가 발생했습니다.",
+ variant: "destructive"
+ })
+ } finally {
+ setIsSubmitting(false)
+ }
+ }
+
+ const addDocument = () => {
+ setDocumentData(prev => ({
+ ...prev,
+ requiredDocuments: [...prev.requiredDocuments, ""]
+ }))
+ }
+
+ const removeDocument = (index: number) => {
+ setDocumentData(prev => ({
+ ...prev,
+ requiredDocuments: prev.requiredDocuments.filter((_, i) => i !== index)
+ }))
+ }
+
+ const updateDocument = (index: number, value: string) => {
+ setDocumentData(prev => ({
+ ...prev,
+ requiredDocuments: prev.requiredDocuments.map((doc, i) => i === index ? value : doc)
+ }))
+ }
+
+ if (!canRequestSupplement) {
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent>
+ <DialogHeader>
+ <DialogTitle>보완 요청 불가</DialogTitle>
+ <DialogDescription>
+ 현재 실사 방법({investigationMethod})에서는 보완 요청을 할 수 없습니다.
+ 보완 요청은 제품검사평가(PRODUCT_INSPECTION) 또는 방문실사평가(SITE_VISIT_EVAL)에서만 가능합니다.
+ </DialogDescription>
+ </DialogHeader>
+ <DialogFooter>
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
+ 확인
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
+ }
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="max-w-2xl">
+ <DialogHeader>
+ <DialogTitle>보완 요청</DialogTitle>
+ <DialogDescription>
+ {vendorName}에 대한 보완 요청을 생성합니다.
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="space-y-6">
+ {/* 요청 유형 선택 */}
+ <div className="space-y-2">
+ <Label>보완 요청 유형</Label>
+ <div className="flex gap-4">
+ <Button
+ type="button"
+ variant={requestType === "REINSPECT" ? "default" : "outline"}
+ onClick={() => setRequestType("REINSPECT")}
+ >
+ 보완-재실사
+ </Button>
+ <Button
+ type="button"
+ variant={requestType === "DOCUMENT" ? "default" : "outline"}
+ onClick={() => setRequestType("DOCUMENT")}
+ >
+ 보완-서류제출
+ </Button>
+ </div>
+ </div>
+
+ {/* 재실사 요청 폼 */}
+ {requestType === "REINSPECT" && (
+ <div className="space-y-4">
+ <div className="grid grid-cols-2 gap-4">
+ <div className="space-y-2">
+ <Label htmlFor="duration">실사 기간 (일)</Label>
+ <Input
+ id="duration"
+ type="number"
+ step="0.1"
+ min="0.1"
+ value={reinspectData.inspectionDuration}
+ onChange={(e) => setReinspectData(prev => ({
+ ...prev,
+ inspectionDuration: parseFloat(e.target.value) || 0
+ }))}
+ />
+ </div>
+ <div className="space-y-2">
+ <Label>실사 방법</Label>
+ <Badge variant="outline">
+ {investigationMethod === "PRODUCT_INSPECTION" ? "제품검사평가" : "방문실사평가"}
+ </Badge>
+ </div>
+ </div>
+
+ <div className="grid grid-cols-2 gap-4">
+ <div className="space-y-2">
+ <Label htmlFor="startDate">요청 시작일</Label>
+ <Input
+ id="startDate"
+ type="date"
+ value={reinspectData.requestedStartDate}
+ onChange={(e) => setReinspectData(prev => ({
+ ...prev,
+ requestedStartDate: e.target.value
+ }))}
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="endDate">요청 종료일</Label>
+ <Input
+ id="endDate"
+ type="date"
+ value={reinspectData.requestedEndDate}
+ onChange={(e) => setReinspectData(prev => ({
+ ...prev,
+ requestedEndDate: e.target.value
+ }))}
+ />
+ </div>
+ </div>
+
+ <div className="space-y-2">
+ <Label htmlFor="reinspectRequests">추가 요청사항</Label>
+ <Textarea
+ id="reinspectRequests"
+ placeholder="재실사에 대한 추가 요청사항을 입력하세요"
+ value={reinspectData.additionalRequests}
+ onChange={(e) => setReinspectData(prev => ({
+ ...prev,
+ additionalRequests: e.target.value
+ }))}
+ className="min-h-20"
+ />
+ </div>
+ </div>
+ )}
+
+ {/* 서류제출 요청 폼 */}
+ {requestType === "DOCUMENT" && (
+ <div className="space-y-4">
+ <div className="space-y-2">
+ <Label>필요 서류 목록</Label>
+ {documentData.requiredDocuments.map((doc, index) => (
+ <div key={index} className="flex gap-2">
+ <Input
+ placeholder="필요한 서류명을 입력하세요"
+ value={doc}
+ onChange={(e) => updateDocument(index, e.target.value)}
+ />
+ <Button
+ type="button"
+ variant="outline"
+ size="sm"
+ onClick={() => removeDocument(index)}
+ disabled={documentData.requiredDocuments.length === 1}
+ >
+ 삭제
+ </Button>
+ </div>
+ ))}
+ <Button
+ type="button"
+ variant="outline"
+ size="sm"
+ onClick={addDocument}
+ >
+ 서류 추가
+ </Button>
+ </div>
+
+ <div className="space-y-2">
+ <Label htmlFor="documentRequests">추가 요청사항</Label>
+ <Textarea
+ id="documentRequests"
+ placeholder="서류제출에 대한 추가 요청사항을 입력하세요"
+ value={documentData.additionalRequests}
+ onChange={(e) => setDocumentData(prev => ({
+ ...prev,
+ additionalRequests: e.target.value
+ }))}
+ className="min-h-20"
+ />
+ </div>
+ </div>
+ )}
+ </div>
+
+ <DialogFooter>
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
+ 취소
+ </Button>
+ <Button onClick={handleSubmit} disabled={isSubmitting}>
+ {isSubmitting ? "요청 중..." : "보완 요청"}
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
+}
diff --git a/components/investigation/supplement-response-dialog.tsx b/components/investigation/supplement-response-dialog.tsx
new file mode 100644
index 00000000..8490c15c
--- /dev/null
+++ b/components/investigation/supplement-response-dialog.tsx
@@ -0,0 +1,483 @@
+"use client"
+
+import * as React from "react"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import { Button } from "@/components/ui/button"
+import { Textarea } from "@/components/ui/textarea"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Badge } from "@/components/ui/badge"
+import { useToast } from "@/hooks/use-toast"
+import {
+ Dropzone,
+ DropzoneDescription,
+ DropzoneInput,
+ DropzoneTitle,
+ DropzoneUploadIcon,
+ DropzoneZone,
+} from "@/components/ui/dropzone"
+import {
+ FileList,
+ FileListAction,
+ FileListDescription,
+ FileListHeader,
+ FileListIcon,
+ FileListInfo,
+ FileListItem,
+ FileListName,
+} from "@/components/ui/file-list"
+import { X, Download } from "lucide-react"
+import prettyBytes from "pretty-bytes"
+import {
+ submitSupplementDocumentResponseAction
+} from "@/lib/vendor-investigation/service"
+import { uploadVendorFileAction } from "@/lib/pq/service"
+
+interface SupplementResponseDialogProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ investigationId: number
+ supplementType: "REINSPECT" | "DOCUMENT"
+ vendorName: string
+ requiredDocuments?: string[]
+ additionalRequests?: string
+}
+
+interface LocalFileState {
+ fileObj: File
+ uploaded: boolean
+}
+
+export function SupplementResponseDialog({
+ open,
+ onOpenChange,
+ investigationId,
+ supplementType,
+ vendorName,
+ requiredDocuments = [],
+ additionalRequests = ""
+}: SupplementResponseDialogProps) {
+ const { toast } = useToast()
+ const [isSubmitting, setIsSubmitting] = React.useState(false)
+ const [isUploading, setIsUploading] = React.useState(false)
+
+ // 서류제출 응답 데이터
+ const [responseData, setResponseData] = React.useState({
+ responseText: "",
+ uploadedFiles: [] as Array<{
+ fileName: string
+ url: string
+ size?: number
+ }>,
+ newUploads: [] as LocalFileState[]
+ })
+
+ // 재실사 응답 데이터
+ const [reinspectData, setReinspectData] = React.useState({
+ inspectionDate: "",
+ inspectionDuration: 1.0,
+ inspectionResults: "",
+ additionalNotes: ""
+ })
+
+ const handleFileDrop = (files: File[]) => {
+ const newFiles: LocalFileState[] = files.map(file => ({
+ fileObj: file,
+ uploaded: false
+ }))
+
+ setResponseData(prev => ({
+ ...prev,
+ newUploads: [...prev.newUploads, ...newFiles]
+ }))
+ }
+
+ const removeNewUpload = (index: number) => {
+ setResponseData(prev => ({
+ ...prev,
+ newUploads: prev.newUploads.filter((_, i) => i !== index)
+ }))
+ }
+
+ const removeUploadedFile = (index: number) => {
+ setResponseData(prev => ({
+ ...prev,
+ uploadedFiles: prev.uploadedFiles.filter((_, i) => i !== index)
+ }))
+ }
+
+ const uploadFiles = async () => {
+ if (responseData.newUploads.length === 0) return
+
+ setIsUploading(true)
+ try {
+ for (const localFile of responseData.newUploads) {
+ const uploadResult = await uploadVendorFileAction(localFile.fileObj)
+ setResponseData(prev => ({
+ ...prev,
+ uploadedFiles: [...prev.uploadedFiles, {
+ fileName: uploadResult.fileName,
+ url: uploadResult.url,
+ size: uploadResult.size
+ }]
+ }))
+ }
+
+ setResponseData(prev => ({
+ ...prev,
+ newUploads: []
+ }))
+
+ toast({
+ title: "파일 업로드 완료",
+ description: "파일이 성공적으로 업로드되었습니다.",
+ })
+ } catch (error) {
+ console.error("파일 업로드 오류:", error)
+ toast({
+ title: "업로드 실패",
+ description: "파일 업로드 중 오류가 발생했습니다.",
+ variant: "destructive"
+ })
+ } finally {
+ setIsUploading(false)
+ }
+ }
+
+ const handleSubmit = async () => {
+ if (supplementType === "DOCUMENT") {
+ // 서류제출 응답 검증
+ if (!responseData.responseText.trim()) {
+ toast({
+ title: "응답 필요",
+ description: "응답 내용을 입력해주세요.",
+ variant: "destructive"
+ })
+ return
+ }
+
+ if (responseData.uploadedFiles.length === 0 && responseData.newUploads.length > 0) {
+ toast({
+ title: "파일 업로드 필요",
+ description: "새로 추가한 파일을 먼저 업로드해주세요.",
+ variant: "destructive"
+ })
+ return
+ }
+ } else {
+ // 재실사 응답 검증
+ if (!reinspectData.inspectionDate || !reinspectData.inspectionResults.trim()) {
+ toast({
+ title: "필수 정보 필요",
+ description: "실사 일정과 결과를 입력해주세요.",
+ variant: "destructive"
+ })
+ return
+ }
+ }
+
+ try {
+ setIsSubmitting(true)
+
+ if (supplementType === "DOCUMENT") {
+ const result = await submitSupplementDocumentResponseAction({
+ investigationId,
+ responseData: {
+ responseText: responseData.responseText,
+ attachments: responseData.uploadedFiles.map(file => ({
+ fileName: file.fileName,
+ url: file.url,
+ size: file.size
+ }))
+ }
+ })
+
+ if (result.success) {
+ toast({
+ title: "서류제출 응답 완료",
+ description: "서류제출 응답이 성공적으로 제출되었습니다.",
+ })
+ onOpenChange(false)
+ } else {
+ toast({
+ title: "제출 실패",
+ description: result.error || "서류제출 응답 중 오류가 발생했습니다.",
+ variant: "destructive"
+ })
+ }
+ } else {
+ // 재실사 응답은 별도 액션 필요 (구현 예정)
+ toast({
+ title: "재실사 응답",
+ description: "재실사 응답 기능은 구현 예정입니다.",
+ })
+ }
+ } catch (error) {
+ console.error("보완 응답 오류:", error)
+ toast({
+ title: "제출 실패",
+ description: "보완 응답 중 오류가 발생했습니다.",
+ variant: "destructive"
+ })
+ } finally {
+ setIsSubmitting(false)
+ }
+ }
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="max-w-3xl">
+ <DialogHeader>
+ <DialogTitle>
+ {supplementType === "REINSPECT" ? "보완-재실사 응답" : "보완-서류제출 응답"}
+ </DialogTitle>
+ <DialogDescription>
+ {vendorName}에 대한 보완 요청에 응답합니다.
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="space-y-6">
+ {/* 서류제출 응답 폼 */}
+ {supplementType === "DOCUMENT" && (
+ <>
+ {/* 요청된 서류 목록 */}
+ {requiredDocuments.length > 0 && (
+ <div className="space-y-2">
+ <Label>요청된 서류 목록</Label>
+ <div className="space-y-1">
+ {requiredDocuments.map((doc, index) => (
+ <div key={index} className="flex items-center gap-2">
+ <Badge variant="outline">{index + 1}</Badge>
+ <span className="text-sm">{doc}</span>
+ </div>
+ ))}
+ </div>
+ </div>
+ )}
+
+ {/* 추가 요청사항 */}
+ {additionalRequests && (
+ <div className="space-y-2">
+ <Label>추가 요청사항</Label>
+ <div className="p-3 bg-muted rounded-md text-sm">
+ {additionalRequests}
+ </div>
+ </div>
+ )}
+
+ {/* 응답 내용 */}
+ <div className="space-y-2">
+ <Label htmlFor="responseText">응답 내용 *</Label>
+ <Textarea
+ id="responseText"
+ placeholder="요청된 서류에 대한 응답 내용을 입력하세요"
+ value={responseData.responseText}
+ onChange={(e) => setResponseData(prev => ({
+ ...prev,
+ responseText: e.target.value
+ }))}
+ className="min-h-32"
+ />
+ </div>
+
+ {/* 파일 업로드 */}
+ <div className="space-y-4">
+ <Label>첨부 파일</Label>
+
+ <Dropzone
+ maxSize={6e8} // 600MB
+ onDropAccepted={handleFileDrop}
+ disabled={isUploading}
+ >
+ {() => (
+ <DropzoneZone className="flex justify-center h-32">
+ <DropzoneInput />
+ <div className="flex items-center gap-6">
+ <DropzoneUploadIcon />
+ <div className="grid gap-0.5">
+ <DropzoneTitle>파일을 드래그하거나 클릭하여 업로드</DropzoneTitle>
+ <DropzoneDescription>
+ PDF, Word, Excel, 이미지 파일 (최대 600MB)
+ </DropzoneDescription>
+ </div>
+ </div>
+ </DropzoneZone>
+ )}
+ </Dropzone>
+
+ {/* 업로드된 파일 목록 */}
+ {(responseData.uploadedFiles.length > 0 || responseData.newUploads.length > 0) && (
+ <div className="space-y-2">
+ <Label>업로드된 파일</Label>
+ <FileList>
+ {responseData.uploadedFiles.map((file, index) => (
+ <FileListItem key={`uploaded-${index}`}>
+ <FileListHeader>
+ <FileListIcon />
+ <FileListInfo>
+ <FileListName>{file.fileName}</FileListName>
+ {file.size && (
+ <FileListDescription>
+ {prettyBytes(file.size)}
+ </FileListDescription>
+ )}
+ </FileListInfo>
+ <div className="flex gap-1">
+ <FileListAction
+ onClick={async () => {
+ try {
+ const { downloadFile } = await import('@/lib/file-download')
+ await downloadFile(file.url, file.fileName, {
+ showToast: true,
+ onError: (error) => {
+ console.error('다운로드 오류:', error)
+ toast({
+ title: "다운로드 실패",
+ description: error,
+ variant: "destructive"
+ })
+ }
+ })
+ } catch (error) {
+ console.error('다운로드 오류:', error)
+ toast({
+ title: "다운로드 실패",
+ description: "파일 다운로드 중 오류가 발생했습니다.",
+ variant: "destructive"
+ })
+ }
+ }}
+ >
+ <Download className="h-4 w-4" />
+ </FileListAction>
+ <FileListAction
+ onClick={() => removeUploadedFile(index)}
+ >
+ <X className="h-4 w-4" />
+ </FileListAction>
+ </div>
+ </FileListHeader>
+ </FileListItem>
+ ))}
+
+ {responseData.newUploads.map((file, index) => (
+ <FileListItem key={`new-${index}`}>
+ <FileListHeader>
+ <FileListIcon />
+ <FileListInfo>
+ <FileListName>{file.fileObj.name}</FileListName>
+ <FileListDescription>
+ {prettyBytes(file.fileObj.size)}
+ </FileListDescription>
+ </FileListInfo>
+ <div className="flex gap-1">
+ <FileListAction
+ onClick={() => removeNewUpload(index)}
+ >
+ <X className="h-4 w-4" />
+ </FileListAction>
+ </div>
+ </FileListHeader>
+ </FileListItem>
+ ))}
+ </FileList>
+
+ {responseData.newUploads.length > 0 && (
+ <Button
+ type="button"
+ variant="outline"
+ size="sm"
+ onClick={uploadFiles}
+ disabled={isUploading}
+ >
+ {isUploading ? "업로드 중..." : "파일 업로드"}
+ </Button>
+ )}
+ </div>
+ )}
+ </div>
+ </>
+ )}
+
+ {/* 재실사 응답 폼 */}
+ {supplementType === "REINSPECT" && (
+ <div className="space-y-4">
+ <div className="grid grid-cols-2 gap-4">
+ <div className="space-y-2">
+ <Label htmlFor="inspectionDate">실사 일정 *</Label>
+ <Input
+ id="inspectionDate"
+ type="date"
+ value={reinspectData.inspectionDate}
+ onChange={(e) => setReinspectData(prev => ({
+ ...prev,
+ inspectionDate: e.target.value
+ }))}
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="duration">실사 기간 (일)</Label>
+ <Input
+ id="duration"
+ type="number"
+ step="0.1"
+ min="0.1"
+ value={reinspectData.inspectionDuration}
+ onChange={(e) => setReinspectData(prev => ({
+ ...prev,
+ inspectionDuration: parseFloat(e.target.value) || 0
+ }))}
+ />
+ </div>
+ </div>
+
+ <div className="space-y-2">
+ <Label htmlFor="inspectionResults">실사 결과 *</Label>
+ <Textarea
+ id="inspectionResults"
+ placeholder="실사 결과를 상세히 입력하세요"
+ value={reinspectData.inspectionResults}
+ onChange={(e) => setReinspectData(prev => ({
+ ...prev,
+ inspectionResults: e.target.value
+ }))}
+ className="min-h-32"
+ />
+ </div>
+
+ <div className="space-y-2">
+ <Label htmlFor="additionalNotes">추가 메모</Label>
+ <Textarea
+ id="additionalNotes"
+ placeholder="추가 메모나 특이사항을 입력하세요"
+ value={reinspectData.additionalNotes}
+ onChange={(e) => setReinspectData(prev => ({
+ ...prev,
+ additionalNotes: e.target.value
+ }))}
+ className="min-h-20"
+ />
+ </div>
+ </div>
+ )}
+ </div>
+
+ <DialogFooter>
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
+ 취소
+ </Button>
+ <Button onClick={handleSubmit} disabled={isSubmitting || isUploading}>
+ {isSubmitting ? "제출 중..." : "응답 제출"}
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
+}