summaryrefslogtreecommitdiff
path: root/lib/vendor-registration-status/vendor-registration-status-view.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendor-registration-status/vendor-registration-status-view.tsx')
-rw-r--r--lib/vendor-registration-status/vendor-registration-status-view.tsx470
1 files changed, 470 insertions, 0 deletions
diff --git a/lib/vendor-registration-status/vendor-registration-status-view.tsx b/lib/vendor-registration-status/vendor-registration-status-view.tsx
new file mode 100644
index 00000000..b3000f73
--- /dev/null
+++ b/lib/vendor-registration-status/vendor-registration-status-view.tsx
@@ -0,0 +1,470 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Separator } from "@/components/ui/separator"
+import {
+ CheckCircle,
+ XCircle,
+ FileText,
+ Users,
+ Building2,
+ AlertCircle,
+ Eye,
+ Upload
+} from "lucide-react"
+import { DocumentStatusDialog } from "@/components/vendor-regular-registrations/document-status-dialog"
+import { AdditionalInfoDialog } from "@/components/vendor-regular-registrations/additional-info-dialog"
+import { format } from "date-fns"
+import { toast } from "sonner"
+import { fetchVendorRegistrationStatus } from "@/lib/vendor-regular-registrations/service"
+
+// 상태별 정의
+const statusConfig = {
+ audit_pass: {
+ label: "실사통과",
+ color: "bg-blue-100 text-blue-800",
+ description: "품질담당자(QM) 최종 의견에 따라 실사 통과로 결정된 상태"
+ },
+ cp_submitted: {
+ label: "CP등록",
+ color: "bg-green-100 text-green-800",
+ description: "협력업체에서 실사 통과 후 기본계약문서에 대한 답변 제출/서약 완료한 상태"
+ },
+ cp_review: {
+ label: "CP검토",
+ color: "bg-yellow-100 text-yellow-800",
+ description: "협력업체에서 제출한 CP/GTC에 대한 법무검토 의뢰한 상태"
+ },
+ cp_finished: {
+ label: "CP완료",
+ color: "bg-purple-100 text-purple-800",
+ description: "CP 답변에 대한 법무검토 완료되어 정규업체 등록 가능한 상태"
+ },
+ approval_ready: {
+ label: "조건충족",
+ color: "bg-emerald-100 text-emerald-800",
+ description: "정규업체 등록 문서/자료 접수현황에 누락이 없는 상태"
+ },
+ in_review: {
+ label: "정규등록검토",
+ color: "bg-orange-100 text-orange-800",
+ description: "구매담당자 요청에 따라 정규업체 등록 관리자가 정규업체 등록 가능여부 검토"
+ },
+ pending_approval: {
+ label: "장기미등록",
+ color: "bg-red-100 text-red-800",
+ description: "정규업체로 등록 요청되어 3개월 이내 정규업체 등록되지 않은 상태"
+ }
+}
+
+// 필수문서 목록
+const requiredDocuments = [
+ { key: "businessRegistration", label: "사업자등록증" },
+ { key: "creditEvaluation", label: "신용평가서" },
+ { key: "bankCopy", label: "통장사본" },
+ { key: "cpDocument", label: "CP문서" },
+ { key: "gtc", label: "GTC" },
+ { key: "standardSubcontract", label: "표준하도급" },
+ { key: "safetyHealth", label: "안전보건관리" },
+ { key: "ethics", label: "윤리규범준수" },
+ { key: "domesticCredit", label: "내국신용장" },
+ { key: "safetyQualification", label: "안전적격성평가" },
+]
+
+export function VendorRegistrationStatusView() {
+ const [additionalInfoDialogOpen, setAdditionalInfoDialogOpen] = useState(false)
+ const [documentDialogOpen, setDocumentDialogOpen] = useState(false)
+ const [hasSignature, setHasSignature] = useState(false)
+ const [data, setData] = useState<any>(null)
+ const [loading, setLoading] = useState(true)
+
+ // 임시로 vendorId = 1 사용 (실제로는 세션에서 가져와야 함)
+ const vendorId = 1
+
+ // 데이터 로드
+ useEffect(() => {
+ const initialLoad = async () => {
+ try {
+ const result = await fetchVendorRegistrationStatus(vendorId)
+ if (result.success) {
+ setData(result.data)
+ } else {
+ toast.error(result.error)
+ }
+ } catch {
+ toast.error("데이터 로드 중 오류가 발생했습니다.")
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ initialLoad()
+ }, [vendorId])
+
+ if (loading) {
+ return <div className="p-8 text-center">로딩 중...</div>
+ }
+
+ if (!data) {
+ return <div className="p-8 text-center">데이터를 불러올 수 없습니다.</div>
+ }
+
+ const currentStatusConfig = statusConfig[data.registration?.status as keyof typeof statusConfig] || statusConfig.audit_pass
+
+ // 미완성 항목 계산
+ const missingDocuments = requiredDocuments.filter(
+ doc => !data.documentStatus[doc.key as keyof typeof data.documentStatus]
+ )
+
+ // Document Status Dialog에 전달할 registration 데이터 구성
+ const registrationForDialog: any = {
+ id: data.registration?.id || 0,
+ vendorId: data.vendor.id,
+ companyName: data.vendor.companyName,
+ businessNumber: data.vendor.businessNumber,
+ representative: data.vendor.representative || "",
+ potentialCode: data.registration?.potentialCode || "",
+ status: data.registration?.status || "audit_pass",
+ majorItems: "[]", // 빈 JSON 문자열
+ establishmentDate: data.vendor.createdAt || new Date(),
+ registrationRequestDate: data.registration?.registrationRequestDate,
+ assignedDepartment: data.registration?.assignedDepartment,
+ assignedDepartmentCode: data.registration?.assignedDepartmentCode,
+ assignedUser: data.registration?.assignedUser,
+ assignedUserCode: data.registration?.assignedUserCode,
+ remarks: data.registration?.remarks,
+ additionalInfo: data.additionalInfo,
+ documentSubmissions: data.documentStatus, // documentSubmissions를 documentStatus로 설정
+ contractAgreements: [],
+ documentSubmissionsStatus: data.documentStatus,
+ contractAgreementsStatus: {
+ cpDocument: data.documentStatus.cpDocument,
+ gtc: data.documentStatus.gtc,
+ standardSubcontract: data.documentStatus.standardSubcontract,
+ safetyHealth: data.documentStatus.safetyHealth,
+ ethics: data.documentStatus.ethics,
+ domesticCredit: data.documentStatus.domesticCredit,
+ },
+ createdAt: data.registration?.createdAt || new Date(),
+ updatedAt: data.registration?.updatedAt || new Date(),
+ }
+
+ const handleSignatureUpload = () => {
+ // TODO: 서명/직인 업로드 기능 구현
+ setHasSignature(true)
+ toast.success("서명/직인이 등록되었습니다.")
+ }
+
+ const handleAdditionalInfoSave = () => {
+ // 데이터 새로고침
+ loadData()
+ }
+
+ const loadData = async () => {
+ try {
+ const result = await fetchVendorRegistrationStatus(vendorId)
+ if (result.success) {
+ setData(result.data)
+ } else {
+ toast.error(result.error)
+ }
+ } catch {
+ toast.error("데이터 로드 중 오류가 발생했습니다.")
+ }
+ }
+
+ return (
+ <div className="space-y-6">
+ {/* 헤더 섹션 */}
+ <div className="space-y-4">
+ <div className="flex items-center justify-between">
+ <div>
+ <h1 className="text-3xl font-bold">정규업체 등록관리 현황</h1>
+ <p className="text-muted-foreground">
+ {data.registration?.potentialCode || "미등록"} | {data.vendor.companyName}
+ </p>
+ <p className="text-sm text-muted-foreground mt-1">
+ 정규업체 등록 진행현황을 확인하세요.
+ </p>
+ </div>
+ <Badge className={currentStatusConfig.color} variant="secondary">
+ {currentStatusConfig.label}
+ </Badge>
+ </div>
+ </div>
+
+ {/* 회사 서명/직인 등록 */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <FileText className="w-5 h-5" />
+ 회사 서명/직인 등록
+ <Badge variant="destructive" className="text-xs">필수</Badge>
+ </CardTitle>
+ </CardHeader>
+ <CardContent>
+ {hasSignature ? (
+ <div className="flex items-center gap-3 p-4 border rounded-lg bg-green-50">
+ <CheckCircle className="w-5 h-5 text-green-600" />
+ <span className="text-green-800">서명/직인이 등록되었습니다.</span>
+ </div>
+ ) : (
+ <Button
+ onClick={handleSignatureUpload}
+ className="w-full h-20 border-2 border-dashed border-muted-foreground/25 bg-muted/25"
+ variant="outline"
+ >
+ <div className="text-center">
+ <Upload className="w-6 h-6 mx-auto mb-2" />
+ <span>서명/직인 등록하기</span>
+ </div>
+ </Button>
+ )}
+ </CardContent>
+ </Card>
+
+ {/* 기본 정보 */}
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <Building2 className="w-5 h-5" />
+ 업체 정보
+ </CardTitle>
+ </CardHeader>
+ <CardContent className="space-y-3">
+ <div className="grid grid-cols-2 gap-4">
+ <div>
+ <span className="text-sm font-medium text-gray-600">업체명:</span>
+ <p className="mt-1">{data.vendor.companyName}</p>
+ </div>
+ <div>
+ <span className="text-sm font-medium text-gray-600">사업자번호:</span>
+ <p className="mt-1">{data.vendor.businessNumber}</p>
+ </div>
+ </div>
+ <div className="grid grid-cols-2 gap-4">
+ <div>
+ <span className="text-sm font-medium text-gray-600">업체구분:</span>
+ <p className="mt-1">{data.registration ? "정규업체" : "잠재업체"}</p>
+ </div>
+ <div>
+ <span className="text-sm font-medium text-gray-600">eVCP 가입:</span>
+ <p className="mt-1">{data.vendor.createdAt ? format(new Date(data.vendor.createdAt), "yyyy.MM.dd") : "-"}</p>
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2">
+ <Users className="w-5 h-5" />
+ 담당자 정보
+ </CardTitle>
+ </CardHeader>
+ <CardContent className="space-y-3">
+ <div className="grid grid-cols-2 gap-4">
+ <div>
+ <span className="text-sm font-medium text-gray-600">SHI 담당자:</span>
+ <p className="mt-1">{data.registration?.assignedDepartment || "-"} {data.registration?.assignedUser || "-"}</p>
+ </div>
+ <div>
+ <span className="text-sm font-medium text-gray-600">진행상태:</span>
+ <Badge className={`mt-1 ${currentStatusConfig.color}`} variant="secondary">
+ {currentStatusConfig.label}
+ </Badge>
+ </div>
+ </div>
+ <div className="grid grid-cols-2 gap-4">
+ <div>
+ <span className="text-sm font-medium text-gray-600">상태변경일:</span>
+ <p className="mt-1">{data.registration?.updatedAt ? format(new Date(data.registration.updatedAt), "yyyy.MM.dd") : "-"}</p>
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+ </div>
+
+ {/* 미완항목 */}
+ {missingDocuments.length > 0 && (
+ <Card className="border-red-200 bg-red-50">
+ <CardHeader>
+ <CardTitle className="flex items-center gap-2 text-red-800">
+ <AlertCircle className="w-5 h-5" />
+ 미완항목
+ </CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
+ {data.incompleteItemsCount.documents > 0 && (
+ <div className="flex items-center justify-between p-3 bg-white rounded-lg border">
+ <span className="text-sm font-medium">미제출문서</span>
+ <Badge variant="destructive">{data.incompleteItemsCount.documents} 건</Badge>
+ </div>
+ )}
+ {!data.documentStatus.auditResult && (
+ <div className="flex items-center justify-between p-3 bg-white rounded-lg border">
+ <span className="text-sm font-medium">실사결과</span>
+ <Badge variant="destructive">미실시</Badge>
+ </div>
+ )}
+ {data.incompleteItemsCount.additionalInfo > 0 && (
+ <div className="flex items-center justify-between p-3 bg-white rounded-lg border">
+ <span className="text-sm font-medium">추가정보</span>
+ <Badge variant="destructive">미입력</Badge>
+ </div>
+ )}
+ </div>
+ </CardContent>
+ </Card>
+ )}
+
+ {/* 상세 진행현황 */}
+ <Card>
+ <CardHeader>
+ <CardTitle>상세 진행현황</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="space-y-6">
+ {/* 기본 진행상황 */}
+ <div className="grid grid-cols-4 gap-4 text-center">
+ <div className="space-y-2">
+ <div className="text-sm font-medium text-gray-600">PQ 제출</div>
+ <div className="text-lg font-semibold">
+ {data.pqSubmission ? (
+ <div className="flex items-center justify-center gap-2">
+ <CheckCircle className="w-5 h-5 text-green-600" />
+ {format(new Date(data.pqSubmission.createdAt), "yyyy.MM.dd")}
+ </div>
+ ) : (
+ <div className="flex items-center justify-center gap-2">
+ <XCircle className="w-5 h-5 text-red-500" />
+ 미제출
+ </div>
+ )}
+ </div>
+ </div>
+ <div className="space-y-2">
+ <div className="text-sm font-medium text-gray-600">실사 통과</div>
+ <div className="text-lg font-semibold">
+ {data.auditPassed ? (
+ <div className="flex items-center justify-center gap-2">
+ <CheckCircle className="w-5 h-5 text-green-600" />
+ 통과
+ </div>
+ ) : (
+ <div className="flex items-center justify-center gap-2">
+ <XCircle className="w-5 h-5 text-red-500" />
+ 미통과
+ </div>
+ )}
+ </div>
+ </div>
+ <div className="space-y-2">
+ <div className="text-sm font-medium text-gray-600">문서 현황</div>
+ <Button
+ onClick={() => setDocumentDialogOpen(true)}
+ variant="outline"
+ size="sm"
+ className="flex items-center gap-2"
+ >
+ <Eye className="w-4 h-4" />
+ 확인하기
+ </Button>
+ </div>
+ <div className="space-y-2">
+ <div className="text-sm font-medium text-gray-600">추가정보</div>
+ <Button
+ onClick={() => setAdditionalInfoDialogOpen(true)}
+ variant={data.additionalInfo ? "outline" : "default"}
+ size="sm"
+ className="flex items-center gap-2"
+ >
+ <FileText className="w-4 h-4" />
+ {data.additionalInfo ? "수정하기" : "등록하기"}
+ </Button>
+ </div>
+ </div>
+
+ <Separator />
+
+ {/* 필수문서 상태 */}
+ <div>
+ <h4 className="text-sm font-medium text-gray-600 mb-4">필수문서 제출 현황</h4>
+ <div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-3">
+ {requiredDocuments.map((doc) => {
+ const isSubmitted = data.documentStatus[doc.key as keyof typeof data.documentStatus]
+ return (
+ <div
+ key={doc.key}
+ className={`p-3 rounded-lg border text-center ${
+ isSubmitted
+ ? 'bg-green-50 border-green-200'
+ : 'bg-red-50 border-red-200'
+ }`}
+ >
+ <div className="flex items-center justify-center mb-2">
+ {isSubmitted ? (
+ <CheckCircle className="w-5 h-5 text-green-600" />
+ ) : (
+ <XCircle className="w-5 h-5 text-red-500" />
+ )}
+ </div>
+ <div className="text-xs font-medium">{doc.label}</div>
+ {isSubmitted && (
+ <Button
+ variant="ghost"
+ size="sm"
+ className="mt-2 h-6 text-xs"
+ >
+ <Eye className="w-3 h-3 mr-1" />
+ 보기
+ </Button>
+ )}
+ </div>
+ )
+ })}
+ </div>
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+
+ {/* 상태 설명 */}
+ <Card>
+ <CardHeader>
+ <CardTitle>현재 상태 안내</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="flex items-start gap-3">
+ <Badge className={currentStatusConfig.color} variant="secondary">
+ {currentStatusConfig.label}
+ </Badge>
+ <p className="text-sm text-muted-foreground">
+ {currentStatusConfig.description}
+ </p>
+ </div>
+ </CardContent>
+ </Card>
+
+ {/* 문서 현황 Dialog */}
+ <DocumentStatusDialog
+ open={documentDialogOpen}
+ onOpenChange={setDocumentDialogOpen}
+ registration={registrationForDialog}
+ />
+
+ {/* 추가정보 입력 Dialog */}
+ <AdditionalInfoDialog
+ open={additionalInfoDialogOpen}
+ onOpenChange={setAdditionalInfoDialogOpen}
+ vendorId={vendorId}
+ onSave={handleAdditionalInfoSave}
+ />
+ </div>
+ )
+}