diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-08-04 09:39:21 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-08-04 09:39:21 +0000 |
| commit | 53ad72732f781e6c6d5ddb3776ea47aec010af8e (patch) | |
| tree | e676287827f8634be767a674b8ad08b6ed7eb3e6 /lib/site-visit/vendor-info-view-dialog.tsx | |
| parent | 3e4d15271322397764601dee09441af8a5b3adf5 (diff) | |
(최겸) PQ/실사 수정 및 개발
Diffstat (limited to 'lib/site-visit/vendor-info-view-dialog.tsx')
| -rw-r--r-- | lib/site-visit/vendor-info-view-dialog.tsx | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/lib/site-visit/vendor-info-view-dialog.tsx b/lib/site-visit/vendor-info-view-dialog.tsx new file mode 100644 index 00000000..b9daf83e --- /dev/null +++ b/lib/site-visit/vendor-info-view-dialog.tsx @@ -0,0 +1,279 @@ +"use client"
+
+import * as React from "react"
+import { format } from "date-fns"
+import { ko } from "date-fns/locale"
+import { Building2, User, Phone, Mail, FileText, Calendar } from "lucide-react"
+
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { toast } from "sonner"
+
+interface VendorInfo {
+ id: number
+ siteVisitRequestId: number
+ factoryName: string
+ factoryLocation: string
+ factoryAddress: string
+ factoryPicName: string
+ factoryPicPhone: string
+ factoryPicEmail: string
+ factoryDirections: string | null
+ accessProcedure: string | null
+ hasAttachments: boolean
+ otherInfo: string | null
+ submittedAt: Date
+ submittedBy: number
+ createdAt: Date
+ updatedAt: Date
+}
+
+interface Attachment {
+ id: number
+ siteVisitRequestId: number
+ vendorSiteVisitInfoId: number | null
+ fileName: string
+ originalFileName: string | null
+ filePath: string
+ fileSize: number | null
+ mimeType: string | null
+ createdAt: Date
+ updatedAt: Date
+}
+
+interface VendorInfoViewDialogProps {
+ isOpen: boolean
+ onClose: () => void
+ siteVisitRequestId: number | null
+}
+
+export function VendorInfoViewDialog({
+ isOpen,
+ onClose,
+ siteVisitRequestId,
+}: VendorInfoViewDialogProps) {
+ const [data, setData] = React.useState<VendorInfo | null>(null)
+ const [attachments, setAttachments] = React.useState<Attachment[]>([])
+ const [isLoading, setIsLoading] = React.useState(false)
+
+ // 데이터 로드
+ React.useEffect(() => {
+ if (isOpen && siteVisitRequestId) {
+ loadVendorInfo()
+ }
+ }, [isOpen, siteVisitRequestId])
+
+ const loadVendorInfo = async () => {
+ if (!siteVisitRequestId) return
+
+ setIsLoading(true)
+ try {
+ const { getVendorSiteVisitInfoAction } = await import("./service")
+ const result = await getVendorSiteVisitInfoAction(siteVisitRequestId)
+
+ if (result.success && result.data) {
+ setData(result.data.vendorInfo)
+ setAttachments(result.data.attachments || [])
+ } else {
+ toast.error("협력업체 정보를 불러올 수 없습니다.")
+ }
+ } catch (error) {
+ console.error("협력업체 정보 로드 오류:", error)
+ toast.error("협력업체 정보를 불러오는 중 오류가 발생했습니다.")
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ const formatDate = (date: Date | null) => {
+ if (!date) return "-"
+ return format(date, "yyyy.MM.dd", { locale: ko })
+ }
+
+ return (
+ <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
+ <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
+ <DialogHeader>
+ <DialogTitle>협력업체 방문실사 정보</DialogTitle>
+ <DialogDescription>
+ 협력업체가 입력한 방문실사 관련 정보를 확인할 수 있습니다.
+ </DialogDescription>
+ </DialogHeader>
+
+ {isLoading ? (
+ <div className="flex items-center justify-center py-8">
+ <div className="text-center">
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
+ <p className="text-muted-foreground">협력업체 정보를 불러오는 중...</p>
+ </div>
+ </div>
+ ) : data ? (
+ <div className="space-y-6">
+ {/* 협력업체 정보 */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <Building2 className="h-5 w-5" />
+ 협력업체 공장 정보
+ </CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
+ <div className="space-y-4">
+ <div>
+ <h4 className="font-semibold mb-2">공장 기본 정보</h4>
+ <div className="space-y-2 text-sm">
+ <div><span className="font-medium">공장명:</span> {data.factoryName}</div>
+ <div><span className="font-medium">공장위치:</span> {data.factoryLocation}</div>
+ <div><span className="font-medium">공장주소:</span> {data.factoryAddress}</div>
+ </div>
+ </div>
+
+ <div>
+ <h4 className="font-semibold mb-2">공장 PIC 정보</h4>
+ <div className="space-y-2 text-sm">
+ <div className="flex items-center gap-2">
+ <User className="h-4 w-4" />
+ <span>{data.factoryPicName}</span>
+ </div>
+ <div className="flex items-center gap-2">
+ <Phone className="h-4 w-4" />
+ <span>{data.factoryPicPhone}</span>
+ </div>
+ <div className="flex items-center gap-2">
+ <Mail className="h-4 w-4" />
+ <span>{data.factoryPicEmail}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div className="space-y-4">
+ {data.factoryDirections && (
+ <div>
+ <h4 className="font-semibold mb-2">공장 가는 법</h4>
+ <div className="bg-muted p-3 rounded-md">
+ <p className="text-sm whitespace-pre-wrap">{data.factoryDirections}</p>
+ </div>
+ </div>
+ )}
+
+ {data.accessProcedure && (
+ <div>
+ <h4 className="font-semibold mb-2">공장 출입절차</h4>
+ <div className="bg-muted p-3 rounded-md">
+ <p className="text-sm whitespace-pre-wrap">{data.accessProcedure}</p>
+ </div>
+ </div>
+ )}
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+
+ {/* 첨부파일 */}
+ {attachments.length > 0 && (
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <FileText className="h-5 w-5" />
+ 협력업체 첨부파일 ({attachments.length}개)
+ </CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="space-y-2">
+ {attachments.map((attachment) => (
+ <div key={attachment.id} className="flex items-center justify-between p-2 border rounded-md">
+ <div className="flex items-center space-x-2 flex-1 min-w-0">
+ <FileText className="h-4 w-4 text-muted-foreground" />
+ <span className="text-sm truncate">{attachment.originalFileName}</span>
+ <span className="text-xs text-muted-foreground">
+ ({Math.round((attachment.fileSize || 0) / 1024)}KB)
+ </span>
+ </div>
+ <Button
+ type="button"
+ variant="outline"
+ size="sm"
+ onClick={async () => {
+ try {
+ const { downloadFile } = await import('@/lib/file-download')
+ await downloadFile(attachment.filePath, attachment.originalFileName || '', {
+ showToast: true,
+ onError: (error) => {
+ console.error('다운로드 오류:', error)
+ toast.error(error)
+ },
+ onSuccess: (fileName, fileSize) => {
+ console.log(`다운로드 성공: ${fileName} (${fileSize} bytes)`)
+ }
+ })
+ } catch (error) {
+ console.error('다운로드 오류:', error)
+ toast.error('파일 다운로드 중 오류가 발생했습니다.')
+ }
+ }}
+ >
+ 다운로드
+ </Button>
+ </div>
+ ))}
+ </div>
+ </CardContent>
+ </Card>
+ )}
+
+ {/* 기타 정보 */}
+ {data.otherInfo && (
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <FileText className="h-5 w-5" />
+ 기타 정보
+ </CardTitle>
+ </CardHeader>
+ <CardContent>
+ <p className="text-sm whitespace-pre-wrap">{data.otherInfo}</p>
+ </CardContent>
+ </Card>
+ )}
+
+ {/* 제출 정보 */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <Calendar className="h-5 w-5" />
+ 제출 정보
+ </CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="grid grid-cols-2 gap-4">
+ <div>
+ <div className="space-y-2 text-sm">
+ <div><span className="font-medium">제출일:</span> {formatDate(data.submittedAt)}</div>
+ <div><span className="font-medium">첨부파일:</span> {data.hasAttachments ? "있음" : "없음"}</div>
+ </div>
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+ </div>
+ ) : (
+ <div className="text-center py-8">
+ <div className="text-muted-foreground">
+ <FileText className="h-12 w-12 mx-auto mb-4 opacity-50" />
+ <p>협력업체가 아직 정보를 입력하지 않았습니다.</p>
+ </div>
+ </div>
+ )}
+ </DialogContent>
+ </Dialog>
+ )
+}
\ No newline at end of file |
