summaryrefslogtreecommitdiff
path: root/lib/rfq-last/vendor-response
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/rfq-last/vendor-response
parent265859d691a01cdcaaf9154f93c38765bc34df06 (diff)
(대표님) 작업사항 - rfqLast, tbeLast, pdfTron, userAuth
Diffstat (limited to 'lib/rfq-last/vendor-response')
-rw-r--r--lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx335
-rw-r--r--lib/rfq-last/vendor-response/service.ts175
-rw-r--r--lib/rfq-last/vendor-response/validations.ts4
-rw-r--r--lib/rfq-last/vendor-response/vendor-quotations-table-columns.tsx2
-rw-r--r--lib/rfq-last/vendor-response/vendor-quotations-table.tsx2
5 files changed, 227 insertions, 291 deletions
diff --git a/lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx b/lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx
index c146e42b..34259d37 100644
--- a/lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx
+++ b/lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx
@@ -1,6 +1,6 @@
"use client"
-import { useState } from "react"
+import { useState,useEffect } from "react"
import { useForm, FormProvider } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
@@ -163,18 +163,74 @@ export default function VendorResponseEditor({
const methods = useForm<VendorResponseFormData>({
resolver: zodResolver(vendorResponseSchema),
- defaultValues
+ defaultValues,
+ mode: 'onChange' // 추가: 실시간 validation
})
+ const { formState: { errors, isValid } } = methods
+
+ useEffect(() => {
+ if (Object.keys(errors).length > 0) {
+ console.log('Validation errors:', errors)
+ }
+ }, [errors])
+
+
+
+ const handleFormSubmit = (isSubmit: boolean = false) => {
+ // 임시저장일 경우 validation 없이 바로 저장
+ if (!isSubmit) {
+ const formData = methods.getValues()
+ onSubmit(formData, false)
+ return
+ }
+
+ // 제출일 경우에만 validation 수행
+ methods.handleSubmit(
+ (data) => onSubmit(data, isSubmit),
+ (errors) => {
+ console.error('Form validation errors:', errors)
+
+ // 첫 번째 에러 필드로 포커스 이동
+ const firstErrorField = Object.keys(errors)[0]
+ if (firstErrorField) {
+ // 어느 탭에 에러가 있는지 확인
+ if (firstErrorField.startsWith('vendor') &&
+ !firstErrorField.startsWith('vendorFirst') &&
+ !firstErrorField.startsWith('vendorSparepart')) {
+ setActiveTab('terms')
+ } else if (firstErrorField === 'quotationItems') {
+ setActiveTab('items')
+ }
+
+ // 구체적인 에러 메시지 표시
+ if (errors.quotationItems) {
+ toast.error("견적 품목 정보를 확인해주세요. 모든 품목의 단가와 총액을 입력해야 합니다.")
+ } else {
+ toast.error("입력 정보를 확인해주세요.")
+ }
+ }
+ }
+ )()
+ }
+
const onSubmit = async (data: VendorResponseFormData, isSubmit: boolean = false) => {
+ console.log('onSubmit called with:', { data, isSubmit }) // 디버깅용
+
setLoading(true)
setUploadProgress(0)
try {
const formData = new FormData()
+ const fileMetadata = attachments.map((file: any) => ({
+ attachmentType: file.attachmentType || "기타",
+ description: file.description || ""
+ }))
+
+
// 기본 데이터 추가
- formData.append('data', JSON.stringify({
+ const submitData = {
...data,
rfqsLastId: rfq.id,
rfqLastDetailsId: rfqDetail.id,
@@ -183,69 +239,76 @@ export default function VendorResponseEditor({
submittedAt: isSubmit ? new Date().toISOString() : null,
submittedBy: isSubmit ? userId : null,
totalAmount: data.quotationItems.reduce((sum, item) => sum + item.totalPrice, 0),
- updatedBy: userId
- }))
+ updatedBy: userId,
+ fileMetadata
+ }
+
+ console.log('Submitting data:', submitData) // 디버깅용
+
+ formData.append('data', JSON.stringify(submitData))
// 첨부파일 추가
attachments.forEach((file, index) => {
formData.append(`attachments`, file)
})
- // const response = await fetch(`/api/partners/rfq-last/${rfq.id}/response`, {
- // method: existingResponse ? 'PUT' : 'POST',
- // body: formData
- // })
-
- // if (!response.ok) {
- // throw new Error('응답 저장에 실패했습니다.')
- // }
-
- // XMLHttpRequest 사용하여 업로드 진행률 추적
- const xhr = new XMLHttpRequest()
-
- // Promise로 감싸서 async/await 사용 가능하게
- const uploadPromise = new Promise((resolve, reject) => {
- // 업로드 진행률 이벤트
- xhr.upload.addEventListener('progress', (event) => {
- if (event.lengthComputable) {
- const percentComplete = Math.round((event.loaded / event.total) * 100)
- setUploadProgress(percentComplete)
- }
- })
-
- // 완료 이벤트
- xhr.addEventListener('load', () => {
- if (xhr.status >= 200 && xhr.status < 300) {
- setUploadProgress(100)
- resolve(JSON.parse(xhr.responseText))
- } else {
- reject(new Error('응답 저장에 실패했습니다.'))
+ // XMLHttpRequest 사용하여 업로드 진행률 추적
+ const xhr = new XMLHttpRequest()
+
+ const uploadPromise = new Promise((resolve, reject) => {
+ xhr.upload.addEventListener('progress', (event) => {
+ if (event.lengthComputable) {
+ const percentComplete = Math.round((event.loaded / event.total) * 100)
+ setUploadProgress(percentComplete)
+ }
+ })
+
+ xhr.addEventListener('load', () => {
+ if (xhr.status >= 200 && xhr.status < 300) {
+ setUploadProgress(100)
+ try {
+ const response = JSON.parse(xhr.responseText)
+ resolve(response)
+ } catch (e) {
+ console.error('Response parsing error:', e)
+ reject(new Error('응답 파싱 실패'))
}
- })
-
- // 에러 이벤트
- xhr.addEventListener('error', () => {
- reject(new Error('네트워크 오류가 발생했습니다.'))
- })
-
- // 요청 전송
- xhr.open(existingResponse ? 'PUT' : 'POST', `/api/partners/rfq-last/${rfq.id}/response`)
- xhr.send(formData)
+ } else {
+ console.error('Server error:', xhr.status, xhr.responseText)
+ reject(new Error(`서버 오류: ${xhr.status}`))
+ }
+ })
+
+ xhr.addEventListener('error', () => {
+ console.error('Network error')
+ reject(new Error('네트워크 오류가 발생했습니다.'))
})
+
+ // 요청 전송
+ const method = existingResponse ? 'PUT' : 'POST'
+ const url = `/api/partners/rfq-last/${rfq.id}/response`
+
+ console.log(`Sending ${method} request to ${url}`) // 디버깅용
- await uploadPromise
+ xhr.open(method, url)
+ xhr.send(formData)
+ })
+
+ await uploadPromise
toast.success(isSubmit ? "견적서가 제출되었습니다." : "견적서가 저장되었습니다.")
router.push('/partners/rfq-last')
router.refresh()
} catch (error) {
- console.error('Error:', error)
- toast.error("오류가 발생했습니다.")
+ console.error('Submit error:', error) // 더 상세한 에러 로깅
+ toast.error(error instanceof Error ? error.message : "오류가 발생했습니다.")
} finally {
setLoading(false)
+ setUploadProgress(0)
}
}
+
const totalAmount = methods.watch('quotationItems')?.reduce(
(sum, item) => sum + (item.totalPrice || 0), 0
) || 0
@@ -256,7 +319,10 @@ export default function VendorResponseEditor({
return (
<FormProvider {...methods}>
- <form onSubmit={methods.handleSubmit((data) => onSubmit(data, false))}>
+ <form onSubmit={(e) => {
+ e.preventDefault() // 기본 submit 동작 방지
+ handleFormSubmit(false)
+ }}>
<div className="space-y-6">
{/* 헤더 정보 */}
<RfqInfoHeader rfq={rfq} rfqDetail={rfqDetail} vendor={vendor} />
@@ -293,92 +359,92 @@ export default function VendorResponseEditor({
</CardDescription>
</CardHeader>
<CardContent>
- {basicContracts.length > 0 ? (
- <div className="space-y-4">
- {/* 계약 목록 - 그리드 레이아웃 */}
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
- {basicContracts.map((contract) => (
- <div
- key={contract.id}
- className="p-3 border rounded-lg bg-card hover:bg-muted/50 transition-colors"
- >
- <div className="flex items-start gap-2">
- <div className="p-1.5 bg-primary/10 rounded">
- <Shield className="h-3.5 w-3.5 text-primary" />
- </div>
- <div className="flex-1 min-w-0">
- <h4 className="font-medium text-sm truncate" title={contract.templateName}>
- {contract.templateName}
- </h4>
- <Badge
- variant={contract.signedAt ? "success" : "warning"}
- className="text-xs mt-1.5"
- >
- {contract.signedAt ? (
- <>
- <CheckCircle className="h-3 w-3 mr-1" />
- 서명완료
- </>
+ {basicContracts.length > 0 ? (
+ <div className="space-y-4">
+ {/* 계약 목록 - 그리드 레이아웃 */}
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
+ {basicContracts.map((contract) => (
+ <div
+ key={contract.id}
+ className="p-3 border rounded-lg bg-card hover:bg-muted/50 transition-colors"
+ >
+ <div className="flex items-start gap-2">
+ <div className="p-1.5 bg-primary/10 rounded">
+ <Shield className="h-3.5 w-3.5 text-primary" />
+ </div>
+ <div className="flex-1 min-w-0">
+ <h4 className="font-medium text-sm truncate" title={contract.templateName}>
+ {contract.templateName}
+ </h4>
+ <Badge
+ variant={contract.signedAt ? "success" : "warning"}
+ className="text-xs mt-1.5"
+ >
+ {contract.signedAt ? (
+ <>
+ <CheckCircle className="h-3 w-3 mr-1" />
+ 서명완료
+ </>
+ ) : (
+ <>
+ <Clock className="h-3 w-3 mr-1" />
+ 서명대기
+ </>
+ )}
+ </Badge>
+ <p className="text-xs text-muted-foreground mt-1">
+ {contract.signedAt
+ ? `${formatDate(new Date(contract.signedAt))}`
+ : contract.deadline
+ ? `~${formatDate(new Date(contract.deadline))}`
+ : '마감일 없음'}
+ </p>
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
+
+ {/* 서명 상태 요약 및 액션 */}
+ {basicContracts.some(contract => !contract.signedAt) ? (
+ <div className="flex items-center justify-between p-3 bg-amber-50 dark:bg-amber-950/20 border border-amber-200 dark:border-amber-900 rounded-lg">
+ <div className="flex items-center gap-2">
+ <AlertCircle className="h-4 w-4 text-amber-600" />
+ <div>
+ <p className="text-sm font-medium">
+ 서명 대기: {basicContracts.filter(c => !c.signedAt).length}/{basicContracts.length}개
+ </p>
+ <p className="text-xs text-muted-foreground">
+ 견적서 제출 전 모든 계약서 서명 필요
+ </p>
+ </div>
+ </div>
+ <Button
+ type="button"
+ size="sm"
+ onClick={() => router.push(`/partners/basic-contract`)}
+ >
+ 서명하기
+ </Button>
+ </div>
+ ) : (
+ <Alert className="border-green-200 bg-green-50 dark:bg-green-950/20">
+ <CheckCircle className="h-4 w-4 text-green-600" />
+ <AlertDescription className="text-sm">
+ 모든 기본계약 서명 완료
+ </AlertDescription>
+ </Alert>
+ )}
+ </div>
) : (
- <>
- <Clock className="h-3 w-3 mr-1" />
- 서명대기
- </>
+ <div className="text-center py-8">
+ <FileText className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
+ <p className="text-muted-foreground">
+ 이 RFQ에 요청된 기본계약이 없습니다
+ </p>
+ </div>
)}
- </Badge>
- <p className="text-xs text-muted-foreground mt-1">
- {contract.signedAt
- ? `${formatDate(new Date(contract.signedAt))}`
- : contract.deadline
- ? `~${formatDate(new Date(contract.deadline))}`
- : '마감일 없음'}
- </p>
- </div>
- </div>
- </div>
- ))}
- </div>
-
- {/* 서명 상태 요약 및 액션 */}
- {basicContracts.some(contract => !contract.signedAt) ? (
- <div className="flex items-center justify-between p-3 bg-amber-50 dark:bg-amber-950/20 border border-amber-200 dark:border-amber-900 rounded-lg">
- <div className="flex items-center gap-2">
- <AlertCircle className="h-4 w-4 text-amber-600" />
- <div>
- <p className="text-sm font-medium">
- 서명 대기: {basicContracts.filter(c => !c.signedAt).length}/{basicContracts.length}개
- </p>
- <p className="text-xs text-muted-foreground">
- 견적서 제출 전 모든 계약서 서명 필요
- </p>
- </div>
- </div>
- <Button
- type="button"
- size="sm"
- onClick={() => router.push(`/partners/basic-contract`)}
- >
- 서명하기
- </Button>
- </div>
- ) : (
- <Alert className="border-green-200 bg-green-50 dark:bg-green-950/20">
- <CheckCircle className="h-4 w-4 text-green-600" />
- <AlertDescription className="text-sm">
- 모든 기본계약 서명 완료
- </AlertDescription>
- </Alert>
- )}
- </div>
- ) : (
- <div className="text-center py-8">
- <FileText className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
- <p className="text-muted-foreground">
- 이 RFQ에 요청된 기본계약이 없습니다
- </p>
- </div>
- )}
-</CardContent>
+ </CardContent>
</Card>
</TabsContent>
@@ -429,8 +495,9 @@ export default function VendorResponseEditor({
취소
</Button>
<Button
- type="submit"
+ type="button" // submit에서 button으로 변경
variant="secondary"
+ onClick={() => handleFormSubmit(false)} // 직접 핸들러 호출
disabled={loading}
>
{loading ? (
@@ -448,7 +515,7 @@ export default function VendorResponseEditor({
<Button
type="button"
variant="default"
- onClick={methods.handleSubmit((data) => onSubmit(data, true))}
+ onClick={() => handleFormSubmit(true)} // 직접 핸들러 호출
disabled={loading || !allContractsSigned}
>
{!allContractsSigned ? (
diff --git a/lib/rfq-last/vendor-response/service.ts b/lib/rfq-last/vendor-response/service.ts
index 7de3ae58..04cc5234 100644
--- a/lib/rfq-last/vendor-response/service.ts
+++ b/lib/rfq-last/vendor-response/service.ts
@@ -7,7 +7,7 @@ import { and, or, eq, desc, asc, count, ilike, inArray } from "drizzle-orm";
import {
rfqsLastView,
rfqLastDetails,
- rfqLastVendorResponses,
+ rfqLastVendorResponses,vendorQuotationView,
type RfqsLastView
} from "@/db/schema";
import { filterColumns } from "@/lib/filter-columns";
@@ -26,25 +26,6 @@ export type VendorQuotationStatus =
| "최종확정" // 최종 확정됨
| "취소" // 취소됨
-// 벤더 견적 뷰 타입 확장
-export interface VendorQuotationView extends RfqsLastView {
- // 벤더 응답 정보
- responseStatus?: VendorQuotationStatus;
- displayStatus?:string;
- responseVersion?: number;
- submittedAt?: Date;
- totalAmount?: number;
- vendorCurrency?: string;
-
- // 벤더별 조건
- vendorPaymentTerms?: string;
- vendorIncoterms?: string;
- vendorDeliveryDate?: Date;
-
- participationStatus: "미응답" | "참여" | "불참" | null
- participationRepliedAt: Date | null
- nonParticipationReason: string | null
-}
/**
* 벤더별 RFQ 목록 조회
@@ -66,28 +47,9 @@ export async function getVendorQuotationsLast(
const perPage = input.perPage || 10;
const offset = (page - 1) * perPage;
- // 1. 먼저 벤더가 포함된 RFQ ID들 조회
- const vendorRfqIds = await db
- .select({ rfqsLastId: rfqLastDetails.rfqsLastId })
- .from(rfqLastDetails)
- .where(
- and(
- eq(rfqLastDetails.vendorsId, numericVendorId),
- eq(rfqLastDetails.isLatest, true)
- )
- );
-
-
- const rfqIds = vendorRfqIds.map(r => r.rfqsLastId).filter(id => id !== null);
-
- if (rfqIds.length === 0) {
- return { data: [], pageCount: 0 };
- }
-
- // 2. 필터링 설정
- // advancedTable 모드로 where 절 구성
+ // 필터링 설정
const advancedWhere = filterColumns({
- table: rfqsLastView,
+ table: vendorQuotationView,
filters: input.filters,
joinOperator: input.joinOperator,
});
@@ -97,148 +59,55 @@ export async function getVendorQuotationsLast(
if (input.search) {
const s = `%${input.search}%`;
globalWhere = or(
- ilike(rfqsLastView.rfqCode, s),
- ilike(rfqsLastView.rfqTitle, s),
- ilike(rfqsLastView.itemName, s),
- ilike(rfqsLastView.projectName, s),
- ilike(rfqsLastView.packageName, s),
- ilike(rfqsLastView.status, s)
+ ilike(vendorQuotationView.rfqCode, s),
+ ilike(vendorQuotationView.rfqTitle, s),
+ ilike(vendorQuotationView.itemName, s),
+ ilike(vendorQuotationView.projectName, s),
+ ilike(vendorQuotationView.packageName, s),
+ ilike(vendorQuotationView.status, s),
+ ilike(vendorQuotationView.displayStatus, s)
);
}
- // RFQ ID 조건 (벤더가 포함된 RFQ만)
- const rfqIdWhere = inArray(rfqsLastView.id, rfqIds);
+ // 벤더 ID 조건 (필수)
+ const vendorIdWhere = eq(vendorQuotationView.vendorId, numericVendorId);
// 모든 조건 결합
- let whereConditions = [rfqIdWhere]; // 필수 조건
+ let whereConditions = [vendorIdWhere];
if (advancedWhere) whereConditions.push(advancedWhere);
if (globalWhere) whereConditions.push(globalWhere);
- // 최종 조건
const finalWhere = and(...whereConditions);
- // 3. 정렬 설정
+ // 정렬 설정
const orderBy = input.sort && input.sort.length > 0
? input.sort.map((item) => {
- // @ts-ignore - 동적 속성 접근
- return item.desc ? desc(rfqsLastView[item.id]) : asc(rfqsLastView[item.id]);
+ // @ts-ignore
+ return item.desc ? desc(vendorQuotationView[item.id]) : asc(vendorQuotationView[item.id]);
})
- : [desc(rfqsLastView.updatedAt)];
+ : [desc(vendorQuotationView.updatedAt)];
- // 4. 메인 쿼리 실행
+ // 메인 쿼리 실행 - 이제 한 번의 쿼리로 모든 데이터를 가져옴
const quotations = await db
.select()
- .from(rfqsLastView)
+ .from(vendorQuotationView)
.where(finalWhere)
.orderBy(...orderBy)
.limit(perPage)
.offset(offset);
- // 5. 각 RFQ에 대한 벤더 응답 정보 조회
- const quotationsWithResponse = await Promise.all(
- quotations.map(async (rfq) => {
- // 벤더 응답 정보 조회
- const response = await db.query.rfqLastVendorResponses.findFirst({
- where: and(
- eq(rfqLastVendorResponses.rfqsLastId, rfq.id),
- eq(rfqLastVendorResponses.vendorId, numericVendorId),
- eq(rfqLastVendorResponses.isLatest, true)
- ),
- columns: {
- status: true,
- responseVersion: true,
- submittedAt: true,
- totalAmount: true,
- vendorCurrency: true,
- vendorPaymentTermsCode: true,
- vendorIncotermsCode: true,
- vendorDeliveryDate: true,
- participationStatus: true,
- participationRepliedAt: true,
- nonParticipationReason: true,
- }
- });
-
- // 벤더 상세 정보 조회
- const detail = await db.query.rfqLastDetails.findFirst({
- where: and(
- eq(rfqLastDetails.rfqsLastId, rfq.id),
- eq(rfqLastDetails.vendorsId, numericVendorId),
- eq(rfqLastDetails.isLatest, true)
- ),
- columns: {
- id: true, // rfqLastDetailsId 필요
- emailSentAt: true,
- emailStatus: true,
- shortList: true,
- }
- });
-
- // 표시할 상태 결정 (새로운 로직)
- let displayStatus: string | null = null;
-
- if (response) {
- // 응답 레코드가 있는 경우
- if (response.participationStatus === "불참") {
- displayStatus = "불참";
- } else if (response.participationStatus === "참여") {
- // 참여한 경우 실제 작업 상태 표시
- displayStatus = response.status || "작성중";
- } else {
- // participationStatus가 없거나 "미응답"인 경우
- displayStatus = "미응답";
- }
- } else {
- // 응답 레코드가 없는 경우
- if (detail?.emailSentAt) {
- displayStatus = "미응답"; // 초대는 받았지만 응답 안함
- } else {
- displayStatus = null; // 아직 초대도 안됨
- }
- }
-
- return {
- ...rfq,
- // 새로운 상태 체계
- displayStatus, // UI에서 표시할 통합 상태
-
- // 참여 관련 정보
- participationStatus: response?.participationStatus || "미응답",
- participationRepliedAt: response?.participationRepliedAt,
- nonParticipationReason: response?.nonParticipationReason,
-
- // 견적 작업 상태 (참여한 경우에만 의미 있음)
- responseStatus: response?.status,
- responseVersion: response?.responseVersion,
- submittedAt: response?.submittedAt,
- totalAmount: response?.totalAmount,
- vendorCurrency: response?.vendorCurrency,
- vendorPaymentTerms: response?.vendorPaymentTermsCode,
- vendorIncoterms: response?.vendorIncotermsCode,
- vendorDeliveryDate: response?.vendorDeliveryDate,
-
- // 초대 관련 정보
- rfqLastDetailsId: detail?.id, // 참여 결정 시 필요
- emailSentAt: detail?.emailSentAt,
- emailStatus: detail?.emailStatus,
- shortList: detail?.shortList,
- } as VendorQuotationView;
- })
- );
-
- // 6. 전체 개수 조회
+ // 전체 개수 조회
const { totalCount } = await db
.select({ totalCount: count() })
- .from(rfqsLastView)
+ .from(vendorQuotationView)
.where(finalWhere)
.then(rows => rows[0]);
// 페이지 수 계산
const pageCount = Math.ceil(Number(totalCount) / perPage);
-
return {
- data: quotationsWithResponse,
+ data: quotations,
pageCount
};
} catch (err) {
diff --git a/lib/rfq-last/vendor-response/validations.ts b/lib/rfq-last/vendor-response/validations.ts
index 033154c2..5834bbf6 100644
--- a/lib/rfq-last/vendor-response/validations.ts
+++ b/lib/rfq-last/vendor-response/validations.ts
@@ -7,7 +7,7 @@ import { createSearchParamsCache,
import * as z from "zod"
import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers"
-import { RfqsLastView } from "@/db/schema";
+import { VendorQuotationView } from "@/db/schema";
@@ -15,7 +15,7 @@ export const searchParamsVendorRfqCache = createSearchParamsCache({
flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault([]),
page: parseAsInteger.withDefault(1),
perPage: parseAsInteger.withDefault(10),
- sort: getSortingStateParser<RfqsLastView>().withDefault([
+ sort: getSortingStateParser<VendorQuotationView>().withDefault([
{ id: "updatedAt", desc: true },
]),
diff --git a/lib/rfq-last/vendor-response/vendor-quotations-table-columns.tsx b/lib/rfq-last/vendor-response/vendor-quotations-table-columns.tsx
index 144c6c43..a7135ea5 100644
--- a/lib/rfq-last/vendor-response/vendor-quotations-table-columns.tsx
+++ b/lib/rfq-last/vendor-response/vendor-quotations-table-columns.tsx
@@ -27,8 +27,8 @@ import {
} from "@/components/ui/tooltip"
import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
import { useRouter } from "next/navigation"
-import type { VendorQuotationView } from "./service"
import { ParticipationDialog } from "./participation-dialog"
+import { VendorQuotationView } from "@/db/schema"
// 통합 상태 배지 컴포넌트 (displayStatus 사용)
function DisplayStatusBadge({ status }: { status: string | null }) {
diff --git a/lib/rfq-last/vendor-response/vendor-quotations-table.tsx b/lib/rfq-last/vendor-response/vendor-quotations-table.tsx
index 683a0318..2e4975f1 100644
--- a/lib/rfq-last/vendor-response/vendor-quotations-table.tsx
+++ b/lib/rfq-last/vendor-response/vendor-quotations-table.tsx
@@ -12,9 +12,9 @@ import { DataTable } from "@/components/data-table/data-table"
import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar"
import { useRouter } from "next/navigation"
import { getColumns } from "./vendor-quotations-table-columns"
-import type { VendorQuotationView } from "./service"
import { RfqAttachmentsDialog } from "./rfq-attachments-dialog";
import { RfqItemsDialog } from "./rfq-items-dialog";
+import { VendorQuotationView } from "@/db/schema"
interface VendorQuotationsTableLastProps {
promises: Promise<[{ data: VendorQuotationView[], pageCount: number }]>