summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/actions.ts44
-rw-r--r--app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/page.tsx4
-rw-r--r--app/[lng]/partners/(partners)/sales-force-test/page.tsx32
-rw-r--r--app/[lng]/partners/pq_new/[id]/page.tsx2
-rw-r--r--components/additional-info/join-form.tsx1
-rw-r--r--components/pq-input/pq-input-tabs.tsx34
-rw-r--r--components/pq-input/pq-review-wrapper.tsx23
-rw-r--r--components/vendor-regular-registrations/document-status-dialog.tsx7
-rw-r--r--lib/vendor-basic-info/actions.ts193
-rw-r--r--lib/vendor-basic-info/basic-info-client.tsx (renamed from app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/basic-info-client.tsx)878
-rw-r--r--lib/vendor-basic-info/constants.ts (renamed from app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/constants.ts)0
-rw-r--r--lib/vendor-basic-info/types.ts (renamed from app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/types.ts)0
-rw-r--r--lib/vendor-regular-registrations/table/vendor-regular-registrations-table-columns.tsx1
-rw-r--r--lib/vendors/service.ts11
14 files changed, 797 insertions, 433 deletions
diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/actions.ts b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/actions.ts
deleted file mode 100644
index 866103a6..00000000
--- a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/actions.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-"use server";
-
-import { getVendorBasicInfo } from "@/lib/vendors/service";
-import { VendorFormData } from "./types";
-
-/**
- * 벤더 기본정보를 가져오는 서버 액션
- */
-export async function getVendorData(vendorId: string) {
- try {
- const id = parseInt(vendorId);
- if (isNaN(id)) {
- return null;
- }
-
- const vendorData = await getVendorBasicInfo(id);
- return vendorData;
- } catch (error) {
- console.error("Error in getVendorData:", error);
- return null;
- }
-}
-
-/**
- * 벤더 기본정보를 업데이트하는 서버 액션 (향후 구현)
- */
-export async function updateVendorData(vendorId: string, formData: VendorFormData) {
- try {
- // TODO: 실제 업데이트 로직 구현
- console.log("Updating vendor data:", { vendorId, formData });
-
- // 임시로 성공 응답 반환
- return {
- success: true,
- message: "(개발중입니다) 벤더 정보가 성공적으로 업데이트되었습니다.",
- };
- } catch (error) {
- console.error("Error in updateVendorData:", error);
- return {
- success: false,
- message: "업데이트 중 오류가 발생했습니다.",
- };
- }
-} \ No newline at end of file
diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/page.tsx b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/page.tsx
index ae63d77d..629717fb 100644
--- a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/page.tsx
+++ b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/page.tsx
@@ -1,5 +1,5 @@
-import { getVendorData } from "./actions";
-import BasicInfoClient from "./basic-info-client";
+import { getVendorData } from "@/lib/vendor-basic-info/actions";
+import BasicInfoClient from "@/lib/vendor-basic-info/basic-info-client";
interface VendorBasicPageProps {
params: {
diff --git a/app/[lng]/partners/(partners)/sales-force-test/page.tsx b/app/[lng]/partners/(partners)/sales-force-test/page.tsx
deleted file mode 100644
index 8d6cbfbc..00000000
--- a/app/[lng]/partners/(partners)/sales-force-test/page.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import path from "path";
-import { promises as fs } from "fs";
-
-type PageProps = {
- params: { lng: string };
-};
-
-export default async function Page({ params }: PageProps) {
- const filePath = path.join(
- process.cwd(),
- "app",
- "[lng]",
- "partners",
- "(partners)",
- "sales-force-test",
- "AF_poc.html"
- );
-
- const html = await fs.readFile(filePath, "utf8");
-
- return (
- <div className="w-full h-[100vh]">
- <iframe
- title="Salesforce LWC Test"
- className="w-full h-full border-0"
- srcDoc={html}
- />
- </div>
- );
-}
-
-
diff --git a/app/[lng]/partners/pq_new/[id]/page.tsx b/app/[lng]/partners/pq_new/[id]/page.tsx
index 41c59b47..3c2858f2 100644
--- a/app/[lng]/partners/pq_new/[id]/page.tsx
+++ b/app/[lng]/partners/pq_new/[id]/page.tsx
@@ -160,7 +160,7 @@ export default async function PQEditPage(props: PQEditPageProps) {
)}
{/* PQ 입력 컴포넌트 */}
- <div className={isReadOnly ? "pointer-events-none opacity-60" : ""}>
+ <div className={isReadOnly ? "opacity-60" : ""}>
<PQInputTabs
data={pqData}
vendorId={idAsNumber}
diff --git a/components/additional-info/join-form.tsx b/components/additional-info/join-form.tsx
index 4f3998e3..220547df 100644
--- a/components/additional-info/join-form.tsx
+++ b/components/additional-info/join-form.tsx
@@ -1879,6 +1879,7 @@ export function InfoForm() {
},
} as VendorRegularRegistration}
onRefresh={handleAdditionalInfoSave}
+ isVendorUser={true}
/>
{/* 추가정보 입력 Dialog */}
diff --git a/components/pq-input/pq-input-tabs.tsx b/components/pq-input/pq-input-tabs.tsx
index 0c3b2276..7ae6d16a 100644
--- a/components/pq-input/pq-input-tabs.tsx
+++ b/components/pq-input/pq-input-tabs.tsx
@@ -159,6 +159,27 @@ export function PQInputTabs({
const shouldDisableInput = isReadOnly;
+ // 코드 순서로 정렬하는 함수 (1-1-1, 1-1-2, 1-2-1 순서)
+ const sortByCode = (items: any[]) => {
+ return [...items].sort((a, b) => {
+ const parseCode = (code: string) => {
+ return code.split('-').map(part => parseInt(part, 10))
+ }
+
+ const aCode = parseCode(a.code)
+ const bCode = parseCode(b.code)
+
+ for (let i = 0; i < Math.max(aCode.length, bCode.length); i++) {
+ const aPart = aCode[i] || 0
+ const bPart = bCode[i] || 0
+ if (aPart !== bPart) {
+ return aPart - bPart
+ }
+ }
+ return 0
+ })
+ }
+
// ----------------------------------------------------------------------
// A) Create initial form values
// Mark items as "saved" if they have existing answer or attachments
@@ -167,7 +188,10 @@ export function PQInputTabs({
const answers: PQFormValues["answers"] = []
data.forEach((group) => {
- group.items.forEach((item) => {
+ // 그룹 내 아이템들을 코드 순서로 정렬
+ const sortedItems = sortByCode(group.items)
+
+ sortedItems.forEach((item) => {
// Check if the server item is already "complete"
const hasExistingAnswer = item.answer && item.answer.trim().length > 0
const hasExistingAttachments = item.attachments && item.attachments.length > 0
@@ -634,7 +658,7 @@ export function PQInputTabs({
disabled={isSaving || !isAnyItemDirty || shouldDisableInput}
onClick={handleSaveAll}
>
- {isSaving ? "Saving..." : "Save All"}
+ {isSaving ? "Saving..." : "임시 저장"}
<Save className="ml-2 h-4 w-4" />
</Button>
@@ -644,7 +668,7 @@ export function PQInputTabs({
disabled={!allSaved || isSubmitting || shouldDisableInput}
onClick={handleSubmitPQ}
>
- {isSubmitting ? "Submitting..." : "Submit PQ"}
+ {isSubmitting ? "Submitting..." : "최종 제출"}
<CheckCircle2 className="ml-2 h-4 w-4" />
</Button>
</div>
@@ -655,7 +679,7 @@ export function PQInputTabs({
<TabsContent key={group.groupName} value={group.groupName}>
{/* 2-column grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pb-4">
- {group.items.map((item) => {
+ {sortByCode(group.items).map((item) => {
const { criteriaId, code, checkPoint, description, contractInfo, additionalRequirement } = item
const answerIndex = getAnswerIndex(criteriaId)
if (answerIndex === -1) return null
@@ -670,7 +694,7 @@ export function PQInputTabs({
return (
- <Collapsible key={criteriaId} defaultOpen={!isSaved} className="w-full">
+ <Collapsible key={criteriaId} defaultOpen={isReadOnly || !isSaved} className="w-full">
<Card className={isSaved ? "border-green-200" : ""}>
<CardHeader className="pb-1">
<div className="flex justify-between">
diff --git a/components/pq-input/pq-review-wrapper.tsx b/components/pq-input/pq-review-wrapper.tsx
index 1056189e..cc0f1b40 100644
--- a/components/pq-input/pq-review-wrapper.tsx
+++ b/components/pq-input/pq-review-wrapper.tsx
@@ -67,6 +67,27 @@ export function PQReviewWrapper({
const [shiComments, setShiComments] = React.useState<Record<number, string>>({})
const [isUpdatingComment, setIsUpdatingComment] = React.useState<number | null>(null)
+ // 코드 순서로 정렬하는 함수 (1-1-1, 1-1-2, 1-2-1 순서)
+ const sortByCode = (items: any[]) => {
+ return [...items].sort((a, b) => {
+ const parseCode = (code: string) => {
+ return code.split('-').map(part => parseInt(part, 10))
+ }
+
+ const aCode = parseCode(a.code)
+ const bCode = parseCode(b.code)
+
+ for (let i = 0; i < Math.max(aCode.length, bCode.length); i++) {
+ const aPart = aCode[i] || 0
+ const bPart = bCode[i] || 0
+ if (aPart !== bPart) {
+ return aPart - bPart
+ }
+ }
+ return 0
+ })
+ }
+
// 기존 SHI 코멘트를 로컬 상태에 초기화
React.useEffect(() => {
const initialComments: Record<number, string> = {}
@@ -344,7 +365,7 @@ export function PQReviewWrapper({
<h3 className="text-lg font-medium">{group.groupName}</h3>
<div className="grid grid-cols-1 gap-4">
- {group.items.map((item) => (
+ {sortByCode(group.items).map((item) => (
<Card key={item.criteriaId}>
<CardHeader>
<div className="flex justify-between items-start">
diff --git a/components/vendor-regular-registrations/document-status-dialog.tsx b/components/vendor-regular-registrations/document-status-dialog.tsx
index 848e4977..cfdd03fd 100644
--- a/components/vendor-regular-registrations/document-status-dialog.tsx
+++ b/components/vendor-regular-registrations/document-status-dialog.tsx
@@ -21,6 +21,7 @@ interface DocumentStatusDialogProps {
onOpenChange: (open: boolean) => void;
registration: VendorRegularRegistration | null;
onRefresh?: () => void;
+ isVendorUser?: boolean;
}
const StatusIcon = ({ status }: { status: string | boolean }) => {
@@ -68,6 +69,7 @@ export function DocumentStatusDialog({
onOpenChange,
registration,
onRefresh,
+ isVendorUser = false,
}: DocumentStatusDialogProps) {
if (!registration) return null;
@@ -82,6 +84,11 @@ export function DocumentStatusDialog({
registrationKeys: Object.keys(registration),
fullRegistration: registration
});
+ //isvendoruser인 경우는 실사 결과 파일 다운로드 불가능
+ if (isVendorUser && docKey === "auditResult") {
+ toast.error("실사 결과 파일은 다운로드할 수 없습니다.");
+ return;
+ }
// documentFiles가 없는 경우 처리
if (!registration.documentFiles) {
diff --git a/lib/vendor-basic-info/actions.ts b/lib/vendor-basic-info/actions.ts
new file mode 100644
index 00000000..8428deb9
--- /dev/null
+++ b/lib/vendor-basic-info/actions.ts
@@ -0,0 +1,193 @@
+"use server";
+
+import { getVendorBasicInfo } from "@/lib/vendors/service";
+import { VendorFormData } from "./types";
+import { getPQDataByVendorId } from "@/lib/pq/service";
+import db from "@/db/db"
+import { vendorPQSubmissions, vendorPqCriteriaAnswers, pqCriterias, vendorCriteriaAttachments, vendorAdditionalInfo } from "@/db/schema"
+import { vendorBusinessContacts } from "@/db/schema"
+
+import { eq } from "drizzle-orm";
+
+/**
+ * 벤더 기본정보를 가져오는 서버 액션
+ */
+export async function getVendorData(vendorId: string) {
+ try {
+ const id = parseInt(vendorId);
+ if (isNaN(id)) {
+ return null;
+ }
+
+ const vendorData = await getVendorBasicInfo(id);
+ return vendorData;
+ } catch (error) {
+ console.error("Error in getVendorData:", error);
+ return null;
+ }
+}
+
+/**
+ * 벤더의 PQ 데이터를 가져오는 서버 액션
+ */
+export async function getVendorPQData(vendorId: string) {
+ try {
+ const id = parseInt(vendorId);
+ if (isNaN(id)) {
+ return null;
+ }
+
+ const pqData = await getPQDataByVendorId(id);
+ return pqData;
+ } catch (error) {
+ console.error("Error in getVendorPQData:", error);
+ return null;
+ }
+}
+
+/**
+ * 벤더의 PQ 제출 데이터와 답변을 가져오는 서버 액션
+ */
+export async function getVendorPQSubmissionData(vendorId: string) {
+ try {
+ const id = parseInt(vendorId);
+ if (isNaN(id)) {
+ return null;
+ }
+
+ // 벤더의 모든 PQ 제출 데이터 조회
+ const submissions = await db
+ .select()
+ .from(vendorPQSubmissions)
+ .where(eq(vendorPQSubmissions.vendorId, id));
+
+ if (submissions.length === 0) {
+ return null;
+ }
+
+ // 각 제출에 대한 답변 데이터 조회
+ const submissionData = await Promise.all(
+ submissions.map(async (submission) => {
+ const answers = await db
+ .select({
+ id: vendorPqCriteriaAnswers.id,
+ vendorId: vendorPqCriteriaAnswers.vendorId,
+ criteriaId: vendorPqCriteriaAnswers.criteriaId,
+ projectId: vendorPqCriteriaAnswers.projectId,
+ answer: vendorPqCriteriaAnswers.answer,
+ shiComment: vendorPqCriteriaAnswers.shiComment,
+ vendorReply: vendorPqCriteriaAnswers.vendorReply,
+ createdAt: vendorPqCriteriaAnswers.createdAt,
+ updatedAt: vendorPqCriteriaAnswers.updatedAt,
+ criteriaCode: pqCriterias.code,
+ checkPoint: pqCriterias.checkPoint,
+ description: pqCriterias.description,
+ groupName: pqCriterias.groupName,
+ subGroupName: pqCriterias.subGroupName,
+ inputFormat: pqCriterias.inputFormat
+ })
+ .from(vendorPqCriteriaAnswers)
+ .leftJoin(pqCriterias, eq(vendorPqCriteriaAnswers.criteriaId, pqCriterias.id))
+ .where(eq(vendorPqCriteriaAnswers.vendorId, id));
+
+ // 각 답변에 대한 첨부파일 정보 조회
+ const answersWithAttachments = await Promise.all(
+ answers.map(async (answer) => {
+ const attachments = await db
+ .select({
+ id: vendorCriteriaAttachments.id,
+ fileName: vendorCriteriaAttachments.fileName,
+ originalFileName: vendorCriteriaAttachments.originalFileName,
+ filePath: vendorCriteriaAttachments.filePath,
+ fileType: vendorCriteriaAttachments.fileType,
+ fileSize: vendorCriteriaAttachments.fileSize
+ })
+ .from(vendorCriteriaAttachments)
+ .where(eq(vendorCriteriaAttachments.vendorCriteriaAnswerId, answer.id));
+
+ return {
+ ...answer,
+ uploadedFiles: attachments
+ };
+ })
+ );
+
+ return {
+ submission,
+ answers: answersWithAttachments
+ };
+ })
+ );
+
+ return submissionData;
+ } catch (error) {
+ console.error("Error in getVendorPQSubmissionData:", error);
+ return null;
+ }
+}
+
+/**
+ * 벤더의 추가정보를 가져오는 서버 액션
+ */
+export async function getVendorAdditionalInfo(vendorId: string) {
+ try {
+ const id = parseInt(vendorId);
+ if (isNaN(id)) {
+ return null;
+ }
+
+ const additionalInfo = await db
+ .select()
+ .from(vendorAdditionalInfo)
+ .where(eq(vendorAdditionalInfo.vendorId, id));
+
+ return additionalInfo.length > 0 ? additionalInfo[0] : null;
+ } catch (error) {
+ console.error("Error in getVendorAdditionalInfo:", error);
+ return null;
+ }
+}
+
+/**
+ * 벤더의 업무담당자 정보를 가져오는 서버 액션
+ */
+export async function getVendorBusinessContacts(vendorId: string) {
+ try {
+ const id = parseInt(vendorId);
+ if (isNaN(id)) {
+ return [];
+ }
+
+ const rows = await db
+ .select()
+ .from(vendorBusinessContacts)
+ .where(eq(vendorBusinessContacts.vendorId, id));
+
+ return rows;
+ } catch (error) {
+ console.error("Error in getVendorBusinessContacts:", error);
+ return [];
+ }
+}
+
+/**
+ * 벤더 기본정보를 업데이트하는 서버 액션 (향후 구현)
+ */
+export async function updateVendorData(vendorId: string, formData: VendorFormData) {
+ try {
+ // TODO: 실제 업데이트 로직 구현
+ console.log("Updating vendor data:", { vendorId, formData });
+
+ // 임시로 성공 응답 반환
+ return {
+ success: true,
+ message: "(개발중입니다) 벤더 정보가 성공적으로 업데이트되었습니다.",
+ };
+ } catch (error) {
+ console.error("Error in updateVendorData:", error);
+ return {
+ success: false,
+ message: "업데이트 중 오류가 발생했습니다.",
+ };
+ }
+} \ No newline at end of file
diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/basic-info-client.tsx b/lib/vendor-basic-info/basic-info-client.tsx
index 78d21719..ce8e4dfc 100644
--- a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/basic-info-client.tsx
+++ b/lib/vendor-basic-info/basic-info-client.tsx
@@ -6,6 +6,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { Checkbox } from "@/components/ui/checkbox";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import {
Select,
SelectContent,
@@ -16,7 +17,8 @@ import {
import { Edit, Save, X } from "lucide-react";
import { toast } from "sonner";
import { VendorData, VendorFormData, VendorAttachment } from "./types";
-import { updateVendorData } from "./actions";
+import { updateVendorData, getVendorPQData, getVendorPQSubmissionData, getVendorAdditionalInfo } from "./actions";
+import { getVendorBusinessContacts } from "./actions";
import { noDataString } from "./constants";
import { PQSimpleDialog } from "@/components/vendor-info/pq-simple-dialog";
import { SiteVisitDetailDialog } from "@/lib/site-visit/site-visit-detail-dialog";
@@ -204,18 +206,20 @@ const OrganizationChart = ({
data,
editMode = false,
onChange,
+ pqData,
}: {
data: any;
editMode?: boolean;
onChange?: (field: string, value: string) => void;
+ pqData: any[];
}) => {
const organizationFields = [
- { key: "representative", label: "대표" },
- { key: "sales", label: "영업" },
- { key: "design", label: "설계" },
- { key: "procurement", label: "구매" },
- { key: "production", label: "생산" },
- { key: "quality", label: "품질" },
+ { key: "representative", label: "대표", code: "1-5-1" },
+ { key: "sales", label: "영업", code: "1-5-2" },
+ { key: "design", label: "설계", code: "1-5-3" },
+ { key: "procurement", label: "구매", code: "1-5-4" },
+ { key: "production", label: "생산", code: "1-5-5" },
+ { key: "quality", label: "품질", code: "1-5-6" },
];
return (
@@ -237,7 +241,7 @@ const OrganizationChart = ({
/>
) : (
<span className="text-sm text-muted-foreground">
- {data?.[field.key]?.toString() || noDataString}
+ {pqData && Array.isArray(pqData) && pqData.find((a: any) => a.criteriaCode === field.code)?.answer || data?.[field.key]?.toString() || noDataString}
</span>
)}
</div>
@@ -337,6 +341,10 @@ export default function BasicInfoClient({
const [attachmentsByType, setAttachmentsByType] = useState<Record<string, any[]>>({});
const [periodicGrade, setPeriodicGrade] = useState<string | null>(null);
const [vendorTypeInfo, setVendorTypeInfo] = useState<any>(null);
+ const [pqData, setPqData] = useState<any[]>([]);
+ const [pqSubmissionData, setPqSubmissionData] = useState<any[]>([]);
+ const [additionalInfo, setAdditionalInfo] = useState<any>(null);
+ const [businessContacts, setBusinessContacts] = useState<any[]>([]);
const [formData, setFormData] = useState<VendorFormData>({
vendorName: initialData?.vendorName || "",
representativeName: initialData?.representativeName || "",
@@ -360,19 +368,19 @@ export default function BasicInfoClient({
});
const handleSave = () => {
- startTransition(async () => {
- try {
- const result = await updateVendorData(vendorId, formData);
- if (result.success) {
- toast.success("[개발중] 저장되지 않습니다. 업데이트는 구현중입니다.");
- setEditMode(false);
- } else {
- toast.error(result.message || "저장에 실패했습니다.");
- }
- } catch {
- toast.error("저장 중 오류가 발생했습니다.");
- }
- });
+ // startTransition(async () => {
+ // try {
+ // const result = await updateVendorData(vendorId, formData);
+ // if (result.success) {
+ // toast.success("[개발중] 저장되지 않습니다. 업데이트는 구현중입니다.");
+ // setEditMode(false);
+ // } else {
+ // toast.error(result.message || "저장에 실패했습니다.");
+ // }
+ // } catch {
+ // toast.error("저장 중 오류가 발생했습니다.");
+ // }
+ // });
};
const handleCancel = () => {
@@ -407,27 +415,6 @@ export default function BasicInfoClient({
setFormData((prev) => ({ ...prev, [field]: value }));
};
- // PQ 조회 핸들러
- const handlePQView = () => {
- setPqDialogOpen(true);
- };
-
- // 실사 정보 조회 핸들러
- const handleSiteVisitView = async () => {
- try {
- const siteVisitRequests = await getSiteVisitRequestsByVendorId(parseInt(vendorId));
- if (siteVisitRequests.length === 0) {
- toast.info("실사 정보가 없습니다.");
- return;
- }
- setSelectedSiteVisitRequest(siteVisitRequests[0]); // 첫 번째 실사 정보 선택
- setSiteVisitDialogOpen(true);
- } catch (error) {
- console.error("실사 정보 조회 오류:", error);
- toast.error("실사 정보를 불러오는데 실패했습니다.");
- }
- };
-
// 기본계약 현황 조회 핸들러
const handleContractView = async () => {
try {
@@ -477,32 +464,6 @@ export default function BasicInfoClient({
}
};
- // 추가정보 조회 핸들러
- const handleAdditionalInfoView = async () => {
- try {
- const result = await fetchVendorRegistrationStatus(parseInt(vendorId));
- if (!result.success || !result.data) {
- toast.info("추가정보가 없습니다.");
- return;
- }
-
- // 추가정보가 있는지 확인 (업무담당자 또는 추가정보 데이터가 있는지 체크)
- const { businessContacts, additionalInfo } = result.data;
- const hasBusinessContacts = businessContacts && businessContacts.length > 0;
- const hasAdditionalInfo = additionalInfo && Object.keys(additionalInfo).length > 0;
-
- if (!hasBusinessContacts && !hasAdditionalInfo) {
- toast.info("추가정보가 없습니다.");
- return;
- }
-
- setAdditionalInfoDialogOpen(true);
- } catch (error) {
- console.error("추가정보 조회 오류:", error);
- toast.error("추가정보를 불러오는데 실패했습니다.");
- }
- };
-
// 첨부파일 및 평가 정보 로드
const loadVendorData = async () => {
try {
@@ -523,6 +484,28 @@ export default function BasicInfoClient({
if (typeResult.success && typeResult.data) {
setVendorTypeInfo(typeResult.data);
}
+
+ // PQ 데이터 조회
+ const pqResult = await getVendorPQData(vendorId);
+ if (pqResult) {
+ setPqData(pqResult);
+ }
+
+ // PQ 제출 데이터 조회
+ const pqSubmissionResult = await getVendorPQSubmissionData(vendorId);
+ if (pqSubmissionResult) {
+ setPqSubmissionData(pqSubmissionResult);
+ }
+
+ // 추가정보 조회
+ const additionalInfoResult = await getVendorAdditionalInfo(vendorId);
+ if (additionalInfoResult) {
+ setAdditionalInfo(additionalInfoResult);
+ }
+
+ // 업무담당자 정보 조회
+ const contacts = await getVendorBusinessContacts(vendorId);
+ setBusinessContacts(contacts || []);
} catch (error) {
console.error("벤더 데이터 로드 오류:", error);
}
@@ -553,6 +536,121 @@ export default function BasicInfoClient({
}
};
+ // PQ 데이터에서 실사 정보 추출
+ const extractFactoryInfo = (pqData: any[]) => {
+ const factoryInfo = {
+ factoryAddress: "",
+ factoryPhone: "",
+ factoryFax: "",
+ factoryPIC: "",
+ factoryPICPosition: "",
+ factoryPICContact: "",
+ factoryPICEmail: "",
+ mainSupplyItems: "",
+ inspectionResult: "",
+ inspectionDate: "",
+ inspectionFiles: [] as any[]
+ };
+
+ if (!pqData || !Array.isArray(pqData)) {
+ return factoryInfo;
+ }
+
+ pqData.forEach(group => {
+ if (group && group.items && Array.isArray(group.items)) {
+ group.items.forEach((item: any) => {
+ const code = item.code;
+ const answer = item.answer;
+ const files = item.uploadedFiles || [];
+
+ // 공장주소 (1-4-1)
+ if (code === "1-4-1") {
+ factoryInfo.factoryAddress = answer || "";
+ }
+ // 공장 전화 (1-4-2)
+ else if (code === "1-4-2") {
+ factoryInfo.factoryPhone = answer || "";
+ }
+ // 공장 팩스 (1-4-3)
+ else if (code === "1-4-3") {
+ factoryInfo.factoryFax = answer || "";
+ }
+ // 공장 대표/담당자 이름 (1-4-4)
+ else if (code === "1-4-4") {
+ factoryInfo.factoryPIC = answer || "";
+ }
+ // 공장 대표/담당자 직책 (1-4-5)
+ else if (code === "1-4-5") {
+ factoryInfo.factoryPICPosition = answer || "";
+ }
+ // 공장 대표/담당자 전화 (1-4-6)
+ else if (code === "1-4-6") {
+ factoryInfo.factoryPICContact = answer || "";
+ }
+ // 공장 대표/담당자 이메일 (1-4-7)
+ else if (code === "1-4-7") {
+ factoryInfo.factoryPICEmail = answer || "";
+ }
+ // 공급품목 (첫 번째 것만 가져오기)
+ else if (code.startsWith("1-5") && !factoryInfo.mainSupplyItems) {
+ try {
+ const supplyItems = JSON.parse(answer || "[]");
+ if (Array.isArray(supplyItems) && supplyItems.length > 0) {
+ factoryInfo.mainSupplyItems = supplyItems[0].name || supplyItems[0] || "";
+ }
+ } catch {
+ factoryInfo.mainSupplyItems = answer || "";
+ }
+ }
+ // 실사 결과
+ else if (code.startsWith("4-") && answer && !factoryInfo.inspectionResult) {
+ factoryInfo.inspectionResult = answer;
+ factoryInfo.inspectionFiles = files;
+ }
+ });
+ }
+ });
+
+ return factoryInfo;
+ };
+
+ // PQ 제출 데이터에서 특정 코드의 답변 가져오기
+ const getPQAnswerByCode = (targetCode: string) => {
+ if (!pqSubmissionData || !Array.isArray(pqSubmissionData)) {
+ return "";
+ }
+
+ for (const submission of pqSubmissionData) {
+ if (submission && submission.answers && Array.isArray(submission.answers)) {
+ const answer = submission.answers.find((a: any) => a.criteriaCode === targetCode);
+ if (answer) {
+ return answer.answer;
+ }
+ }
+ }
+ return "";
+ };
+
+ // PQ 제출 데이터에서 특정 코드의 첨부파일 가져오기
+ const getPQAttachmentsByCode = (targetCode: string) => {
+ const files: any[] = [];
+
+ if (!pqSubmissionData || !Array.isArray(pqSubmissionData)) {
+ return files;
+ }
+
+ for (const submission of pqSubmissionData) {
+ if (submission && submission.answers && Array.isArray(submission.answers)) {
+ const answer = submission.answers.find((a: any) => a.criteriaCode === targetCode);
+ if (answer && answer.uploadedFiles) {
+ files.push(...answer.uploadedFiles);
+ }
+ }
+ }
+
+ return files;
+ };
+
// 첨부파일 관리 핸들러 (타입별)
const handleAttachmentFileManagement = (attachmentType: string, typeName: string) => {
const files = attachmentsByType[attachmentType] || [];
@@ -615,13 +713,22 @@ export default function BasicInfoClient({
</Button>
</>
) : (
- <Button
- onClick={() => setEditMode(true)}
- className="flex items-center gap-1"
- >
- <Edit className="w-4 h-4" />
- 수정
- </Button>
+ <>
+ <Button
+ onClick={() => setEditMode(true)}
+ className="flex items-center gap-1"
+ >
+ <Edit className="w-4 h-4" />
+ 수정
+ </Button>
+ <Button
+ variant="outline"
+ onClick={handleContractView}
+ className="flex items-center gap-1"
+ >
+ 정규업체등록 현황
+ </Button>
+ </>
)}
</div>
</div>
@@ -798,8 +905,6 @@ export default function BasicInfoClient({
}
/>
- <Separator />
-
{/* 첨부파일 */}
<WideInfoSection
title="첨부파일"
@@ -819,7 +924,7 @@ export default function BasicInfoClient({
className="mt-2"
onClick={() => handleAttachmentFileManagement("BUSINESS_REGISTRATION", "사업자등록증")}
>
- 파일 보기
+ 파일 다운로드
</Button>
)}
</div>
@@ -839,7 +944,7 @@ export default function BasicInfoClient({
className="mt-2"
onClick={() => handleAttachmentFileManagement("CREDIT_REPORT", "신용평가보고서")}
>
- 파일 보기
+ 파일 다운로드
</Button>
)}
</div>
@@ -859,7 +964,7 @@ export default function BasicInfoClient({
className="mt-2"
onClick={() => handleAttachmentFileManagement("BANK_ACCOUNT_COPY", "통장사본")}
>
- 파일 보기
+ 파일 다운로드
</Button>
)}
</div>
@@ -879,7 +984,7 @@ export default function BasicInfoClient({
className="mt-2"
onClick={() => handleAttachmentFileManagement("ISO_CERTIFICATION", "ISO 인증서")}
>
- 파일 보기
+ 파일 다운로드
</Button>
)}
</div>
@@ -899,7 +1004,7 @@ export default function BasicInfoClient({
className="mt-2"
onClick={() => handleAttachmentFileManagement("GENERAL", "기타 첨부파일")}
>
- 파일 보기
+ 파일 다운로드
</Button>
)}
</div>
@@ -908,10 +1013,9 @@ export default function BasicInfoClient({
}
/>
- <Separator />
{/* 상세정보 */}
- {/* <InfoSection
+ <InfoSection
title="상세정보"
column1={
<div className="space-y-2">
@@ -936,13 +1040,16 @@ export default function BasicInfoClient({
/>
<InfoItem
title="대표자 생년월일"
- value={formData.representativeBirth || null}
+ value={formData.representativeBirth || ""}
isEditable={true}
+ editMode={editMode}
+ fieldKey="representativeBirth"
+ onChange={(value) => updateField("representativeBirth", value)}
/>
<InfoItem
title="임직원수"
- value={formData.employeeCount.toString() || null}
- isEditable={true}
+ value={getPQAnswerByCode("1-8-3") || formData.employeeCount.toString()}
+ type="readonly"
/>
</div>
}
@@ -958,13 +1065,16 @@ export default function BasicInfoClient({
/>
<InfoItem
title="대표자 주소"
- value={formData.address || null}
+ value={formData.address || ""}
isEditable={true}
+ editMode={editMode}
+ fieldKey="address"
+ onChange={(value) => updateField("address", value)}
/>
<InfoItem
title="연간 매출"
- value={initialData.capacityInfo?.annualSales || null}
- isEditable={true}
+ value={getPQAnswerByCode("1-7-1") || getPQAnswerByCode("1-7-2") || initialData.capacityInfo?.annualSales || ""}
+ type="readonly"
/>
</div>
}
@@ -980,8 +1090,8 @@ export default function BasicInfoClient({
/>
<InfoItem
title="생산능력"
- value={initialData.capacityInfo?.productionCapacity || null}
- isEditable={true}
+ value={getPQAnswerByCode("1-9-1") || getPQAnswerByCode("1-9-2") || initialData.capacityInfo?.productionCapacity || ""}
+ type="readonly"
/>
</div>
}
@@ -990,49 +1100,80 @@ export default function BasicInfoClient({
<OrganizationChart
data={initialData.organization}
editMode={editMode}
+ pqData={pqSubmissionData && Array.isArray(pqSubmissionData) ? pqSubmissionData.flatMap(s => s.answers || []) : []}
onChange={(field, value) => {
// TODO: 조직도 업데이트 로직 구현
- toast.info(
- `[개발중] 조직도 ${field} 필드 업데이트 기능을 구현 예정입니다.`
- );
}}
/>
<div className="flex flex-col items-center gap-3">
<div className="text-sm font-semibold text-center">
- 관련 정보
+ 관련 첨부파일
</div>
<div className="space-y-2">
<Button
variant="outline"
className="text-xs w-32 flex items-center gap-2"
- onClick={() => handleFileManagement("협력업체정보")}
+ onClick={() => {
+ const files = getPQAttachmentsByCode("1-10");
+ if (files.length > 0) {
+ files.forEach((file, index) => {
+ setTimeout(() => {
+ handleAttachmentDownload(file.filePath, file.fileName);
+ }, index * 500);
+ });
+ } else {
+ toast.info("협력업체정보 파일이 없습니다.");
+ }
+ }}
>
- 협력업체정보
+ 협력업체정보 ({getPQAttachmentsByCode("1-10").length}건)
</Button>
<Button
variant="outline"
className="text-xs w-32 flex items-center gap-2"
- onClick={() => handleFileManagement("외주화정보")}
+ onClick={() => {
+ const files = getPQAttachmentsByCode("1-12");
+ if (files.length > 0) {
+ files.forEach((file, index) => {
+ setTimeout(() => {
+ handleAttachmentDownload(file.filePath, file.fileName);
+ }, index * 500);
+ });
+ } else {
+ toast.info("외주화정보 파일이 없습니다.");
+ }
+ }}
>
- 외주화정보
+ 외주화정보 ({getPQAttachmentsByCode("1-12").length}건)
</Button>
<Button
variant="outline"
className="text-xs w-32 flex items-center gap-2"
- onClick={() => handleFileManagement("A/S 네트워크")}
+ onClick={() => {
+ const files = getPQAttachmentsByCode("1-13");
+ if (files.length > 0) {
+ files.forEach((file, index) => {
+ setTimeout(() => {
+ handleAttachmentDownload(file.filePath, file.fileName);
+ }, index * 500);
+ });
+ } else {
+ toast.info("A/S 네트워크 파일이 없습니다.");
+ }
+ }}
>
- A/S 네트워크
+ A/S 네트워크 ({getPQAttachmentsByCode("1-13").length}건)
</Button>
</div>
</div>
</div>
}
- /> */}
+ />
{/* <Separator /> */}
{/* 매출정보 */}
- {/* <WideInfoSection
+ <WideInfoSection
title="매출정보"
subtitle="(3개년)"
noPadding={true}
@@ -1176,283 +1317,334 @@ export default function BasicInfoClient({
</TableBody>
</Table>
}
- /> */}
+ />
{/* <Separator /> */}
{/* 실사정보 */}
- {/* <InfoSection
- title="실사정보"
- subtitle="(3년)"
+ {pqSubmissionData && pqSubmissionData.length > 0 && pqSubmissionData.map((submission, index) => {
+ const factoryInfo = extractFactoryInfo([{
+ groupName: "Factory Info",
+ items: submission.answers.map((answer: any) => ({
+ code: answer.criteriaCode,
+ answer: answer.answer,
+ uploadedFiles: answer.uploadedFiles || []
+ }))
+ }]);
+ const inspectionFiles = getPQAttachmentsByCode("4-1");
+
+ return (
+ <InfoSection
+ key={submission.submission.id}
+ title="실사정보"
+ subtitle={`${submission.submission.type || "일반"} - ${submission.submission.status || "상태없음"}`}
+ column1={
+ <div className="space-y-2">
+ <InfoItem
+ title="공장주소"
+ value={factoryInfo.factoryAddress || ""}
+ type="readonly"
+ />
+ <InfoItem
+ title="공장 전화"
+ value={factoryInfo.factoryPhone || ""}
+ type="readonly"
+ />
+ <InfoItem
+ title="공장 팩스"
+ value={factoryInfo.factoryFax || ""}
+ type="readonly"
+ />
+ </div>
+ }
+ column2={
+ <div className="space-y-2">
+ <InfoItem
+ title="공장 담당자"
+ value={
+ factoryInfo.factoryPIC
+ ? `${factoryInfo.factoryPIC} [${factoryInfo.factoryPICPosition || ""}] [${factoryInfo.factoryPICContact || ""}] [${factoryInfo.factoryPICEmail || ""}]`
+ : ""
+ }
+ type="readonly"
+ />
+ <InfoItem
+ title="실사결과"
+ value={factoryInfo.inspectionResult || ""}
+ type="readonly"
+ />
+ {inspectionFiles.length > 0 && (
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => {
+ inspectionFiles.forEach((file, idx) => {
+ setTimeout(() => {
+ handleAttachmentDownload(file.filePath, file.fileName);
+ }, idx * 500);
+ });
+ }}
+ >
+ 실사결과 파일 다운로드 ({inspectionFiles.length}건)
+ </Button>
+ )}
+ </div>
+ }
+ column3={
+ <div className="flex flex-col gap-2">
+ <div className="space-y-2">
+ <InfoItem
+ title="대표공급품목"
+ value={factoryInfo.mainSupplyItems || ""}
+ type="readonly"
+ />
+ </div>
+ </div>
+ }
+ additionalContent={
+ <div className="grid grid-cols-5 gap-4 min-w-0 overflow-x-auto">
+ <div className="text-center min-w-0">
+ <div className="text-sm font-medium mb-2 break-words">
+ 공장소개자료
+ </div>
+ <div className="text-sm text-muted-foreground">
+ {getPQAttachmentsByCode("1-4").length}건
+ </div>
+ {getPQAttachmentsByCode("1-4").length > 0 && (
+ <Button
+ variant="outline"
+ size="sm"
+ className="mt-2"
+ onClick={() => {
+ const files = getPQAttachmentsByCode("1-4");
+ files.forEach((file, idx) => {
+ setTimeout(() => {
+ handleAttachmentDownload(file.filePath, file.fileName);
+ }, idx * 500);
+ });
+ }}
+ >
+ 파일 다운로드
+ </Button>
+ )}
+ </div>
+ <div className="text-center min-w-0">
+ <div className="text-sm font-medium mb-2 break-words">
+ QMS Cert
+ </div>
+ <div className="text-sm text-muted-foreground">
+ {getPQAttachmentsByCode("2-1").length}건
+ </div>
+ {getPQAttachmentsByCode("2-1").length > 0 && (
+ <Button
+ variant="outline"
+ size="sm"
+ className="mt-2"
+ onClick={() => {
+ const files = getPQAttachmentsByCode("2-1");
+ files.forEach((file, idx) => {
+ setTimeout(() => {
+ handleAttachmentDownload(file.filePath, file.fileName);
+ }, idx * 500);
+ });
+ }}
+ >
+ 파일 다운로드
+ </Button>
+ )}
+ </div>
+ <div className="text-center min-w-0">
+ <div className="text-sm font-medium mb-2 break-words">
+ Product Cert
+ </div>
+ <div className="text-sm text-muted-foreground">
+ {getPQAttachmentsByCode("2-2").length}건
+ </div>
+ {getPQAttachmentsByCode("2-2").length > 0 && (
+ <Button
+ variant="outline"
+ size="sm"
+ className="mt-2"
+ onClick={() => {
+ const files = getPQAttachmentsByCode("2-2");
+ files.forEach((file, idx) => {
+ setTimeout(() => {
+ handleAttachmentDownload(file.filePath, file.fileName);
+ }, idx * 500);
+ });
+ }}
+ >
+ 파일 다운로드
+ </Button>
+ )}
+ </div>
+ <div className="text-center min-w-0">
+ <div className="text-sm font-medium mb-2 break-words">
+ Ex. Cert
+ </div>
+ <div className="text-sm text-muted-foreground">
+ {getPQAttachmentsByCode("2-17").length}건
+ </div>
+ {getPQAttachmentsByCode("2-17").length > 0 && (
+ <Button
+ variant="outline"
+ size="sm"
+ className="mt-2"
+ onClick={() => {
+ const files = getPQAttachmentsByCode("2-17");
+ files.forEach((file, idx) => {
+ setTimeout(() => {
+ handleAttachmentDownload(file.filePath, file.fileName);
+ }, idx * 500);
+ });
+ }}
+ >
+ 파일 다운로드
+ </Button>
+ )}
+ </div>
+ <div className="text-center min-w-0">
+ <div className="text-sm font-medium mb-2 break-words">
+ HSE Cert
+ </div>
+ <div className="text-sm text-muted-foreground">
+ {getPQAttachmentsByCode("3-1").length}건
+ </div>
+ {getPQAttachmentsByCode("3-1").length > 0 && (
+ <Button
+ variant="outline"
+ size="sm"
+ className="mt-2"
+ onClick={() => {
+ const files = getPQAttachmentsByCode("3-1");
+ files.forEach((file, idx) => {
+ setTimeout(() => {
+ handleAttachmentDownload(file.filePath, file.fileName);
+ }, idx * 500);
+ });
+ }}
+ >
+ 파일 다운로드
+ </Button>
+ )}
+ </div>
+ </div>
+ }
+ />
+ );
+ })}
+ {/* 추가정보 */}
+ <InfoSection
+ title="추가정보"
+ // subtitle="정규업체 등록 시 입력된 정보"
column1={
<div className="space-y-2">
<InfoItem
- title="공장주소"
- value={initialData.factoryInfo?.factoryAddress || null}
+ title="사업유형"
+ value={additionalInfo?.businessType || ""}
+ type="readonly"
/>
<InfoItem
- title="공장설립일"
- value={
- initialData.factoryInfo?.factoryEstablishmentDate || null
- }
+ title="산업유형"
+ value={additionalInfo?.industryType || ""}
+ type="readonly"
+ />
+ <InfoItem
+ title="회사규모"
+ value={additionalInfo?.companySize || ""}
+ type="readonly"
/>
</div>
}
column2={
<div className="space-y-2">
<InfoItem
- title="공장 담당자"
- value={
- initialData.factoryInfo?.factoryPIC
- ? `${initialData.factoryInfo.factoryPIC} [${
- initialData.factoryInfo.factoryPICContact || ""
- }] [${initialData.factoryInfo.factoryPICEmail || ""}]`
- : null
- }
+ title="매출액"
+ value={additionalInfo?.revenue ? `${additionalInfo.revenue.toLocaleString()}원` : ""}
+ type="readonly"
/>
<InfoItem
- title="실사결과"
- value={
- initialData.inspectionInfo?.inspectionResult
- ? `${initialData.inspectionInfo.inspectionResult} (${
- initialData.inspectionInfo.inspectionDate || ""
- })`
- : null
- }
+ title="공장설립일"
+ value={additionalInfo?.factoryEstablishedDate ? new Date(additionalInfo.factoryEstablishedDate).toLocaleDateString('ko-KR') : ""}
+ type="readonly"
+ />
+ <InfoItem
+ title="선호계약조건"
+ value={additionalInfo?.preferredContractTerms || ""}
+ type="readonly"
/>
</div>
}
column3={
- <div className="flex flex-col gap-2">
- <div className="space-y-2">
- <InfoItem
- title="대표공급품목"
- value={initialData.capacityInfo?.mainSupplyItems || null}
- />
- </div>
- <Button
- variant="outline"
- onClick={() => handleFileManagement("대표공급품목")}
- >
- 대표 공급품목 상세보기
- </Button>
- </div>
- }
- additionalContent={
- <div className="grid grid-cols-5 gap-4 min-w-0 overflow-x-auto">
- <div className="text-center min-w-0">
- <div className="text-sm font-medium mb-2 break-words">
- 공정소개자료
- </div>
- <div className="text-sm text-muted-foreground">
- {attachmentsByType.BUSINESS_REGISTRATION?.length || 0}건
- </div>
- </div>
- <div className="text-center min-w-0">
- <div className="text-sm font-medium mb-2 break-words">
- QMS Cert
- </div>
- <div className="text-sm text-muted-foreground">
- {attachmentsByType.ISO_CERTIFICATION?.length || 0}건
- </div>
- </div>
- <div className="text-center min-w-0">
- <div className="text-sm font-medium mb-2 break-words">
- Product Cert
- </div>
- <div className="text-sm text-muted-foreground">
- {attachmentsByType.PRODUCT_CERT?.length || 0}건
- </div>
- </div>
- <div className="text-center min-w-0">
- <div className="text-sm font-medium mb-2 break-words">
- Ex. Cert
- </div>
- <div className="text-sm text-muted-foreground">
- {attachmentsByType.EX_CERT?.length || 0}건
- </div>
- </div>
- <div className="text-center min-w-0">
- <div className="text-sm font-medium mb-2 break-words">
- HSE Cert
- </div>
- <div className="text-sm text-muted-foreground">
- {attachmentsByType.HSE_CERT?.length || 0}건
- </div>
- </div>
+ <div className="space-y-2">
+ {/* 추가 정보가 더 있다면 여기에 배치 */}
</div>
}
- /> */}
-
- {/* <Separator /> */}
-
- {/* 계약정보 */}
- {/* <InfoSection
- title="계약정보"
+ />
+ {/* 업무담당자 */}
+ <InfoSection
+ title="업무담당자"
column1={
- <div className="space-y-2">
- <InfoItem
- title="정규등록현황"
- value={
- initialData.contractDetails?.regularRegistrationStatus || null
- }
- />
+ <div className="space-y-3">
+ <div className="space-y-1">
+ <div className="text-sm font-medium">영업 담당자</div>
+ <InfoItem title="이름" value={businessContacts.find(c => c.contactType === "sales")?.contactName || ""} type="readonly" />
+ <InfoItem title="직급" value={businessContacts.find(c => c.contactType === "sales")?.position || ""} type="readonly" />
+ <InfoItem title="부서" value={businessContacts.find(c => c.contactType === "sales")?.department || ""} type="readonly" />
+ <InfoItem title="담당업무" value={businessContacts.find(c => c.contactType === "sales")?.responsibility || ""} type="readonly" />
+ <InfoItem title="E-mail" value={businessContacts.find(c => c.contactType === "sales")?.email || ""} type="readonly" />
+ </div>
</div>
}
column2={
- <div className="space-y-2">
- <InfoItem
- title="선호 계약조건"
- value={
- initialData.contractDetails?.preferredContractTerms || null
- }
- />
+ <div className="space-y-3">
+ <div className="space-y-1">
+ <div className="text-sm font-medium">설계 담당자</div>
+ <InfoItem title="이름" value={businessContacts.find(c => c.contactType === "design")?.contactName || ""} type="readonly" />
+ <InfoItem title="직급" value={businessContacts.find(c => c.contactType === "design")?.position || ""} type="readonly" />
+ <InfoItem title="부서" value={businessContacts.find(c => c.contactType === "design")?.department || ""} type="readonly" />
+ <InfoItem title="담당업무" value={businessContacts.find(c => c.contactType === "design")?.responsibility || ""} type="readonly" />
+ <InfoItem title="E-mail" value={businessContacts.find(c => c.contactType === "design")?.email || ""} type="readonly" />
+ </div>
+ <div className="space-y-1">
+ <div className="text-sm font-medium">납기 담당자</div>
+ <InfoItem title="이름" value={businessContacts.find(c => c.contactType === "delivery")?.contactName || ""} type="readonly" />
+ <InfoItem title="직급" value={businessContacts.find(c => c.contactType === "delivery")?.position || ""} type="readonly" />
+ <InfoItem title="부서" value={businessContacts.find(c => c.contactType === "delivery")?.department || ""} type="readonly" />
+ <InfoItem title="담당업무" value={businessContacts.find(c => c.contactType === "delivery")?.responsibility || ""} type="readonly" />
+ <InfoItem title="E-mail" value={businessContacts.find(c => c.contactType === "delivery")?.email || ""} type="readonly" />
+ </div>
</div>
}
column3={
- <div className="space-y-2">
- <InfoItem
- title="최근 거래현황"
- value={
- initialData.contractDetails?.recentTransactionStatus || null
- }
- />
- </div>
- }
- additionalContent={
- <div className="grid grid-cols-10 gap-4 min-w-0 overflow-x-auto">
- {[
- {
- title: "준법서약",
- value:
- initialData.contractDetails?.compliancePledgeDate || null,
- },
- {
- title: "기술자료",
- value: initialData.contractDetails?.technicalDataDate || null,
- },
- {
- title: "비밀유지",
- value:
- initialData.contractDetails?.confidentialityDate || null,
- },
- {
- title: "GTC",
- value: initialData.contractDetails?.gtcDate || null,
- },
- {
- title: "표준하도급",
- value:
- initialData.contractDetails?.standardSubcontractDate ||
- null,
- },
- {
- title: "안전보건",
- value: initialData.contractDetails?.safetyHealthDate || null,
- },
- {
- title: "직납자재",
- value:
- initialData.contractDetails?.directMaterialDate || null,
- },
- {
- title: "내국신용장",
- value: initialData.contractDetails?.domesticLCDate || null,
- },
- {
- title: "동반성장",
- value: initialData.contractDetails?.mutualGrowthDate || null,
- },
- {
- title: "윤리규범",
- value: initialData.contractDetails?.ethicsDate || null,
- },
- ].map((item, index) => (
- <div key={index} className="text-center min-w-0">
- <div className="text-sm font-medium mb-2 break-words">
- {item.title}
- </div>
- <div className="text-sm text-muted-foreground">
- {item.value || "-"}
- </div>
- </div>
- ))}
+ <div className="space-y-3">
+ <div className="space-y-1">
+ <div className="text-sm font-medium">품질 담당자</div>
+ <InfoItem title="이름" value={businessContacts.find(c => c.contactType === "quality")?.contactName || ""} type="readonly" />
+ <InfoItem title="직급" value={businessContacts.find(c => c.contactType === "quality")?.position || ""} type="readonly" />
+ <InfoItem title="부서" value={businessContacts.find(c => c.contactType === "quality")?.department || ""} type="readonly" />
+ <InfoItem title="담당업무" value={businessContacts.find(c => c.contactType === "quality")?.responsibility || ""} type="readonly" />
+ <InfoItem title="E-mail" value={businessContacts.find(c => c.contactType === "quality")?.email || ""} type="readonly" />
+ </div>
+ <div className="space-y-1">
+ <div className="text-sm font-medium">세금계산서 담당자</div>
+ <InfoItem title="이름" value={businessContacts.find(c => c.contactType === "tax_invoice")?.contactName || ""} type="readonly" />
+ <InfoItem title="직급" value={businessContacts.find(c => c.contactType === "tax_invoice")?.position || ""} type="readonly" />
+ <InfoItem title="부서" value={businessContacts.find(c => c.contactType === "tax_invoice")?.department || ""} type="readonly" />
+ <InfoItem title="담당업무" value={businessContacts.find(c => c.contactType === "tax_invoice")?.responsibility || ""} type="readonly" />
+ <InfoItem title="E-mail" value={businessContacts.find(c => c.contactType === "tax_invoice")?.email || ""} type="readonly" />
+ </div>
</div>
}
- /> */}
-
-
-
- {/* 추가 조회 기능 버튼들 */}
- <div className="border rounded-lg p-6">
- <div className="text-lg font-semibold mb-4">상세 정보 조회</div>
- <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
- <Button
- variant="outline"
- onClick={handlePQView}
- className="h-20 flex flex-col items-center justify-center space-y-2"
- >
- <div className="text-sm font-medium">PQ 조회</div>
- <div className="text-xs text-muted-foreground">제출된 PQ 정보 확인</div>
- </Button>
-
- <Button
- variant="outline"
- onClick={handleSiteVisitView}
- className="h-20 flex flex-col items-center justify-center space-y-2"
- >
- <div className="text-sm font-medium">실사 정보</div>
- <div className="text-xs text-muted-foreground">협력업체 방문실사 조회</div>
- </Button>
-
- <Button
- variant="outline"
- onClick={handleContractView}
- className="h-20 flex flex-col items-center justify-center space-y-2"
- >
- <div className="text-sm font-medium">정규업체 등록 현황</div>
- <div className="text-xs text-muted-foreground">정규업체 등록 현황 보기</div>
- </Button>
-
- <Button
- variant="outline"
- onClick={handleAdditionalInfoView}
- className="h-20 flex flex-col items-center justify-center space-y-2"
- >
- <div className="text-sm font-medium">추가정보</div>
- <div className="text-xs text-muted-foreground">업체 추가정보 조회</div>
- </Button>
- </div>
- </div>
+ />
+
</div>
-
- {/* 다이얼로그들 */}
- <PQSimpleDialog
- open={pqDialogOpen}
- onOpenChange={setPqDialogOpen}
- vendorId={vendorId}
- />
-
- <SiteVisitDetailDialog
- isOpen={siteVisitDialogOpen}
- onOpenChange={setSiteVisitDialogOpen}
- selectedRequest={selectedSiteVisitRequest}
- />
-
- {registrationData && (
<DocumentStatusDialog
open={contractDialogOpen}
onOpenChange={setContractDialogOpen}
registration={registrationData}
+ isVendorUser={false}
/>
- )}
-
- <AdditionalInfoDialog
- open={additionalInfoDialogOpen}
- onOpenChange={setAdditionalInfoDialogOpen}
- vendorId={parseInt(vendorId)}
- readonly={true}
- />
</div>
);
}
diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/constants.ts b/lib/vendor-basic-info/constants.ts
index d16f791f..d16f791f 100644
--- a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/constants.ts
+++ b/lib/vendor-basic-info/constants.ts
diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/types.ts b/lib/vendor-basic-info/types.ts
index ead3a44c..ead3a44c 100644
--- a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/types.ts
+++ b/lib/vendor-basic-info/types.ts
diff --git a/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-columns.tsx b/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-columns.tsx
index 7446716b..8d21df24 100644
--- a/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-columns.tsx
+++ b/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-columns.tsx
@@ -214,6 +214,7 @@ export function getColumns(): ColumnDef<VendorRegularRegistration>[] {
open={documentDialogOpen}
onOpenChange={setDocumentDialogOpen}
registration={registration}
+ isVendorUser={false}
/>
</>
)
diff --git a/lib/vendors/service.ts b/lib/vendors/service.ts
index e3a38891..5d790a6e 100644
--- a/lib/vendors/service.ts
+++ b/lib/vendors/service.ts
@@ -2779,10 +2779,8 @@ export async function requestPQVendors(input: ApproveVendorsInput & {
? `[eVCP] You are invited to submit Non-Inspection PQ ${vendorPQ?.pqNumber || ""}`
: `[eVCP] You are invited to submit PQ ${vendorPQ?.pqNumber || ""}`;
- const baseLoginUrl = `${host}/partners/pq`;
- const loginUrl = input.projectId
- ? `${baseLoginUrl}?projectId=${input.projectId}`
- : baseLoginUrl;
+ const baseUrl = process.env.NEXT_PUBLIC_URL || `http://${host}`;
+ const loginUrl = `${baseUrl}/partners/pq_new`;
// 체크된 계약 항목 배열 생성
const contracts = input.agreements
@@ -2807,6 +2805,8 @@ export async function requestPQVendors(input: ApproveVendorsInput & {
}
}
+ console.log("loginUrl-pq", loginUrl);
+
await sendEmail({
to: vendor.email,
subject,
@@ -2959,8 +2959,9 @@ export async function requestBasicContractInfo({
const headersList = await headers();
const host = headersList.get('host') || 'localhost:3000';
// 로그인 또는 서명 페이지 URL 생성
- const baseUrl = `http://${host}`
+ const baseUrl = process.env.NEXT_PUBLIC_URL || `http://${host}`;
const loginUrl = `${baseUrl}/partners/basic-contract`;
+ console.log("loginUrl-basic-contract", loginUrl);
// 사용자 언어 설정 (기본값은 한국어)
const userLang = "ko";