From 25749225689c3934bc10ad1e8285e13020b61282 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Thu, 4 Dec 2025 09:04:09 +0000
Subject: (최겸)구매 입찰, 계약 수정
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../vendor/components/pr-items-pricing-table.tsx       | 18 +++++++-----------
 1 file changed, 7 insertions(+), 11 deletions(-)

(limited to 'lib/bidding/vendor/components/pr-items-pricing-table.tsx')

diff --git a/lib/bidding/vendor/components/pr-items-pricing-table.tsx b/lib/bidding/vendor/components/pr-items-pricing-table.tsx
index 7dd8384e..5afb2b67 100644
--- a/lib/bidding/vendor/components/pr-items-pricing-table.tsx
+++ b/lib/bidding/vendor/components/pr-items-pricing-table.tsx
@@ -382,18 +382,14 @@ export function PrItemsPricingTable({
                           </span>
                         ) : (
                           <Input
-                            type="number"
-                            inputMode="decimal"
-                            min={0}
-                            pattern="^(0|[1-9][0-9]*)(\.[0-9]+)?$"
-                            value={quotation.bidUnitPrice === 0 ? '' : quotation.bidUnitPrice}
+                            type="text"
+                            inputMode="numeric"
+                            value={quotation.bidUnitPrice === 0 ? '' : quotation.bidUnitPrice.toLocaleString()}
                             onChange={(e) => {
-                              let value = e.target.value
-                              if (/^0[0-9]+/.test(value)) {
-                                value = value.replace(/^0+/, '')
-                                if (value === '') value = '0'
-                              }
-                              const numericValue = parseFloat(value)
+                              // 콤마 제거 및 숫자만 허용
+                              const value = e.target.value.replace(/,/g, '').replace(/[^0-9]/g, '')
+                              const numericValue = Number(value)
+                              
                               updateQuotation(
                                 item.id,
                                 'bidUnitPrice',
-- 
cgit v1.2.3


From d47334639bd717aa860563ec1020a29827524fd4 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Fri, 5 Dec 2025 06:29:23 +0000
Subject: (최겸)구매 결재일 기준 공고 수정
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../bidding/manage/bidding-schedule-editor.tsx     | 340 ++++++---
 lib/bidding/detail/service.ts                      |  97 ++-
 .../detail/table/bidding-detail-vendor-columns.tsx |  78 ++
 .../detail/table/bidding-detail-vendor-table.tsx   |  41 +-
 .../bidding-detail-vendor-toolbar-actions.tsx      |  25 +-
 .../detail/table/price-adjustment-dialog.tsx       | 195 +++++
 .../table/vendor-price-adjustment-view-dialog.tsx  | 324 +++++++++
 lib/bidding/handlers.ts                            |  94 ++-
 lib/bidding/receive/biddings-receive-columns.tsx   | 808 ++++++++++-----------
 lib/bidding/receive/biddings-receive-table.tsx     | 593 +++++++--------
 lib/bidding/service.ts                             |  61 +-
 .../vendor/components/pr-items-pricing-table.tsx   | 125 +++-
 12 files changed, 1922 insertions(+), 859 deletions(-)
 create mode 100644 lib/bidding/detail/table/price-adjustment-dialog.tsx
 create mode 100644 lib/bidding/detail/table/vendor-price-adjustment-view-dialog.tsx

(limited to 'lib/bidding/vendor/components/pr-items-pricing-table.tsx')

diff --git a/components/bidding/manage/bidding-schedule-editor.tsx b/components/bidding/manage/bidding-schedule-editor.tsx
index 49659ae7..32ce6940 100644
--- a/components/bidding/manage/bidding-schedule-editor.tsx
+++ b/components/bidding/manage/bidding-schedule-editor.tsx
@@ -21,8 +21,10 @@ import { registerBidding } from '@/lib/bidding/detail/service'
 import { useToast } from '@/hooks/use-toast'
 import { format } from 'date-fns'
 interface BiddingSchedule {
-  submissionStartDate?: string
-  submissionEndDate?: string
+  submissionStartOffset?: number      // 시작일 오프셋 (결재 후 n일)
+  submissionStartTime?: string        // 시작 시간 (HH:MM)
+  submissionDurationDays?: number     // 기간 (시작일 + n일)
+  submissionEndTime?: string          // 마감 시간 (HH:MM)
   remarks?: string
   isUrgent?: boolean
   hasSpecificationMeeting?: boolean
@@ -149,6 +151,44 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc
     return new Date(kstTime).toISOString().slice(0, 16)
   }
 
+  // timestamp에서 시간(HH:MM) 추출 (KST 기준)
+  const extractTimeFromTimestamp = (date: string | Date | undefined | null): string => {
+    if (!date) return ''
+    const d = new Date(date)
+    // UTC 시간에 9시간을 더함
+    const kstTime = d.getTime() + (9 * 60 * 60 * 1000)
+    const kstDate = new Date(kstTime)
+    const hours = kstDate.getUTCHours().toString().padStart(2, '0')
+    const minutes = kstDate.getUTCMinutes().toString().padStart(2, '0')
+    return `${hours}:${minutes}`
+  }
+
+  // 예상 일정 계산 (오늘 기준 미리보기)
+  const getPreviewDates = () => {
+    const today = new Date()
+    today.setHours(0, 0, 0, 0)
+    
+    const startOffset = schedule.submissionStartOffset ?? 0
+    const durationDays = schedule.submissionDurationDays ?? 7
+    const startTime = schedule.submissionStartTime || '09:00'
+    const endTime = schedule.submissionEndTime || '18:00'
+
+    // 시작일 계산
+    const startDate = new Date(today)
+    startDate.setDate(startDate.getDate() + startOffset)
+    const [startHour, startMinute] = startTime.split(':').map(Number)
+    startDate.setHours(startHour, startMinute, 0, 0)
+
+    // 마감일 계산
+    const endDate = new Date(startDate)
+    endDate.setHours(0, 0, 0, 0) // 시작일의 날짜만
+    endDate.setDate(endDate.getDate() + durationDays)
+    const [endHour, endMinute] = endTime.split(':').map(Number)
+    endDate.setHours(endHour, endMinute, 0, 0)
+
+    return { startDate, endDate }
+  }
+
   // 데이터 로딩
   React.useEffect(() => {
     const loadSchedule = async () => {
@@ -165,36 +205,36 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc
           })
 
           setSchedule({
-            submissionStartDate: toKstInputValue(bidding.submissionStartDate),
-            submissionEndDate: toKstInputValue(bidding.submissionEndDate),
+            submissionStartOffset: bidding.submissionStartOffset ?? 1,
+            submissionStartTime: extractTimeFromTimestamp(bidding.submissionStartDate) || '09:00',
+            submissionDurationDays: bidding.submissionDurationDays ?? 7,
+            submissionEndTime: extractTimeFromTimestamp(bidding.submissionEndDate) || '18:00',
             remarks: bidding.remarks || '',
             isUrgent: bidding.isUrgent || false,
             hasSpecificationMeeting: bidding.hasSpecificationMeeting || false,
           })
 
-          // 사양설명회 정보 로드
-          if (bidding.hasSpecificationMeeting) {
-            try {
-              const meetingDetails = await getSpecificationMeetingDetailsAction(biddingId)
-              if (meetingDetails.success && meetingDetails.data) {
-                const meeting = meetingDetails.data
-                setSpecMeetingInfo({
-                  meetingDate: toKstInputValue(meeting.meetingDate),
-                  meetingTime: meeting.meetingTime || '',
-                  location: meeting.location || '',
-                  address: meeting.address || '',
-                  contactPerson: meeting.contactPerson || '',
-                  contactPhone: meeting.contactPhone || '',
-                  contactEmail: meeting.contactEmail || '',
-                  agenda: meeting.agenda || '',
-                  materials: meeting.materials || '',
-                  notes: meeting.notes || '',
-                  isRequired: meeting.isRequired || false,
-                })
-              }
-            } catch (error) {
-              console.error('Failed to load specification meeting details:', error)
+          // 사양설명회 정보 로드 (T/F 무관하게 기존 데이터가 있으면 로드)
+          try {
+            const meetingDetails = await getSpecificationMeetingDetailsAction(biddingId)
+            if (meetingDetails.success && meetingDetails.data) {
+              const meeting = meetingDetails.data
+              setSpecMeetingInfo({
+                meetingDate: toKstInputValue(meeting.meetingDate),
+                meetingTime: meeting.meetingTime || '',
+                location: meeting.location || '',
+                address: meeting.address || '',
+                contactPerson: meeting.contactPerson || '',
+                contactPhone: meeting.contactPhone || '',
+                contactEmail: meeting.contactEmail || '',
+                agenda: meeting.agenda || '',
+                materials: meeting.materials || '',
+                notes: meeting.notes || '',
+                isRequired: meeting.isRequired || false,
+              })
             }
+          } catch (error) {
+            console.error('Failed to load specification meeting details:', error)
           }
         }
       } catch (error) {
@@ -258,10 +298,18 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc
   const handleBiddingInvitationClick = async () => {
     try {
       // 1. 입찰서 제출기간 검증
-      if (!schedule.submissionStartDate || !schedule.submissionEndDate) {
+      if (schedule.submissionStartOffset === undefined || schedule.submissionDurationDays === undefined) {
         toast({
           title: '입찰서 제출기간 미설정',
-          description: '입찰서 제출 시작일시와 마감일시를 모두 설정해주세요.',
+          description: '입찰서 제출 시작일 오프셋과 기간을 모두 설정해주세요.',
+          variant: 'destructive',
+        })
+        return
+      }
+      if (!schedule.submissionStartTime || !schedule.submissionEndTime) {
+        toast({
+          title: '입찰서 제출시간 미설정',
+          description: '입찰 시작 시간과 마감 시간을 모두 설정해주세요.',
           variant: 'destructive',
         })
         return
@@ -484,10 +532,48 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc
       const userId = session?.user?.id?.toString() || '1'
 
       // 입찰서 제출기간 필수 검증
-      if (!schedule.submissionStartDate || !schedule.submissionEndDate) {
+      if (schedule.submissionStartOffset === undefined || schedule.submissionDurationDays === undefined) {
         toast({
           title: '입찰서 제출기간 미설정',
-          description: '입찰서 제출 시작일시와 마감일시를 모두 설정해주세요.',
+          description: '입찰서 제출 시작일 오프셋과 기간을 모두 설정해주세요.',
+          variant: 'destructive',
+        })
+        setIsSubmitting(false)
+        return
+      }
+      if (!schedule.submissionStartTime || !schedule.submissionEndTime) {
+        toast({
+          title: '입찰서 제출시간 미설정',
+          description: '입찰 시작 시간과 마감 시간을 모두 설정해주세요.',
+          variant: 'destructive',
+        })
+        setIsSubmitting(false)
+        return
+      }
+      // 오프셋/기간 검증
+      if (schedule.submissionStartOffset < 0) {
+        toast({
+          title: '시작일 오프셋 오류',
+          description: '시작일 오프셋은 0 이상이어야 합니다.',
+          variant: 'destructive',
+        })
+        setIsSubmitting(false)
+        return
+      }
+      if (schedule.submissionDurationDays < 1) {
+        toast({
+          title: '기간 오류',
+          description: '입찰 기간은 최소 1일 이상이어야 합니다.',
+          variant: 'destructive',
+        })
+        setIsSubmitting(false)
+        return
+      }
+      // 긴급 입찰이 아닌 경우 당일 시작 불가 (오프셋 0)
+      if (!schedule.isUrgent && schedule.submissionStartOffset === 0) {
+        toast({
+          title: '시작일 오류',
+          description: '긴급 입찰이 아닌 경우 당일 시작(오프셋 0)은 불가능합니다.',
           variant: 'destructive',
         })
         setIsSubmitting(false)
@@ -538,62 +624,55 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc
     }
   }
 
-  const handleScheduleChange = (field: keyof BiddingSchedule, value: string | boolean) => {
-    // 마감일시 검증 - 현재일 이전 설정 불가
-    if (field === 'submissionEndDate' && typeof value === 'string' && value) {
-      const selectedDate = new Date(value)
-      const now = new Date()
-      now.setHours(0, 0, 0, 0) // 시간을 00:00:00으로 설정하여 날짜만 비교
-
-      if (selectedDate < now) {
+  const handleScheduleChange = (field: keyof BiddingSchedule, value: string | boolean | number) => {
+    // 시작일 오프셋 검증
+    if (field === 'submissionStartOffset' && typeof value === 'number') {
+      if (value < 0) {
+        toast({
+          title: '시작일 오프셋 오류',
+          description: '시작일 오프셋은 0 이상이어야 합니다.',
+          variant: 'destructive',
+        })
+        return
+      }
+      // 긴급 입찰이 아닌 경우 당일 시작(오프셋 0) 불가
+      if (!schedule.isUrgent && value === 0) {
         toast({
-          title: '마감일시 오류',
-          description: '마감일시는 현재일 이전으로 설정할 수 없습니다.',
+          title: '시작일 오프셋 오류',
+          description: '긴급 입찰이 아닌 경우 당일 시작(오프셋 0)은 불가능합니다.',
           variant: 'destructive',
         })
-        return // 변경을 적용하지 않음
+        return
       }
     }
 
-    // 긴급여부 미선택 시 당일 제출시작 불가
-    if (field === 'submissionStartDate' && typeof value === 'string' && value) {
-      const selectedDate = new Date(value)
-      const today = new Date()
-      today.setHours(0, 0, 0, 0) // 시간을 00:00:00으로 설정
-      selectedDate.setHours(0, 0, 0, 0)
-
-      // 현재 긴급 여부 확인 (field가 'isUrgent'인 경우 value 사용, 아니면 기존 schedule 값)
-      const isUrgent = field === 'isUrgent' ? (value as boolean) : schedule.isUrgent || false
+    // 기간 검증
+    if (field === 'submissionDurationDays' && typeof value === 'number') {
+      if (value < 1) {
+        toast({
+          title: '기간 오류',
+          description: '입찰 기간은 최소 1일 이상이어야 합니다.',
+          variant: 'destructive',
+        })
+        return
+      }
+    }
 
-      // 긴급이 아닌 경우 당일 시작 불가
-      if (!isUrgent && selectedDate.getTime() === today.getTime()) {
+    // 시간 형식 검증 (HH:MM)
+    if ((field === 'submissionStartTime' || field === 'submissionEndTime') && typeof value === 'string') {
+      const timeRegex = /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/
+      if (value && !timeRegex.test(value)) {
         toast({
-          title: '제출 시작일시 오류',
-          description: '긴급 입찰이 아닌 경우 당일 제출 시작은 불가능합니다.',
+          title: '시간 형식 오류',
+          description: '시간은 HH:MM 형식으로 입력해주세요.',
           variant: 'destructive',
         })
-        return // 변경을 적용하지 않음
+        return
       }
     }
 
     setSchedule(prev => ({ ...prev, [field]: value }))
-
-    // 사양설명회 실시 여부가 false로 변경되면 상세 정보 초기화
-    if (field === 'hasSpecificationMeeting' && value === false) {
-      setSpecMeetingInfo({
-        meetingDate: '',
-        meetingTime: '',
-        location: '',
-        address: '',
-        contactPerson: '',
-        contactPhone: '',
-        contactEmail: '',
-        agenda: '',
-        materials: '',
-        notes: '',
-        isRequired: false,
-      })
-    }
+    // 사양설명회 실시 여부가 false로 변경되어도 상세 정보 초기화하지 않음 (기존 데이터 유지)
   }
 
   if (isLoading) {
@@ -624,40 +703,98 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc
               <Clock className="h-4 w-4" />
               입찰서 제출 기간
             </h3>
+            <p className="text-sm text-muted-foreground">
+              입찰공고(결재 완료) 시점을 기준으로 일정이 자동 계산됩니다.
+            </p>
+            
+            {/* 시작일 설정 */}
             <div className="grid grid-cols-2 gap-4">
               <div className="space-y-2">
-                <Label htmlFor="submission-start">제출 시작일시 <span className="text-red-500">*</span></Label>
+                <Label htmlFor="submission-start-offset">시작일 (결재 후) <span className="text-red-500">*</span></Label>
+                <div className="flex items-center gap-2">
+                  <Input
+                    id="submission-start-offset"
+                    type="number"
+                    min={schedule.isUrgent ? 0 : 1}
+                    value={schedule.submissionStartOffset ?? ''}
+                    onChange={(e) => handleScheduleChange('submissionStartOffset', parseInt(e.target.value) || 0)}
+                    className={schedule.submissionStartOffset === undefined ? 'border-red-200' : ''}
+                    disabled={readonly}
+                    placeholder="0"
+                  />
+                  <span className="text-sm text-muted-foreground whitespace-nowrap">일 후</span>
+                </div>
+                {schedule.submissionStartOffset === undefined && (
+                  <p className="text-sm text-red-500">시작일 오프셋은 필수입니다</p>
+                )}
+                {!schedule.isUrgent && schedule.submissionStartOffset === 0 && (
+                  <p className="text-sm text-amber-600">긴급 입찰만 당일 시작(0일) 가능</p>
+                )}
+              </div>
+              <div className="space-y-2">
+                <Label htmlFor="submission-start-time">시작 시간 <span className="text-red-500">*</span></Label>
                 <Input
-                  id="submission-start"
-                  type="datetime-local"
-                  value={schedule.submissionStartDate}
-                  onChange={(e) => handleScheduleChange('submissionStartDate', e.target.value)}
-                  className={!schedule.submissionStartDate ? 'border-red-200' : ''}
+                  id="submission-start-time"
+                  type="time"
+                  value={schedule.submissionStartTime || ''}
+                  onChange={(e) => handleScheduleChange('submissionStartTime', e.target.value)}
+                  className={!schedule.submissionStartTime ? 'border-red-200' : ''}
                   disabled={readonly}
-                  min="1900-01-01T00:00"
-                  max="2100-12-31T23:59"
                 />
-                {!schedule.submissionStartDate && (
-                  <p className="text-sm text-red-500">제출 시작일시는 필수입니다</p>
+                {!schedule.submissionStartTime && (
+                  <p className="text-sm text-red-500">시작 시간은 필수입니다</p>
+                )}
+              </div>
+            </div>
+
+            {/* 마감일 설정 */}
+            <div className="grid grid-cols-2 gap-4">
+              <div className="space-y-2">
+                <Label htmlFor="submission-duration">입찰 기간 (시작일 +) <span className="text-red-500">*</span></Label>
+                <div className="flex items-center gap-2">
+                  <Input
+                    id="submission-duration"
+                    type="number"
+                    min={1}
+                    value={schedule.submissionDurationDays ?? ''}
+                    onChange={(e) => handleScheduleChange('submissionDurationDays', parseInt(e.target.value) || 1)}
+                    className={schedule.submissionDurationDays === undefined ? 'border-red-200' : ''}
+                    disabled={readonly}
+                    placeholder="7"
+                  />
+                  <span className="text-sm text-muted-foreground whitespace-nowrap">일간</span>
+                </div>
+                {schedule.submissionDurationDays === undefined && (
+                  <p className="text-sm text-red-500">입찰 기간은 필수입니다</p>
                 )}
               </div>
               <div className="space-y-2">
-                <Label htmlFor="submission-end">제출 마감일시 <span className="text-red-500">*</span></Label>
+                <Label htmlFor="submission-end-time">마감 시간 <span className="text-red-500">*</span></Label>
                 <Input
-                  id="submission-end"
-                  type="datetime-local"
-                  value={schedule.submissionEndDate}
-                  onChange={(e) => handleScheduleChange('submissionEndDate', e.target.value)}
-                  className={!schedule.submissionEndDate ? 'border-red-200' : ''}
+                  id="submission-end-time"
+                  type="time"
+                  value={schedule.submissionEndTime || ''}
+                  onChange={(e) => handleScheduleChange('submissionEndTime', e.target.value)}
+                  className={!schedule.submissionEndTime ? 'border-red-200' : ''}
                   disabled={readonly}
-                  min="1900-01-01T00:00"
-                  max="2100-12-31T23:59"
                 />
-                {!schedule.submissionEndDate && (
-                  <p className="text-sm text-red-500">제출 마감일시는 필수입니다</p>
+                {!schedule.submissionEndTime && (
+                  <p className="text-sm text-red-500">마감 시간은 필수입니다</p>
                 )}
               </div>
             </div>
+
+            {/* 예상 일정 미리보기 */}
+            {schedule.submissionStartOffset !== undefined && schedule.submissionDurationDays !== undefined && (
+              <div className="p-3 bg-blue-50 rounded-lg border border-blue-200">
+                <p className="text-sm font-medium text-blue-800 mb-1">📅 예상 일정 (오늘 공고 기준)</p>
+                <div className="text-sm text-blue-700">
+                  <span>시작: {format(getPreviewDates().startDate, "yyyy-MM-dd HH:mm")}</span>
+                  <span className="mx-2">~</span>
+                  <span>마감: {format(getPreviewDates().endDate, "yyyy-MM-dd HH:mm")}</span>
+                </div>
+              </div>
+            )}
           </div>
 
           {/* 긴급 여부 */}
@@ -690,8 +827,8 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc
             />
           </div>
 
-          {/* 사양설명회 상세 정보 */}
-          {schedule.hasSpecificationMeeting && (
+          {/* 사양설명회 상세 정보 - T/F와 무관하게 기존 데이터가 있거나 hasSpecificationMeeting이 true면 표시 */}
+          {(schedule.hasSpecificationMeeting) && (
             <div className="space-y-6 p-4 border rounded-lg bg-muted/50">
               <div className="grid grid-cols-2 gap-4">
                 <div>
@@ -834,10 +971,19 @@ export function BiddingScheduleEditor({ biddingId, readonly = false }: BiddingSc
         <CardContent>
           <div className="space-y-2 text-sm">
             <div className="flex justify-between">
-              <span className="font-medium">입찰서 제출 기간:</span>
+              <span className="font-medium">시작일:</span>
+              <span>
+                {schedule.submissionStartOffset !== undefined
+                  ? `결재 후 ${schedule.submissionStartOffset}일, ${schedule.submissionStartTime || '미설정'}`
+                  : '미설정'
+                }
+              </span>
+            </div>
+            <div className="flex justify-between">
+              <span className="font-medium">마감일:</span>
               <span>
-                {schedule.submissionStartDate && schedule.submissionEndDate
-                  ? `${format(new Date(schedule.submissionStartDate), "yyyy-MM-dd HH:mm")} ~ ${format(new Date(schedule.submissionEndDate), "yyyy-MM-dd HH:mm")}`
+                {schedule.submissionDurationDays !== undefined
+                  ? `시작일 + ${schedule.submissionDurationDays}일, ${schedule.submissionEndTime || '미설정'}`
                   : '미설정'
                 }
               </span>
diff --git a/lib/bidding/detail/service.ts b/lib/bidding/detail/service.ts
index 17ea8f28..4ef48d33 100644
--- a/lib/bidding/detail/service.ts
+++ b/lib/bidding/detail/service.ts
@@ -64,6 +64,12 @@ export async function getBiddingDetailData(biddingId: number): Promise<BiddingDe
         awardRatio: biddingCompanies.awardRatio,
         isBiddingParticipated: biddingCompanies.isBiddingParticipated,
         invitationStatus: biddingCompanies.invitationStatus,
+        // 연동제 관련 필드
+        isPriceAdjustmentApplicableQuestion: biddingCompanies.isPriceAdjustmentApplicableQuestion,
+        priceAdjustmentResponse: companyConditionResponses.priceAdjustmentResponse, // 벤더가 응답한 연동제 적용 여부
+        shiPriceAdjustmentApplied: biddingCompanies.shiPriceAdjustmentApplied,
+        priceAdjustmentNote: biddingCompanies.priceAdjustmentNote,
+        hasChemicalSubstance: biddingCompanies.hasChemicalSubstance,
         // Contact info from biddingCompaniesContacts
         contactPerson: biddingCompaniesContacts.contactName,
         contactEmail: biddingCompaniesContacts.contactEmail,
@@ -75,6 +81,10 @@ export async function getBiddingDetailData(biddingId: number): Promise<BiddingDe
         eq(biddingCompaniesContacts.biddingId, biddingId),
         eq(biddingCompaniesContacts.vendorId, biddingCompanies.companyId)
       ))
+      .leftJoin(companyConditionResponses, and(
+        eq(companyConditionResponses.biddingCompanyId, biddingCompanies.id),
+        eq(companyConditionResponses.isPreQuote, false) // 본입찰 데이터만
+      ))
       .where(and(
         eq(biddingCompanies.biddingId, biddingId),
         eq(biddingCompanies.isBiddingParticipated, true)
@@ -102,6 +112,12 @@ export async function getBiddingDetailData(biddingId: number): Promise<BiddingDe
           awardRatio: curr.awardRatio ? Number(curr.awardRatio) : null,
           isBiddingParticipated: curr.isBiddingParticipated,
           invitationStatus: curr.invitationStatus,
+          // 연동제 관련 필드
+          isPriceAdjustmentApplicableQuestion: curr.isPriceAdjustmentApplicableQuestion,
+          priceAdjustmentResponse: curr.priceAdjustmentResponse, // 벤더가 응답한 연동제 적용 여부
+          shiPriceAdjustmentApplied: curr.shiPriceAdjustmentApplied,
+          priceAdjustmentNote: curr.priceAdjustmentNote,
+          hasChemicalSubstance: curr.hasChemicalSubstance,
           documents: [],
         })
       }
@@ -148,6 +164,12 @@ export interface QuotationVendor {
   awardRatio: number | null // 발주비율
   isBiddingParticipated: boolean | null // 본입찰 참여여부
   invitationStatus: 'pending' | 'pre_quote_sent' | 'pre_quote_accepted' | 'pre_quote_declined' | 'pre_quote_submitted' | 'bidding_sent' | 'bidding_accepted' | 'bidding_declined' | 'bidding_cancelled' | 'bidding_submitted'
+  // 연동제 관련 필드
+  isPriceAdjustmentApplicableQuestion: boolean | null // SHI가 요청한 연동제 요청 여부
+  priceAdjustmentResponse: boolean | null // 벤더가 응답한 연동제 적용 여부 (companyConditionResponses.priceAdjustmentResponse)
+  shiPriceAdjustmentApplied: boolean | null // SHI 연동제 적용여부
+  priceAdjustmentNote: string | null // 연동제 Note
+  hasChemicalSubstance: boolean | null // 화학물질여부
   documents: Array<{
     id: number
     fileName: string
@@ -818,11 +840,52 @@ export async function registerBidding(biddingId: number, userId: string) {
 
     await db.transaction(async (tx) => {
       debugLog('registerBidding: Transaction started')
-      // 1. 입찰 상태를 오픈으로 변경
+      
+      // 0. 입찰서 제출기간 계산 (오프셋 기반)
+      const { submissionStartOffset, submissionDurationDays, submissionStartDate, submissionEndDate } = bidding
+      
+      let calculatedStartDate = bidding.submissionStartDate
+      let calculatedEndDate = bidding.submissionEndDate
+
+      // 오프셋 값이 있으면 날짜 계산
+      if (submissionStartOffset !== null && submissionDurationDays !== null) {
+        // 시간 추출 (기본값: 시작 09:00, 마감 18:00)
+        const startTime = submissionStartDate 
+          ? { hours: submissionStartDate.getUTCHours(), minutes: submissionStartDate.getUTCMinutes() }
+          : { hours: 9, minutes: 0 }
+        const endTime = submissionEndDate
+          ? { hours: submissionEndDate.getUTCHours(), minutes: submissionEndDate.getUTCMinutes() }
+          : { hours: 18, minutes: 0 }
+
+        // baseDate = 현재일 날짜만 (00:00:00)
+        const baseDate = new Date()
+        baseDate.setHours(0, 0, 0, 0)
+
+        // 시작일 = baseDate + offset일 + 시작시간
+        calculatedStartDate = new Date(baseDate)
+        calculatedStartDate.setDate(calculatedStartDate.getDate() + submissionStartOffset)
+        calculatedStartDate.setHours(startTime.hours, startTime.minutes, 0, 0)
+
+        // 마감일 = 시작일(날짜만) + duration일 + 마감시간
+        calculatedEndDate = new Date(calculatedStartDate)
+        calculatedEndDate.setHours(0, 0, 0, 0)
+        calculatedEndDate.setDate(calculatedEndDate.getDate() + submissionDurationDays)
+        calculatedEndDate.setHours(endTime.hours, endTime.minutes, 0, 0)
+
+        debugLog('registerBidding: Submission dates calculated', {
+          baseDate: baseDate.toISOString(),
+          calculatedStartDate: calculatedStartDate.toISOString(),
+          calculatedEndDate: calculatedEndDate.toISOString(),
+        })
+      }
+
+      // 1. 입찰 상태를 오픈으로 변경 + 제출기간 업데이트
       await tx
         .update(biddings)
         .set({
           status: 'bidding_opened',
+          submissionStartDate: calculatedStartDate,
+          submissionEndDate: calculatedEndDate,
           updatedBy: userName,
           updatedAt: new Date()
         })
@@ -2617,3 +2680,35 @@ export async function setSpecificationMeetingParticipation(biddingCompanyId: num
     return { success: false, error: '사양설명회 참여상태 업데이트에 실패했습니다.' }
   }
 }
+
+// 연동제 정보 업데이트
+export async function updatePriceAdjustmentInfo(params: {
+  biddingCompanyId: number
+  shiPriceAdjustmentApplied: boolean | null
+  priceAdjustmentNote: string | null
+  hasChemicalSubstance: boolean | null
+}): Promise<{ success: boolean; error?: string }> {
+  try {
+    const result = await db.update(biddingCompanies)
+      .set({
+        shiPriceAdjustmentApplied: params.shiPriceAdjustmentApplied,
+        priceAdjustmentNote: params.priceAdjustmentNote,
+        hasChemicalSubstance: params.hasChemicalSubstance,
+        updatedAt: new Date(),
+      })
+      .where(eq(biddingCompanies.id, params.biddingCompanyId))
+      .returning({ biddingId: biddingCompanies.biddingId })
+
+    if (result.length > 0) {
+      const biddingId = result[0].biddingId
+      revalidateTag(`bidding-${biddingId}`)
+      revalidateTag('quotation-vendors')
+      revalidatePath(`/evcp/bid/${biddingId}`)
+    }
+
+    return { success: true }
+  } catch (error) {
+    console.error('Failed to update price adjustment info:', error)
+    return { success: false, error: '연동제 정보 업데이트에 실패했습니다.' }
+  }
+}
diff --git a/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx b/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx
index 5368b287..05c1a93d 100644
--- a/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx
+++ b/lib/bidding/detail/table/bidding-detail-vendor-columns.tsx
@@ -31,6 +31,7 @@ interface GetVendorColumnsProps {
 }
 
 export function getBiddingDetailVendorColumns({
+  onViewPriceAdjustment,
   onViewItemDetails,
   onSendBidding,
   onUpdateParticipation,
@@ -238,6 +239,83 @@ export function getBiddingDetailVendorColumns({
         </div>
       ),
     },
+    {
+      accessorKey: 'priceAdjustmentResponse',
+      header: '연동제 응답',
+      cell: ({ row }) => {
+        const vendor = row.original
+        const response = vendor.priceAdjustmentResponse
+        
+        // 버튼 형태로 표시, 클릭 시 상세 다이얼로그 열기
+        const getBadgeVariant = () => {
+          if (response === null || response === undefined) return 'outline'
+          return response ? 'default' : 'secondary'
+        }
+        
+        const getBadgeClass = () => {
+          if (response === true) return 'bg-green-600 hover:bg-green-700 cursor-pointer'
+          if (response === false) return 'hover:bg-gray-300 cursor-pointer'
+          return ''
+        }
+        
+        const getLabel = () => {
+          if (response === null || response === undefined) return '해당없음'
+          return response ? '예' : '아니오'
+        }
+        
+        return (
+          <Badge 
+            variant={getBadgeVariant()} 
+            className={getBadgeClass()}
+            onClick={() => onViewPriceAdjustment?.(vendor)}
+          >
+            {getLabel()}
+          </Badge>
+        )
+      },
+    },
+    {
+      accessorKey: 'shiPriceAdjustmentApplied',
+      header: 'SHI연동제적용',
+      cell: ({ row }) => {
+        const applied = row.original.shiPriceAdjustmentApplied
+        if (applied === null || applied === undefined) {
+          return <Badge variant="outline">미정</Badge>
+        }
+        return (
+          <Badge variant={applied ? 'default' : 'secondary'} className={applied ? 'bg-green-600' : ''}>
+            {applied ? '적용' : '미적용'}
+          </Badge>
+        )
+      },
+    },
+    {
+      accessorKey: 'priceAdjustmentNote',
+      header: '연동제 Note',
+      cell: ({ row }) => {
+        const note = row.original.priceAdjustmentNote
+        return (
+          <div className="text-sm max-w-[150px] truncate" title={note || ''}>
+            {note || '-'}
+          </div>
+        )
+      },
+    },
+    {
+      accessorKey: 'hasChemicalSubstance',
+      header: '화학물질',
+      cell: ({ row }) => {
+        const hasChemical = row.original.hasChemicalSubstance
+        if (hasChemical === null || hasChemical === undefined) {
+          return <Badge variant="outline">미정</Badge>
+        }
+        return (
+          <Badge variant={hasChemical ? 'destructive' : 'secondary'}>
+            {hasChemical ? '해당' : '해당없음'}
+          </Badge>
+        )
+      },
+    },
     {
       id: 'actions',
       header: '작업',
diff --git a/lib/bidding/detail/table/bidding-detail-vendor-table.tsx b/lib/bidding/detail/table/bidding-detail-vendor-table.tsx
index a6f64964..407cc51c 100644
--- a/lib/bidding/detail/table/bidding-detail-vendor-table.tsx
+++ b/lib/bidding/detail/table/bidding-detail-vendor-table.tsx
@@ -10,9 +10,9 @@ import { BiddingDetailVendorToolbarActions } from './bidding-detail-vendor-toolb
 import { BiddingDetailVendorEditDialog } from './bidding-detail-vendor-edit-dialog'
 import { BiddingAwardDialog } from './bidding-award-dialog'
 import { getBiddingDetailVendorColumns } from './bidding-detail-vendor-columns'
-import { QuotationVendor, getPriceAdjustmentFormByBiddingCompanyId } from '@/lib/bidding/detail/service'
+import { QuotationVendor } from '@/lib/bidding/detail/service'
 import { Bidding } from '@/db/schema'
-import { PriceAdjustmentDialog } from '@/components/bidding/price-adjustment-dialog'
+import { VendorPriceAdjustmentViewDialog } from './vendor-price-adjustment-view-dialog'
 import { QuotationHistoryDialog } from './quotation-history-dialog'
 import { ApprovalPreviewDialog } from '@/lib/approval/approval-preview-dialog'
 import { ApplicationReasonDialog } from '@/lib/rfq-last/vendor/application-reason-dialog'
@@ -98,8 +98,7 @@ export function BiddingDetailVendorTableContent({
   const [selectedVendor, setSelectedVendor] = React.useState<QuotationVendor | null>(null)
   const [isAwardDialogOpen, setIsAwardDialogOpen] = React.useState(false)
   const [isAwardRatioDialogOpen, setIsAwardRatioDialogOpen] = React.useState(false)
-  const [priceAdjustmentData, setPriceAdjustmentData] = React.useState<any>(null)
-  const [isPriceAdjustmentDialogOpen, setIsPriceAdjustmentDialogOpen] = React.useState(false)
+  const [isVendorPriceAdjustmentDialogOpen, setIsVendorPriceAdjustmentDialogOpen] = React.useState(false)
   const [quotationHistoryData, setQuotationHistoryData] = React.useState<any>(null)
   const [isQuotationHistoryDialogOpen, setIsQuotationHistoryDialogOpen] = React.useState(false)
   const [approvalPreviewData, setApprovalPreviewData] = React.useState<{
@@ -116,28 +115,9 @@ export function BiddingDetailVendorTableContent({
   } | null>(null)
   const [isApprovalPreviewDialogOpen, setIsApprovalPreviewDialogOpen] = React.useState(false)
 
-  const handleViewPriceAdjustment = async (vendor: QuotationVendor) => {
-    try {
-      const priceAdjustmentForm = await getPriceAdjustmentFormByBiddingCompanyId(vendor.id)
-      if (priceAdjustmentForm) {
-        setPriceAdjustmentData(priceAdjustmentForm)
-        setSelectedVendor(vendor)
-        setIsPriceAdjustmentDialogOpen(true)
-      } else {
-        toast({
-          title: '연동제 정보 없음',
-          description: '해당 업체의 연동제 정보가 없습니다.',
-          variant: 'default',
-        })
-      }
-    } catch (error) {
-      console.error('Failed to load price adjustment form:', error)
-      toast({
-        title: '오류',
-        description: '연동제 정보를 불러오는데 실패했습니다.',
-        variant: 'destructive',
-      })
-    }
+  const handleViewPriceAdjustment = (vendor: QuotationVendor) => {
+    setSelectedVendor(vendor)
+    setIsVendorPriceAdjustmentDialogOpen(true)
   }
 
   const handleViewQuotationHistory = async (vendor: QuotationVendor) => {
@@ -299,11 +279,12 @@ export function BiddingDetailVendorTableContent({
         }}
       />
 
-      <PriceAdjustmentDialog
-        open={isPriceAdjustmentDialogOpen}
-        onOpenChange={setIsPriceAdjustmentDialogOpen}
-        data={priceAdjustmentData}
+      <VendorPriceAdjustmentViewDialog
+        open={isVendorPriceAdjustmentDialogOpen}
+        onOpenChange={setIsVendorPriceAdjustmentDialogOpen}
         vendorName={selectedVendor?.vendorName || ''}
+        priceAdjustmentResponse={selectedVendor?.priceAdjustmentResponse ?? null}
+        biddingCompanyId={selectedVendor?.id || 0}
       />
 
       <QuotationHistoryDialog
diff --git a/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx b/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx
index 7e571a23..d3df141a 100644
--- a/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx
+++ b/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx
@@ -5,13 +5,14 @@ import { useRouter } from "next/navigation"
 import { useTransition } from "react"
 import { Button } from "@/components/ui/button"
 import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
-import { Plus, Send, RotateCcw, XCircle, Trophy, FileText, DollarSign, RotateCw } from "lucide-react"
+import { Plus, Send, RotateCcw, XCircle, Trophy, FileText, DollarSign, RotateCw, Link2 } from "lucide-react"
 import { registerBidding, markAsDisposal, cancelAwardRatio } from "@/lib/bidding/detail/service"
 import { sendBiddingBasicContracts, getSelectedVendorsForBidding } from "@/lib/bidding/pre-quote/service"
 import { increaseRoundOrRebid } from "@/lib/bidding/service"
 
 import { BiddingDetailVendorCreateDialog } from "../../../../components/bidding/manage/bidding-detail-vendor-create-dialog"
 import { BiddingDocumentUploadDialog } from "./bidding-document-upload-dialog"
+import { PriceAdjustmentDialog } from "./price-adjustment-dialog"
 import { Bidding } from "@/db/schema"
 import { useToast } from "@/hooks/use-toast"
 import { QuotationVendor } from "@/lib/bidding/detail/service"
@@ -49,6 +50,7 @@ export function BiddingDetailVendorToolbarActions({
   const [selectedVendors, setSelectedVendors] = React.useState<any[]>([])
   const [isRoundIncreaseDialogOpen, setIsRoundIncreaseDialogOpen] = React.useState(false)
   const [isCancelAwardDialogOpen, setIsCancelAwardDialogOpen] = React.useState(false)
+  const [isPriceAdjustmentDialogOpen, setIsPriceAdjustmentDialogOpen] = React.useState(false)
 
   // 본입찰 초대 다이얼로그가 열릴 때 선정된 업체들 조회
   React.useEffect(() => {
@@ -196,6 +198,19 @@ export function BiddingDetailVendorToolbarActions({
               </Button>
             )}
 
+            {/* 연동제 적용여부: single select 시에만 활성화 */}
+            {(bidding.status === 'evaluation_of_bidding') && (
+              <Button
+                variant="outline"
+                size="sm"
+                onClick={() => setIsPriceAdjustmentDialogOpen(true)}
+                disabled={!singleSelectedVendor || isPending || singleSelectedVendor.isBiddingParticipated !== true}
+              >
+                <Link2 className="mr-2 h-4 w-4" />
+                연동제 적용
+              </Button>
+            )}
+
             {/* 유찰/낙찰: 입찰공고 또는 입찰평가중 상태에서만 */}
             {(bidding.status === 'bidding_opened' || bidding.status === 'evaluation_of_bidding') && (
               <>
@@ -331,6 +346,14 @@ export function BiddingDetailVendorToolbarActions({
         </DialogContent>
       </Dialog>
 
+      {/* 연동제 적용여부 다이얼로그 */}
+      <PriceAdjustmentDialog
+        open={isPriceAdjustmentDialogOpen}
+        onOpenChange={setIsPriceAdjustmentDialogOpen}
+        vendor={singleSelectedVendor || null}
+        onSuccess={onSuccess}
+      />
+
     </>
   )
 }
diff --git a/lib/bidding/detail/table/price-adjustment-dialog.tsx b/lib/bidding/detail/table/price-adjustment-dialog.tsx
new file mode 100644
index 00000000..14bbd843
--- /dev/null
+++ b/lib/bidding/detail/table/price-adjustment-dialog.tsx
@@ -0,0 +1,195 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/components/ui/button"
+import {
+  Dialog,
+  DialogContent,
+  DialogDescription,
+  DialogFooter,
+  DialogHeader,
+  DialogTitle,
+} from "@/components/ui/dialog"
+import { Label } from "@/components/ui/label"
+import { Textarea } from "@/components/ui/textarea"
+import { Switch } from "@/components/ui/switch"
+import { useToast } from "@/hooks/use-toast"
+import { updatePriceAdjustmentInfo } from "@/lib/bidding/detail/service"
+import { QuotationVendor } from "@/lib/bidding/detail/service"
+import { Loader2 } from "lucide-react"
+
+interface PriceAdjustmentDialogProps {
+  open: boolean
+  onOpenChange: (open: boolean) => void
+  vendor: QuotationVendor | null
+  onSuccess: () => void
+}
+
+export function PriceAdjustmentDialog({
+  open,
+  onOpenChange,
+  vendor,
+  onSuccess,
+}: PriceAdjustmentDialogProps) {
+  const { toast } = useToast()
+  const [isSubmitting, setIsSubmitting] = React.useState(false)
+  
+  // 폼 상태
+  const [shiPriceAdjustmentApplied, setSHIPriceAdjustmentApplied] = React.useState<boolean | null>(null)
+  const [priceAdjustmentNote, setPriceAdjustmentNote] = React.useState("")
+  const [hasChemicalSubstance, setHasChemicalSubstance] = React.useState<boolean | null>(null)
+
+  // 다이얼로그가 열릴 때 벤더 정보로 폼 초기화
+  React.useEffect(() => {
+    if (open && vendor) {
+      setSHIPriceAdjustmentApplied(vendor.shiPriceAdjustmentApplied ?? null)
+      setPriceAdjustmentNote(vendor.priceAdjustmentNote || "")
+      setHasChemicalSubstance(vendor.hasChemicalSubstance ?? null)
+    }
+  }, [open, vendor])
+
+  const handleSubmit = async () => {
+    if (!vendor) return
+
+    setIsSubmitting(true)
+    try {
+      const result = await updatePriceAdjustmentInfo({
+        biddingCompanyId: vendor.id,
+        shiPriceAdjustmentApplied,
+        priceAdjustmentNote: priceAdjustmentNote || null,
+        hasChemicalSubstance,
+      })
+
+      if (result.success) {
+        toast({
+          title: "저장 완료",
+          description: "연동제 정보가 저장되었습니다.",
+        })
+        onOpenChange(false)
+        onSuccess()
+      } else {
+        toast({
+          title: "오류",
+          description: result.error || "저장 중 오류가 발생했습니다.",
+          variant: "destructive",
+        })
+      }
+    } catch (error) {
+      console.error("연동제 정보 저장 오류:", error)
+      toast({
+        title: "오류",
+        description: "저장 중 오류가 발생했습니다.",
+        variant: "destructive",
+      })
+    } finally {
+      setIsSubmitting(false)
+    }
+  }
+
+  if (!vendor) return null
+
+  return (
+    <Dialog open={open} onOpenChange={onOpenChange}>
+      <DialogContent className="sm:max-w-[500px]">
+        <DialogHeader>
+          <DialogTitle>연동제 적용 설정</DialogTitle>
+          <DialogDescription>
+            <span className="font-semibold text-primary">{vendor.vendorName}</span> 업체의 연동제 적용 여부 및 화학물질 정보를 설정합니다.
+          </DialogDescription>
+        </DialogHeader>
+
+        <div className="space-y-6 py-4">
+          {/* 업체가 제출한 연동제 요청 여부 (읽기 전용) */}
+          <div className="flex flex-row items-center justify-between rounded-lg border p-4 bg-muted/50">
+            <div className="space-y-0.5">
+              <Label className="text-base">업체 연동제 요청</Label>
+              <p className="text-sm text-muted-foreground">
+                업체가 제출한 연동제 적용 요청 여부입니다.
+              </p>
+            </div>
+            <span className={`font-medium ${vendor.isPriceAdjustmentApplicableQuestion ? 'text-green-600' : 'text-gray-500'}`}>
+              {vendor.isPriceAdjustmentApplicableQuestion === null ? '미정' : vendor.isPriceAdjustmentApplicableQuestion ? '예' : '아니오'}
+            </span>
+          </div>
+
+          {/* SHI 연동제 적용여부 */}
+          <div className="flex flex-row items-center justify-between rounded-lg border p-4">
+            <div className="space-y-0.5">
+              <Label className="text-base">SHI 연동제 적용</Label>
+              <p className="text-sm text-muted-foreground">
+                해당 업체에 연동제를 적용할지 결정합니다.
+              </p>
+            </div>
+            <div className="flex items-center gap-3">
+              <span className={`text-sm ${shiPriceAdjustmentApplied === false ? 'font-medium' : 'text-muted-foreground'}`}>
+                미적용
+              </span>
+              <Switch
+                checked={shiPriceAdjustmentApplied === true}
+                onCheckedChange={(checked) => setSHIPriceAdjustmentApplied(checked)}
+              />
+              <span className={`text-sm ${shiPriceAdjustmentApplied === true ? 'font-medium' : 'text-muted-foreground'}`}>
+                적용
+              </span>
+            </div>
+          </div>
+
+          {/* 연동제 Note */}
+          <div className="space-y-2">
+            <Label htmlFor="price-adjustment-note">연동제 Note</Label>
+            <Textarea
+              id="price-adjustment-note"
+              placeholder="연동제 관련 추가 사항을 입력하세요"
+              value={priceAdjustmentNote}
+              onChange={(e) => setPriceAdjustmentNote(e.target.value)}
+              rows={4}
+            />
+          </div>
+
+          {/* 화학물질 여부 */}
+          <div className="flex flex-row items-center justify-between rounded-lg border p-4">
+            <div className="space-y-0.5">
+              <Label className="text-base">화학물질 해당여부</Label>
+              <p className="text-sm text-muted-foreground">
+                해당 업체가 화학물질 취급 대상인지 여부입니다.
+              </p>
+            </div>
+            <div className="flex items-center gap-3">
+              <span className={`text-sm ${hasChemicalSubstance === false ? 'font-medium' : 'text-muted-foreground'}`}>
+                해당없음
+              </span>
+              <Switch
+                checked={hasChemicalSubstance === true}
+                onCheckedChange={(checked) => setHasChemicalSubstance(checked)}
+              />
+              <span className={`text-sm ${hasChemicalSubstance === true ? 'font-medium text-red-600' : 'text-muted-foreground'}`}>
+                해당
+              </span>
+            </div>
+          </div>
+        </div>
+
+        <DialogFooter>
+          <Button
+            variant="outline"
+            onClick={() => onOpenChange(false)}
+            disabled={isSubmitting}
+          >
+            취소
+          </Button>
+          <Button onClick={handleSubmit} disabled={isSubmitting}>
+            {isSubmitting ? (
+              <>
+                <Loader2 className="mr-2 h-4 w-4 animate-spin" />
+                저장 중...
+              </>
+            ) : (
+              "저장"
+            )}
+          </Button>
+        </DialogFooter>
+      </DialogContent>
+    </Dialog>
+  )
+}
+
diff --git a/lib/bidding/detail/table/vendor-price-adjustment-view-dialog.tsx b/lib/bidding/detail/table/vendor-price-adjustment-view-dialog.tsx
new file mode 100644
index 00000000..f31caf5e
--- /dev/null
+++ b/lib/bidding/detail/table/vendor-price-adjustment-view-dialog.tsx
@@ -0,0 +1,324 @@
+'use client'
+
+import React from 'react'
+import {
+  Dialog,
+  DialogContent,
+  DialogDescription,
+  DialogHeader,
+  DialogTitle,
+} from '@/components/ui/dialog'
+import { Badge } from '@/components/ui/badge'
+import { Separator } from '@/components/ui/separator'
+import { format } from 'date-fns'
+import { ko } from 'date-fns/locale'
+import { Loader2 } from 'lucide-react'
+
+interface PriceAdjustmentData {
+  id: number
+  itemName?: string | null
+  adjustmentReflectionPoint?: string | null
+  majorApplicableRawMaterial?: string | null
+  adjustmentFormula?: string | null
+  rawMaterialPriceIndex?: string | null
+  referenceDate?: Date | string | null
+  comparisonDate?: Date | string | null
+  adjustmentRatio?: string | null
+  notes?: string | null
+  adjustmentConditions?: string | null
+  majorNonApplicableRawMaterial?: string | null
+  adjustmentPeriod?: string | null
+  contractorWriter?: string | null
+  adjustmentDate?: Date | string | null
+  nonApplicableReason?: string | null
+  createdAt: Date | string
+  updatedAt: Date | string
+}
+
+interface VendorPriceAdjustmentViewDialogProps {
+  open: boolean
+  onOpenChange: (open: boolean) => void
+  vendorName: string
+  priceAdjustmentResponse: boolean | null // 벤더가 응답한 연동제 적용 여부
+  biddingCompanyId: number
+}
+
+export function VendorPriceAdjustmentViewDialog({
+  open,
+  onOpenChange,
+  vendorName,
+  priceAdjustmentResponse,
+  biddingCompanyId,
+}: VendorPriceAdjustmentViewDialogProps) {
+  const [data, setData] = React.useState<PriceAdjustmentData | null>(null)
+  const [isLoading, setIsLoading] = React.useState(false)
+  const [error, setError] = React.useState<string | null>(null)
+
+  // 다이얼로그가 열릴 때 데이터 로드
+  React.useEffect(() => {
+    if (open && biddingCompanyId) {
+      loadPriceAdjustmentData()
+    }
+  }, [open, biddingCompanyId])
+
+  const loadPriceAdjustmentData = async () => {
+    setIsLoading(true)
+    setError(null)
+    try {
+      // 서버에서 연동제 폼 데이터 조회
+      const { getPriceAdjustmentFormByBiddingCompanyId } = await import('@/lib/bidding/detail/service')
+      const formData = await getPriceAdjustmentFormByBiddingCompanyId(biddingCompanyId)
+      setData(formData)
+    } catch (err) {
+      console.error('Failed to load price adjustment data:', err)
+      setError('연동제 정보를 불러오는데 실패했습니다.')
+    } finally {
+      setIsLoading(false)
+    }
+  }
+
+  // 날짜 포맷팅 헬퍼
+  const formatDateValue = (date: Date | string | null | undefined) => {
+    if (!date) return '-'
+    try {
+      const dateObj = typeof date === 'string' ? new Date(date) : date
+      return format(dateObj, 'yyyy-MM-dd', { locale: ko })
+    } catch {
+      return '-'
+    }
+  }
+
+  // 연동제 적용 여부 판단
+  const isApplied = priceAdjustmentResponse === true
+  const isNotApplied = priceAdjustmentResponse === false
+
+  return (
+    <Dialog open={open} onOpenChange={onOpenChange}>
+      <DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
+        <DialogHeader>
+          <DialogTitle className="flex items-center gap-2">
+            <span>하도급대금등 연동표</span>
+            <Badge variant="secondary">{vendorName}</Badge>
+            {isApplied && (
+              <Badge variant="default" className="bg-green-600 hover:bg-green-700">
+                연동제 적용
+              </Badge>
+            )}
+            {isNotApplied && (
+              <Badge variant="outline" className="border-red-500 text-red-600">
+                연동제 미적용
+              </Badge>
+            )}
+            {priceAdjustmentResponse === null && (
+              <Badge variant="outline">해당없음</Badge>
+            )}
+          </DialogTitle>
+          <DialogDescription>
+            협력업체가 제출한 연동제 적용 정보입니다.
+            {isApplied && " (연동제 적용)"}
+            {isNotApplied && " (연동제 미적용)"}
+          </DialogDescription>
+        </DialogHeader>
+
+        {isLoading ? (
+          <div className="flex items-center justify-center py-12">
+            <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
+            <span className="ml-2 text-muted-foreground">연동제 정보를 불러오는 중...</span>
+          </div>
+        ) : error ? (
+          <div className="py-8 text-center text-red-600">{error}</div>
+        ) : !data && priceAdjustmentResponse !== null ? (
+          <div className="py-8 text-center text-muted-foreground">연동제 상세 정보가 없습니다.</div>
+        ) : priceAdjustmentResponse === null ? (
+          <div className="py-8 text-center text-muted-foreground">해당 업체는 연동제 관련 응답을 하지 않았습니다.</div>
+        ) : (
+          <div className="space-y-6">
+            {/* 기본 정보 */}
+            <div>
+              <h3 className="text-sm font-medium text-gray-900 mb-3">기본 정보</h3>
+              <div className="grid grid-cols-2 gap-4">
+                <div>
+                  <label className="text-xs text-gray-500">물품등의 명칭</label>
+                  <p className="text-sm font-medium">{data?.itemName || '-'}</p>
+                </div>
+                <div>
+                  <label className="text-xs text-gray-500">연동제 적용 여부</label>
+                  <div className="mt-1">
+                    {isApplied && (
+                      <Badge variant="default" className="bg-green-600 hover:bg-green-700">
+                        예 (연동제 적용)
+                      </Badge>
+                    )}
+                    {isNotApplied && (
+                      <Badge variant="outline" className="border-red-500 text-red-600">
+                        아니오 (연동제 미적용)
+                      </Badge>
+                    )}
+                  </div>
+                </div>
+                {isApplied && (
+                  <div>
+                    <label className="text-xs text-gray-500">조정대금 반영시점</label>
+                    <p className="text-sm font-medium">{data?.adjustmentReflectionPoint || '-'}</p>
+                  </div>
+                )}
+              </div>
+            </div>
+
+            <Separator />
+
+            {/* 원재료 정보 */}
+            <div>
+              <h3 className="text-sm font-medium text-gray-900 mb-3">원재료 정보</h3>
+              <div className="space-y-4">
+                {isApplied && (
+                  <div>
+                    <label className="text-xs text-gray-500">연동대상 주요 원재료</label>
+                    <p className="text-sm font-medium whitespace-pre-wrap">
+                      {data?.majorApplicableRawMaterial || '-'}
+                    </p>
+                  </div>
+                )}
+                {isNotApplied && (
+                  <>
+                    <div>
+                      <label className="text-xs text-gray-500">연동 미적용 주요 원재료</label>
+                      <p className="text-sm font-medium whitespace-pre-wrap">
+                        {data?.majorNonApplicableRawMaterial || '-'}
+                      </p>
+                    </div>
+                    <div>
+                      <label className="text-xs text-gray-500">연동 미적용 사유</label>
+                      <p className="text-sm font-medium whitespace-pre-wrap">
+                        {data?.nonApplicableReason || '-'}
+                      </p>
+                    </div>
+                  </>
+                )}
+              </div>
+            </div>
+
+            {isApplied && data && (
+              <>
+                <Separator />
+
+                {/* 연동 공식 및 지표 */}
+                <div>
+                  <h3 className="text-sm font-medium text-gray-900 mb-3">연동 공식 및 지표</h3>
+                  <div className="space-y-4">
+                    <div>
+                      <label className="text-xs text-gray-500">하도급대금등 연동 산식</label>
+                      <div className="p-3 bg-gray-50 rounded-md">
+                        <p className="text-sm font-mono whitespace-pre-wrap">
+                          {data.adjustmentFormula || '-'}
+                        </p>
+                      </div>
+                    </div>
+                    <div>
+                      <label className="text-xs text-gray-500">원재료 가격 기준지표</label>
+                      <p className="text-sm font-medium whitespace-pre-wrap">
+                        {data.rawMaterialPriceIndex || '-'}
+                      </p>
+                    </div>
+                    <div className="grid grid-cols-2 gap-4">
+                      <div>
+                        <label className="text-xs text-gray-500">원재료 기준 가격의 변동률 산정을 위한 기준시점</label>
+                        <p className="text-sm font-medium">{data.referenceDate ? formatDateValue(data.referenceDate) : '-'}</p>
+                      </div>
+                      <div>
+                        <label className="text-xs text-gray-500">원재료 기준 가격의 변동률 산정을 위한 비교시점</label>
+                        <p className="text-sm font-medium">{data.comparisonDate ? formatDateValue(data.comparisonDate) : '-'}</p>
+                      </div>
+                    </div>
+                    {data.adjustmentRatio && (
+                      <div>
+                        <label className="text-xs text-gray-500">반영비율</label>
+                        <p className="text-sm font-medium">
+                          {data.adjustmentRatio}%
+                        </p>
+                      </div>
+                    )}
+                  </div>
+                </div>
+
+                <Separator />
+
+                {/* 조정 조건 및 기타 */}
+                <div>
+                  <h3 className="text-sm font-medium text-gray-900 mb-3">조정 조건 및 기타</h3>
+                  <div className="space-y-4">
+                    <div>
+                      <label className="text-xs text-gray-500">조정요건</label>
+                      <p className="text-sm font-medium whitespace-pre-wrap">
+                        {data.adjustmentConditions || '-'}
+                      </p>
+                    </div>
+                    <div className="grid grid-cols-2 gap-4">
+                      <div>
+                        <label className="text-xs text-gray-500">조정주기</label>
+                        <p className="text-sm font-medium">{data.adjustmentPeriod || '-'}</p>
+                      </div>
+                      <div>
+                        <label className="text-xs text-gray-500">조정일</label>
+                        <p className="text-sm font-medium">{data.adjustmentDate ? formatDateValue(data.adjustmentDate) : '-'}</p>
+                      </div>
+                    </div>
+                    <div>
+                      <label className="text-xs text-gray-500">수탁기업(협력사)작성자</label>
+                      <p className="text-sm font-medium">{data.contractorWriter || '-'}</p>
+                    </div>
+                    {data.notes && (
+                      <div>
+                        <label className="text-xs text-gray-500">기타사항</label>
+                        <p className="text-sm font-medium whitespace-pre-wrap">
+                          {data.notes}
+                        </p>
+                      </div>
+                    )}
+                  </div>
+                </div>
+              </>
+            )}
+
+            {isNotApplied && data && (
+              <>
+                <Separator />
+                <div>
+                  <h3 className="text-sm font-medium text-gray-900 mb-3">작성자 정보</h3>
+                  <div>
+                    <label className="text-xs text-gray-500">수탁기업(협력사) 작성자</label>
+                    <p className="text-sm font-medium">{data.contractorWriter || '-'}</p>
+                  </div>
+                </div>
+              </>
+            )}
+
+            {data && (
+              <>
+                <Separator />
+
+                {/* 메타 정보 */}
+                <div className="text-xs text-gray-500 space-y-1">
+                  <p>작성일: {formatDateValue(data.createdAt)}</p>
+                  <p>수정일: {formatDateValue(data.updatedAt)}</p>
+                </div>
+              </>
+            )}
+
+            <Separator />
+
+            {/* 참고 경고문 */}
+            <div className="text-xs text-red-600 space-y-2 bg-red-50 p-3 rounded-md border border-red-200">
+              <p className="font-medium">※ 참고사항</p>
+              <div className="space-y-1">
+                <p>• 납품대금의 10% 이상을 차지하는 주요 원재료가 있는 경우 모든 주요 원재료에 대해서 적용 또는 미적용에 대한 연동표를 작성해야 한다.</p>
+                <p>• 납품대급연동표를 허위로 작성하거나 근거자료를 허위로 제출할 경우 본 계약이 체결되지 않을 수 있으며, 본 계약이 체결되었더라도 계약의 전부 또는 일부를 해제 또는 해지할 수 있다.</p>
+              </div>
+            </div>
+          </div>
+        )}
+      </DialogContent>
+    </Dialog>
+  )
+}
+
diff --git a/lib/bidding/handlers.ts b/lib/bidding/handlers.ts
index 03a85bb6..b422118d 100644
--- a/lib/bidding/handlers.ts
+++ b/lib/bidding/handlers.ts
@@ -9,6 +9,96 @@
 
 import { debugLog, debugError, debugSuccess } from '@/lib/debug-utils';
 
+/**
+ * 결재 완료 시점을 기준으로 입찰서 제출기간 계산 및 업데이트
+ * 
+ * 계산 로직:
+ * - baseDate = 결재완료일 날짜만 (00:00:00)
+ * - 시작일 = baseDate + submissionStartOffset일 + submissionStartDate의 시:분
+ * - 마감일 = 시작일(날짜만) + submissionDurationDays일 + submissionEndDate의 시:분
+ */
+async function calculateAndUpdateSubmissionDates(biddingId: number) {
+  const { default: db } = await import('@/db/db');
+  const { biddings } = await import('@/db/schema');
+  const { eq } = await import('drizzle-orm');
+
+  // 현재 입찰 정보 조회
+  const biddingInfo = await db
+    .select({
+      submissionStartOffset: biddings.submissionStartOffset,
+      submissionDurationDays: biddings.submissionDurationDays,
+      submissionStartDate: biddings.submissionStartDate, // 시간만 저장된 상태 (1970-01-01 HH:MM:00)
+      submissionEndDate: biddings.submissionEndDate,     // 시간만 저장된 상태 (1970-01-01 HH:MM:00)
+    })
+    .from(biddings)
+    .where(eq(biddings.id, biddingId))
+    .limit(1);
+
+  if (biddingInfo.length === 0) {
+    debugError('[calculateAndUpdateSubmissionDates] 입찰 정보를 찾을 수 없음', { biddingId });
+    throw new Error('입찰 정보를 찾을 수 없습니다.');
+  }
+
+  const { submissionStartOffset, submissionDurationDays, submissionStartDate, submissionEndDate } = biddingInfo[0];
+
+  // 필수 값 검증
+  if (submissionStartOffset === null || submissionDurationDays === null) {
+    debugError('[calculateAndUpdateSubmissionDates] 오프셋 값이 설정되지 않음', { submissionStartOffset, submissionDurationDays });
+    throw new Error('입찰서 제출기간 오프셋이 설정되지 않았습니다.');
+  }
+
+  // 시간 추출 (기본값: 시작 09:00, 마감 18:00)
+  const startTime = submissionStartDate 
+    ? { hours: submissionStartDate.getUTCHours(), minutes: submissionStartDate.getUTCMinutes() }
+    : { hours: 9, minutes: 0 };
+  const endTime = submissionEndDate
+    ? { hours: submissionEndDate.getUTCHours(), minutes: submissionEndDate.getUTCMinutes() }
+    : { hours: 18, minutes: 0 };
+
+  // 1. baseDate = 결재완료일 날짜만 (KST 기준 00:00:00)
+  const now = new Date();
+  const baseDate = new Date(now);
+  // KST 기준으로 날짜만 추출 (시간은 00:00:00)
+  baseDate.setHours(0, 0, 0, 0);
+
+  // 2. 시작일 = baseDate + offset일 + 시작시간
+  const calculatedStartDate = new Date(baseDate);
+  calculatedStartDate.setDate(calculatedStartDate.getDate() + submissionStartOffset);
+  calculatedStartDate.setHours(startTime.hours, startTime.minutes, 0, 0);
+
+  // 3. 마감일 = 시작일(날짜만) + duration일 + 마감시간
+  const calculatedEndDate = new Date(calculatedStartDate);
+  calculatedEndDate.setHours(0, 0, 0, 0); // 시작일의 날짜만
+  calculatedEndDate.setDate(calculatedEndDate.getDate() + submissionDurationDays);
+  calculatedEndDate.setHours(endTime.hours, endTime.minutes, 0, 0);
+
+  debugLog('[calculateAndUpdateSubmissionDates] 입찰서 제출기간 계산 완료', {
+    biddingId,
+    baseDate: baseDate.toISOString(),
+    submissionStartOffset,
+    submissionDurationDays,
+    startTime,
+    endTime,
+    calculatedStartDate: calculatedStartDate.toISOString(),
+    calculatedEndDate: calculatedEndDate.toISOString(),
+  });
+
+  // DB 업데이트
+  await db
+    .update(biddings)
+    .set({
+      submissionStartDate: calculatedStartDate,
+      submissionEndDate: calculatedEndDate,
+      updatedAt: new Date(),
+    })
+    .where(eq(biddings.id, biddingId));
+
+  return {
+    startDate: calculatedStartDate,
+    endDate: calculatedEndDate,
+  };
+}
+
 /**
  * 입찰초대 핸들러 (결재 승인 후 실행됨)
  *
@@ -52,7 +142,7 @@ export async function requestBiddingInvitationInternal(payload: {
   try {
     // 1. 기본계약 발송
     const { sendBiddingBasicContracts } = await import('@/lib/bidding/pre-quote/service');
-
+    
     const vendorDataForContract = payload.vendors.map(vendor => ({
       vendorId: vendor.vendorId,
       vendorName: vendor.vendorName,
@@ -86,7 +176,7 @@ export async function requestBiddingInvitationInternal(payload: {
 
     debugLog('[BiddingInvitationHandler] 기본계약 발송 완료');
 
-    // 2. 입찰 등록 진행 (상태를 bidding_opened로 변경)
+    // 2. 입찰 등록 진행 (상태를 bidding_opened로 변경, 입찰서 제출기간 자동 계산)
     const { registerBidding } = await import('@/lib/bidding/detail/service');
 
     const registerResult = await registerBidding(payload.biddingId, payload.currentUserId.toString());
diff --git a/lib/bidding/receive/biddings-receive-columns.tsx b/lib/bidding/receive/biddings-receive-columns.tsx
index 9650574a..f2e2df17 100644
--- a/lib/bidding/receive/biddings-receive-columns.tsx
+++ b/lib/bidding/receive/biddings-receive-columns.tsx
@@ -1,404 +1,404 @@
-"use client"
-
-import * as React from "react"
-import { type ColumnDef } from "@tanstack/react-table"
-import { Badge } from "@/components/ui/badge"
-import { Button } from "@/components/ui/button"
-import { Checkbox } from "@/components/ui/checkbox"
-import {
-  Eye, Calendar, Users, CheckCircle, XCircle, Clock, AlertTriangle
-} from "lucide-react"
-import {
-  Tooltip,
-  TooltipContent,
-  TooltipProvider,
-  TooltipTrigger,
-} from "@/components/ui/tooltip"
-import {
-  DropdownMenu,
-  DropdownMenuContent,
-  DropdownMenuItem,
-  DropdownMenuSeparator,
-  DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
-import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
-import {
-  biddingStatusLabels,
-  contractTypeLabels,
-} from "@/db/schema"
-import { formatDate } from "@/lib/utils"
-import { DataTableRowAction } from "@/types/table"
-
-type BiddingReceiveItem = {
-  id: number
-  biddingNumber: string
-  originalBiddingNumber: string | null
-  title: string
-  status: string
-  contractType: string
-  prNumber: string | null
-  submissionStartDate: Date | null
-  submissionEndDate: Date | null
-  bidPicName: string | null
-  supplyPicName: string | null
-  createdBy: string | null
-  createdAt: Date | null
-  updatedAt: Date | null
-
-  // 참여 현황
-  participantExpected: number
-  participantParticipated: number
-  participantDeclined: number
-  participantPending: number
-
-  // 개찰 정보
-  openedAt: Date | null
-  openedBy: string | null
-}
-
-interface GetColumnsProps {
-  setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<BiddingReceiveItem> | null>>
-  onParticipantClick?: (biddingId: number, participantType: 'expected' | 'participated' | 'declined' | 'pending') => void
-}
-
-// 상태별 배지 색상
-const getStatusBadgeVariant = (status: string) => {
-  switch (status) {
-    case 'received_quotation':
-      return 'secondary'
-    case 'bidding_opened':
-      return 'default'
-    case 'bidding_closed':
-      return 'outline'
-    default:
-      return 'outline'
-  }
-}
-
-// 금액 포맷팅
-const formatCurrency = (amount: string | number | null, currency = 'KRW') => {
-  if (!amount) return '-'
-
-  const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount
-  if (isNaN(numAmount)) return '-'
-
-  return new Intl.NumberFormat('ko-KR', {
-    style: 'currency',
-    currency: currency,
-    minimumFractionDigits: 0,
-    maximumFractionDigits: 0,
-  }).format(numAmount)
-}
-
-export function getBiddingsReceiveColumns({ setRowAction, onParticipantClick }: GetColumnsProps): ColumnDef<BiddingReceiveItem>[] {
-
-  return [
-    // ░░░ 선택 ░░░
-    {
-      id: "select",
-      header: "",
-      cell: ({ row }) => (
-        <Checkbox
-          checked={row.getIsSelected()}
-          onCheckedChange={(value) => {
-            // single select 모드에서는 다른 행들의 선택을 해제
-            row.toggleSelected(!!value)
-          }}
-          aria-label="행 선택"
-        />
-      ),
-      size: 50,
-      enableSorting: false,
-      enableHiding: false,
-    },
-
-    // ░░░ 입찰번호 ░░░
-    {
-      accessorKey: "biddingNumber",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="입찰번호" />,
-      cell: ({ row }) => (
-        <div className="font-mono text-sm">
-          {row.original.biddingNumber}
-        </div>
-      ),
-      size: 120,
-      meta: { excelHeader: "입찰번호" },
-    },
-
-    // ░░░ 입찰명 ░░░
-    {
-      accessorKey: "title",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="입찰명" />,
-      cell: ({ row }) => (
-        <div className="truncate max-w-[200px]" title={row.original.title}>
-          {/* <Button
-            variant="link"
-            className="p-0 h-auto text-left justify-start font-bold underline"
-            onClick={() => setRowAction({ row, type: "view" })}
-          >
-            <div className="whitespace-pre-line">
-              {row.original.title}
-            </div>
-          </Button> */}
-          {row.original.title}
-        </div>
-      ),
-      size: 200,
-      meta: { excelHeader: "입찰명" },
-    },
-
-    // ░░░ 원입찰번호 ░░░
-    {
-      accessorKey: "originalBiddingNumber",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="원입찰번호" />,
-      cell: ({ row }) => (
-        <div className="font-mono text-sm">
-          {row.original.originalBiddingNumber || '-'}
-        </div>
-      ),
-      size: 120,
-      meta: { excelHeader: "원입찰번호" },
-    },
-
-    // ░░░ 진행상태 ░░░
-    {
-      accessorKey: "status",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="진행상태" />,
-      cell: ({ row }) => (
-        <Badge variant={getStatusBadgeVariant(row.original.status)}>
-          {biddingStatusLabels[row.original.status]}
-        </Badge>
-      ),
-      size: 120,
-      meta: { excelHeader: "진행상태" },
-    },
-
-    // ░░░ 계약구분 ░░░
-    {
-      accessorKey: "contractType",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="계약구분" />,
-      cell: ({ row }) => (
-        <Badge variant="outline">
-          {contractTypeLabels[row.original.contractType]}
-        </Badge>
-      ),
-      size: 100,
-      meta: { excelHeader: "계약구분" },
-    },
-
-    // ░░░ 입찰서제출기간 ░░░
-    {
-      id: "submissionPeriod",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="입찰서제출기간" />,
-      cell: ({ row }) => {
-        const startDate = row.original.submissionStartDate
-        const endDate = row.original.submissionEndDate
-        
-        if (!startDate || !endDate) return <span className="text-muted-foreground">-</span>
-        
-        const startObj = new Date(startDate)
-        const endObj = new Date(endDate)
-
-        // UI 표시용 KST 변환
-        const formatKst = (d: Date) => new Date(d.getTime() + 9 * 60 * 60 * 1000).toISOString().slice(0, 16).replace('T', ' ')
-        
-        return (
-          <div className="text-xs">
-            <div>
-              {formatKst(startObj)} ~ {formatKst(endObj)}
-            </div>
-          </div>
-        )
-      },
-      size: 140,
-      meta: { excelHeader: "입찰서제출기간" },
-    },
-
-    // ░░░ P/R번호 ░░░
-    {
-      accessorKey: "prNumber",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="P/R번호" />,
-      cell: ({ row }) => (
-        <span className="font-mono text-sm">{row.original.prNumber || '-'}</span>
-      ),
-      size: 100,
-      meta: { excelHeader: "P/R번호" },
-    },
-
-    // ░░░ 입찰담당자 ░░░
-    {
-      accessorKey: "bidPicName",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="입찰담당자" />,
-      cell: ({ row }) => {
-        const bidPic = row.original.bidPicName
-        const supplyPic = row.original.supplyPicName
-
-        const displayName = bidPic || supplyPic || "-"
-        return <span className="text-sm">{displayName}</span>
-      },
-      size: 100,
-      meta: { excelHeader: "입찰담당자" },
-    },
-
-    // ░░░ 참여예정협력사 ░░░
-    {
-      id: "participantExpected",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="참여예정협력사" />,
-      cell: ({ row }) => (
-        <Button
-          variant="ghost"
-          size="sm"
-          className="h-auto p-1 hover:bg-blue-50"
-          onClick={() => onParticipantClick?.(row.original.id, 'expected')}
-          disabled={row.original.participantExpected === 0}
-        >
-          <div className="flex items-center gap-1">
-            <Users className="h-4 w-4 text-blue-500" />
-            <span className="text-sm font-medium">{row.original.participantExpected}</span>
-          </div>
-        </Button>
-      ),
-      size: 100,
-      meta: { excelHeader: "참여예정협력사" },
-    },
-
-    // ░░░ 참여협력사 ░░░
-    {
-      id: "participantParticipated",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="참여협력사" />,
-      cell: ({ row }) => (
-        <Button
-          variant="ghost"
-          size="sm"
-          className="h-auto p-1 hover:bg-green-50"
-          onClick={() => onParticipantClick?.(row.original.id, 'participated')}
-          disabled={row.original.participantParticipated === 0}
-        >
-          <div className="flex items-center gap-1">
-            <CheckCircle className="h-4 w-4 text-green-500" />
-            <span className="text-sm font-medium">{row.original.participantParticipated}</span>
-          </div>
-        </Button>
-      ),
-      size: 100,
-      meta: { excelHeader: "참여협력사" },
-    },
-
-    // ░░░ 포기협력사 ░░░
-    {
-      id: "participantDeclined",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="포기협력사" />,
-      cell: ({ row }) => (
-        <Button
-          variant="ghost"
-          size="sm"
-          className="h-auto p-1 hover:bg-red-50"
-          onClick={() => onParticipantClick?.(row.original.id, 'declined')}
-          disabled={row.original.participantDeclined === 0}
-        >
-          <div className="flex items-center gap-1">
-            <XCircle className="h-4 w-4 text-red-500" />
-            <span className="text-sm font-medium">{row.original.participantDeclined}</span>
-          </div>
-        </Button>
-      ),
-      size: 100,
-      meta: { excelHeader: "포기협력사" },
-    },
-
-    // ░░░ 미제출협력사 ░░░
-    {
-      id: "participantPending",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="미제출협력사" />,
-      cell: ({ row }) => (
-        <Button
-          variant="ghost"
-          size="sm"
-          className="h-auto p-1 hover:bg-yellow-50"
-          onClick={() => onParticipantClick?.(row.original.id, 'pending')}
-          disabled={row.original.participantPending === 0}
-        >
-          <div className="flex items-center gap-1">
-            <Clock className="h-4 w-4 text-yellow-500" />
-            <span className="text-sm font-medium">{row.original.participantPending}</span>
-          </div>
-        </Button>
-      ),
-      size: 100,
-      meta: { excelHeader: "미제출협력사" },
-    },
-
-    // ░░░ 개찰자명 ░░░
-    {
-      id: "openedBy",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="개찰자명" />,
-      cell: ({ row }) => {
-        const openedBy = row.original.openedBy
-        return <span className="text-sm">{openedBy || '-'}</span>
-      },
-      size: 100,
-      meta: { excelHeader: "개찰자명" },
-    },
-
-    // ░░░ 개찰일 ░░░
-    {
-      id: "openedAt",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="개찰일" />,
-      cell: ({ row }) => {
-        const openedAt = row.original.openedAt
-        return <span className="text-sm">{openedAt ? formatDate(openedAt, "KR") : '-'}</span>
-      },
-      size: 100,
-      meta: { excelHeader: "개찰일" },
-    },
-
-    // ░░░ 등록자 ░░░
-    {
-      accessorKey: "createdBy",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="등록자" />,
-      cell: ({ row }) => (
-        <span className="text-sm">{row.original.createdBy || '-'}</span>
-      ),
-      size: 100,
-      meta: { excelHeader: "등록자" },
-    },
-
-    // ░░░ 등록일시 ░░░
-    {
-      accessorKey: "createdAt",
-      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="등록일시" />,
-      cell: ({ row }) => (
-        <span className="text-sm">{row.original.createdAt ? formatDate(row.original.createdAt, "KR") : '-'}</span>
-      ),
-      size: 100,
-      meta: { excelHeader: "등록일시" },
-    },
-
-    // ═══════════════════════════════════════════════════════════════
-    // 액션
-    // ═══════════════════════════════════════════════════════════════
-    // {
-    //   id: "actions",
-    //   header: "액션",
-    //   cell: ({ row }) => (
-    //     <DropdownMenu>
-    //       <DropdownMenuTrigger asChild>
-    //         <Button variant="ghost" className="h-8 w-8 p-0">
-    //           <span className="sr-only">메뉴 열기</span>
-    //           <AlertTriangle className="h-4 w-4" />
-    //         </Button>
-    //       </DropdownMenuTrigger>
-    //       <DropdownMenuContent align="end">
-    //         <DropdownMenuItem onClick={() => setRowAction({ row, type: "view" })}>
-    //           <Eye className="mr-2 h-4 w-4" />
-    //           상세보기
-    //         </DropdownMenuItem>
-    //       </DropdownMenuContent>
-    //     </DropdownMenu>
-    //   ),
-    //   size: 50,
-    //   enableSorting: false,
-    //   enableHiding: false,
-    // },
-  ]
-}
+"use client"
+
+import * as React from "react"
+import { type ColumnDef } from "@tanstack/react-table"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Checkbox } from "@/components/ui/checkbox"
+import {
+  Eye, Calendar, Users, CheckCircle, XCircle, Clock, AlertTriangle
+} from "lucide-react"
+import {
+  Tooltip,
+  TooltipContent,
+  TooltipProvider,
+  TooltipTrigger,
+} from "@/components/ui/tooltip"
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuSeparator,
+  DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+import {
+  biddingStatusLabels,
+  contractTypeLabels,
+} from "@/db/schema"
+import { formatDate } from "@/lib/utils"
+import { DataTableRowAction } from "@/types/table"
+
+type BiddingReceiveItem = {
+  id: number
+  biddingNumber: string
+  originalBiddingNumber: string | null
+  title: string
+  status: string
+  contractType: string
+  prNumber: string | null
+  submissionStartDate: Date | null
+  submissionEndDate: Date | null
+  bidPicName: string | null
+  supplyPicName: string | null
+  createdBy: string | null
+  createdAt: Date | null
+  updatedAt: Date | null
+
+  // 참여 현황
+  participantExpected: number
+  participantParticipated: number
+  participantDeclined: number
+  participantPending: number
+
+  // 개찰 정보
+  openedAt: Date | null
+  openedBy: string | null
+}
+
+interface GetColumnsProps {
+  setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<BiddingReceiveItem> | null>>
+  onParticipantClick?: (biddingId: number, participantType: 'expected' | 'participated' | 'declined' | 'pending') => void
+}
+
+// 상태별 배지 색상
+const getStatusBadgeVariant = (status: string) => {
+  switch (status) {
+    case 'received_quotation':
+      return 'secondary'
+    case 'bidding_opened':
+      return 'default'
+    case 'bidding_closed':
+      return 'outline'
+    default:
+      return 'outline'
+  }
+}
+
+// 금액 포맷팅
+const formatCurrency = (amount: string | number | null, currency = 'KRW') => {
+  if (!amount) return '-'
+
+  const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount
+  if (isNaN(numAmount)) return '-'
+
+  return new Intl.NumberFormat('ko-KR', {
+    style: 'currency',
+    currency: currency,
+    minimumFractionDigits: 0,
+    maximumFractionDigits: 0,
+  }).format(numAmount)
+}
+
+export function getBiddingsReceiveColumns({ setRowAction, onParticipantClick }: GetColumnsProps): ColumnDef<BiddingReceiveItem>[] {
+
+  return [
+    // ░░░ 선택 ░░░
+    {
+      id: "select",
+      header: "",
+      cell: ({ row }) => (
+        <Checkbox
+          checked={row.getIsSelected()}
+          onCheckedChange={(value) => {
+            // single select 모드에서는 다른 행들의 선택을 해제
+            row.toggleSelected(!!value)
+          }}
+          aria-label="행 선택"
+        />
+      ),
+      size: 50,
+      enableSorting: false,
+      enableHiding: false,
+    },
+
+    // ░░░ 입찰번호 ░░░
+    {
+      accessorKey: "biddingNumber",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="입찰번호" />,
+      cell: ({ row }) => (
+        <div className="font-mono text-sm">
+          {row.original.biddingNumber}
+        </div>
+      ),
+      size: 120,
+      meta: { excelHeader: "입찰번호" },
+    },
+
+    // ░░░ 입찰명 ░░░
+    {
+      accessorKey: "title",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="입찰명" />,
+      cell: ({ row }) => (
+        <div className="truncate max-w-[200px]" title={row.original.title}>
+          {/* <Button
+            variant="link"
+            className="p-0 h-auto text-left justify-start font-bold underline"
+            onClick={() => setRowAction({ row, type: "view" })}
+          >
+            <div className="whitespace-pre-line">
+              {row.original.title}
+            </div>
+          </Button> */}
+          {row.original.title}
+        </div>
+      ),
+      size: 200,
+      meta: { excelHeader: "입찰명" },
+    },
+
+    // ░░░ 원입찰번호 ░░░
+    {
+      accessorKey: "originalBiddingNumber",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="원입찰번호" />,
+      cell: ({ row }) => (
+        <div className="font-mono text-sm">
+          {row.original.originalBiddingNumber || '-'}
+        </div>
+      ),
+      size: 120,
+      meta: { excelHeader: "원입찰번호" },
+    },
+
+    // ░░░ 진행상태 ░░░
+    {
+      accessorKey: "status",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="진행상태" />,
+      cell: ({ row }) => (
+        <Badge variant={getStatusBadgeVariant(row.original.status)}>
+          {biddingStatusLabels[row.original.status]}
+        </Badge>
+      ),
+      size: 120,
+      meta: { excelHeader: "진행상태" },
+    },
+
+    // ░░░ 계약구분 ░░░
+    {
+      accessorKey: "contractType",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="계약구분" />,
+      cell: ({ row }) => (
+        <Badge variant="outline">
+          {contractTypeLabels[row.original.contractType]}
+        </Badge>
+      ),
+      size: 100,
+      meta: { excelHeader: "계약구분" },
+    },
+
+    // ░░░ 입찰서제출기간 ░░░
+    {
+      id: "submissionPeriod",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="입찰서제출기간" />,
+      cell: ({ row }) => {
+        const startDate = row.original.submissionStartDate
+        const endDate = row.original.submissionEndDate
+        
+        if (!startDate || !endDate) return <span className="text-muted-foreground">-</span>
+        
+        const startObj = new Date(startDate)
+        const endObj = new Date(endDate)
+
+        // UI 표시용 KST 변환
+        const formatKst = (d: Date) => new Date(d.getTime() + 9 * 60 * 60 * 1000).toISOString().slice(0, 16).replace('T', ' ')
+        
+        return (
+          <div className="text-xs">
+            <div>
+              {formatKst(startObj)} ~ {formatKst(endObj)}
+            </div>
+          </div>
+        )
+      },
+      size: 140,
+      meta: { excelHeader: "입찰서제출기간" },
+    },
+
+    // ░░░ P/R번호 ░░░
+    {
+      accessorKey: "prNumber",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="P/R번호" />,
+      cell: ({ row }) => (
+        <span className="font-mono text-sm">{row.original.prNumber || '-'}</span>
+      ),
+      size: 100,
+      meta: { excelHeader: "P/R번호" },
+    },
+
+    // ░░░ 입찰담당자 ░░░
+    {
+      accessorKey: "bidPicName",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="입찰담당자" />,
+      cell: ({ row }) => {
+        const bidPic = row.original.bidPicName
+        const supplyPic = row.original.supplyPicName
+
+        const displayName = bidPic || supplyPic || "-"
+        return <span className="text-sm">{displayName}</span>
+      },
+      size: 100,
+      meta: { excelHeader: "입찰담당자" },
+    },
+
+    // ░░░ 참여예정협력사 ░░░
+    {
+      id: "participantExpected",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="참여예정협력사" />,
+      cell: ({ row }) => (
+        <Button
+          variant="ghost"
+          size="sm"
+          className="h-auto p-1 hover:bg-blue-50"
+          onClick={() => onParticipantClick?.(row.original.id, 'expected')}
+          disabled={row.original.participantExpected === 0}
+        >
+          <div className="flex items-center gap-1">
+            <Users className="h-4 w-4 text-blue-500" />
+            <span className="text-sm font-medium">{row.original.participantExpected}</span>
+          </div>
+        </Button>
+      ),
+      size: 100,
+      meta: { excelHeader: "참여예정협력사" },
+    },
+
+    // ░░░ 참여협력사 ░░░
+    {
+      id: "participantParticipated",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="참여협력사" />,
+      cell: ({ row }) => (
+        <Button
+          variant="ghost"
+          size="sm"
+          className="h-auto p-1 hover:bg-green-50"
+          onClick={() => onParticipantClick?.(row.original.id, 'participated')}
+          disabled={row.original.participantParticipated === 0}
+        >
+          <div className="flex items-center gap-1">
+            <CheckCircle className="h-4 w-4 text-green-500" />
+            <span className="text-sm font-medium">{row.original.participantParticipated}</span>
+          </div>
+        </Button>
+      ),
+      size: 100,
+      meta: { excelHeader: "참여협력사" },
+    },
+
+    // ░░░ 포기협력사 ░░░
+    {
+      id: "participantDeclined",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="포기협력사" />,
+      cell: ({ row }) => (
+        <Button
+          variant="ghost"
+          size="sm"
+          className="h-auto p-1 hover:bg-red-50"
+          onClick={() => onParticipantClick?.(row.original.id, 'declined')}
+          disabled={row.original.participantDeclined === 0}
+        >
+          <div className="flex items-center gap-1">
+            <XCircle className="h-4 w-4 text-red-500" />
+            <span className="text-sm font-medium">{row.original.participantDeclined}</span>
+          </div>
+        </Button>
+      ),
+      size: 100,
+      meta: { excelHeader: "포기협력사" },
+    },
+
+    // ░░░ 미제출협력사 ░░░
+    {
+      id: "participantPending",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="미제출협력사" />,
+      cell: ({ row }) => (
+        <Button
+          variant="ghost"
+          size="sm"
+          className="h-auto p-1 hover:bg-yellow-50"
+          onClick={() => onParticipantClick?.(row.original.id, 'pending')}
+          disabled={row.original.participantPending === 0}
+        >
+          <div className="flex items-center gap-1">
+            <Clock className="h-4 w-4 text-yellow-500" />
+            <span className="text-sm font-medium">{row.original.participantPending}</span>
+          </div>
+        </Button>
+      ),
+      size: 100,
+      meta: { excelHeader: "미제출협력사" },
+    },
+
+    // ░░░ 개찰자명 ░░░
+    {
+      id: "openedBy",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="개찰자명" />,
+      cell: ({ row }) => {
+        const openedBy = row.original.openedBy
+        return <span className="text-sm">{openedBy || '-'}</span>
+      },
+      size: 100,
+      meta: { excelHeader: "개찰자명" },
+    },
+
+    // ░░░ 개찰일 ░░░
+    {
+      id: "openedAt",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="개찰일" />,
+      cell: ({ row }) => {
+        const openedAt = row.original.openedAt
+        return <span className="text-sm">{openedAt ? formatDate(openedAt, "KR") : '-'}</span>
+      },
+      size: 100,
+      meta: { excelHeader: "개찰일" },
+    },
+
+    // ░░░ 등록자 ░░░
+    {
+      accessorKey: "createdBy",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="등록자" />,
+      cell: ({ row }) => (
+        <span className="text-sm">{row.original.createdBy || '-'}</span>
+      ),
+      size: 100,
+      meta: { excelHeader: "등록자" },
+    },
+
+    // ░░░ 등록일시 ░░░
+    {
+      accessorKey: "createdAt",
+      header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="등록일시" />,
+      cell: ({ row }) => (
+        <span className="text-sm">{row.original.createdAt ? formatDate(row.original.createdAt, "KR") : '-'}</span>
+      ),
+      size: 100,
+      meta: { excelHeader: "등록일시" },
+    },
+
+    // ═══════════════════════════════════════════════════════════════
+    // 액션
+    // ═══════════════════════════════════════════════════════════════
+    // {
+    //   id: "actions",
+    //   header: "액션",
+    //   cell: ({ row }) => (
+    //     <DropdownMenu>
+    //       <DropdownMenuTrigger asChild>
+    //         <Button variant="ghost" className="h-8 w-8 p-0">
+    //           <span className="sr-only">메뉴 열기</span>
+    //           <AlertTriangle className="h-4 w-4" />
+    //         </Button>
+    //       </DropdownMenuTrigger>
+    //       <DropdownMenuContent align="end">
+    //         <DropdownMenuItem onClick={() => setRowAction({ row, type: "view" })}>
+    //           <Eye className="mr-2 h-4 w-4" />
+    //           상세보기
+    //         </DropdownMenuItem>
+    //       </DropdownMenuContent>
+    //     </DropdownMenu>
+    //   ),
+    //   size: 50,
+    //   enableSorting: false,
+    //   enableHiding: false,
+    // },
+  ]
+}
diff --git a/lib/bidding/receive/biddings-receive-table.tsx b/lib/bidding/receive/biddings-receive-table.tsx
index 2b141d5e..6a48fa79 100644
--- a/lib/bidding/receive/biddings-receive-table.tsx
+++ b/lib/bidding/receive/biddings-receive-table.tsx
@@ -1,296 +1,297 @@
-"use client"
-
-import * as React from "react"
-import { useRouter } from "next/navigation"
-import { useSession } from "next-auth/react"
-import type {
-    DataTableAdvancedFilterField,
-    DataTableFilterField,
-    DataTableRowAction,
-} from "@/types/table"
-import { Button } from "@/components/ui/button"
-import { Loader2 } from "lucide-react"
-import { toast } from "sonner"
-
-import { useDataTable } from "@/hooks/use-data-table"
-import { DataTable } from "@/components/data-table/data-table"
-import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar"
-import { getBiddingsReceiveColumns } from "./biddings-receive-columns"
-import { getBiddingsForReceive } from "@/lib/bidding/service"
-import {
-    biddingStatusLabels,
-    contractTypeLabels,
-} from "@/db/schema"
-// import { SpecificationMeetingDialog, PrDocumentsDialog } from "../list/bidding-detail-dialogs"
-import { openBiddingAction } from "@/lib/bidding/actions"
-import { BiddingParticipantsDialog } from "@/components/bidding/receive/bidding-participants-dialog"
-import { getAllBiddingCompanies } from "@/lib/bidding/detail/service"
-
-type BiddingReceiveItem = {
-  id: number
-  biddingNumber: string
-  originalBiddingNumber: string | null
-  title: string
-  status: string
-  contractType: string
-  prNumber: string | null
-  submissionStartDate: Date | null
-  submissionEndDate: Date | null
-  bidPicName: string | null
-  supplyPicName: string | null
-  createdBy: string | null
-  createdAt: Date | null
-  updatedAt: Date | null
-
-  // 참여 현황
-  participantExpected: number
-  participantParticipated: number
-  participantDeclined: number
-  participantPending: number
-  participantFinalSubmitted: number
-
-  // 개찰 정보
-  openedAt: Date | null
-  openedBy: string | null
-}
-
-interface BiddingsReceiveTableProps {
-    promises: Promise<
-        [
-            Awaited<ReturnType<typeof getBiddingsForReceive>>
-        ]
-    >
-}
-
-export function BiddingsReceiveTable({ promises }: BiddingsReceiveTableProps) {
-    const [biddingsResult] = React.use(promises)
-
-    // biddingsResult에서 data와 pageCount 추출
-    const { data, pageCount } = biddingsResult
-
-    const [isCompact, setIsCompact] = React.useState<boolean>(false)
-    // const [specMeetingDialogOpen, setSpecMeetingDialogOpen] = React.useState(false)
-    // const [prDocumentsDialogOpen, setPrDocumentsDialogOpen] = React.useState(false)
-    const [selectedBidding, setSelectedBidding] = React.useState<BiddingReceiveItem | null>(null)
-
-    const [rowAction, setRowAction] = React.useState<DataTableRowAction<BiddingReceiveItem> | null>(null)
-    const [isOpeningBidding, setIsOpeningBidding] = React.useState(false)
-
-    // 협력사 다이얼로그 관련 상태
-    const [participantsDialogOpen, setParticipantsDialogOpen] = React.useState(false)
-    const [selectedParticipantType, setSelectedParticipantType] = React.useState<'expected' | 'participated' | 'declined' | 'pending' | null>(null)
-    const [selectedBiddingId, setSelectedBiddingId] = React.useState<number | null>(null)
-    const [participantCompanies, setParticipantCompanies] = React.useState<any[]>([])
-    const [isLoadingParticipants, setIsLoadingParticipants] = React.useState(false)
-
-    const router = useRouter()
-    const { data: session } = useSession()
-
-    // 협력사 클릭 핸들러
-    const handleParticipantClick = React.useCallback(async (biddingId: number, participantType: 'expected' | 'participated' | 'declined' | 'pending') => {
-        setSelectedBiddingId(biddingId)
-        setSelectedParticipantType(participantType)
-        setIsLoadingParticipants(true)
-        setParticipantsDialogOpen(true)
-
-        try {
-            // 협력사 데이터 로드 (모든 초대된 협력사)
-            const companies = await getAllBiddingCompanies(biddingId)
-            
-            console.log('Loaded companies:', companies)
-            
-            // 필터링 없이 모든 데이터 그대로 표시
-            // invitationStatus가 그대로 다이얼로그에 표시됨
-            setParticipantCompanies(companies)
-        } catch (error) {
-            console.error('Failed to load participant companies:', error)
-            toast.error('협력사 목록을 불러오는데 실패했습니다.')
-            setParticipantCompanies([])
-        } finally {
-            setIsLoadingParticipants(false)
-        }
-    }, [])
-
-    const columns = React.useMemo(
-        () => getBiddingsReceiveColumns({ setRowAction, onParticipantClick: handleParticipantClick }),
-        [setRowAction, handleParticipantClick]
-    )
-
-    // rowAction 변경 감지하여 해당 다이얼로그 열기
-    React.useEffect(() => {
-        if (rowAction) {
-            setSelectedBidding(rowAction.row.original)
-
-            switch (rowAction.type) {
-                case "view":
-                    // 상세 페이지로 이동
-                    router.push(`/evcp/bid/${rowAction.row.original.id}`)
-                    break
-                default:
-                    break
-            }
-        }
-    }, [rowAction, router])
-
-    const filterFields: DataTableFilterField<BiddingReceiveItem>[] = [
-        {
-            id: "biddingNumber",
-            label: "입찰번호",
-            placeholder: "입찰번호를 입력하세요",
-        },
-        {
-            id: "prNumber",
-            label: "P/R번호",
-            placeholder: "P/R번호를 입력하세요",
-        },
-        {
-            id: "title",
-            label: "입찰명",
-            placeholder: "입찰명을 입력하세요",
-        },
-    ]
-
-    const advancedFilterFields: DataTableAdvancedFilterField<BiddingReceiveItem>[] = [
-        { id: "title", label: "입찰명", type: "text" },
-        { id: "biddingNumber", label: "입찰번호", type: "text" },
-        { id: "bidPicName", label: "입찰담당자", type: "text" },
-        {
-            id: "status",
-            label: "진행상태",
-            type: "multi-select",
-            options: Object.entries(biddingStatusLabels).map(([value, label]) => ({
-                label,
-                value,
-            })),
-        },
-        {
-            id: "contractType",
-            label: "계약구분",
-            type: "select",
-            options: Object.entries(contractTypeLabels).map(([value, label]) => ({
-                label,
-                value,
-            })),
-        },
-        { id: "createdAt", label: "등록일", type: "date" },
-        { id: "submissionStartDate", label: "제출시작일", type: "date" },
-        { id: "submissionEndDate", label: "제출마감일", type: "date" },
-    ]
-
-    const { table } = useDataTable({
-        data,
-        columns,
-        pageCount,
-        filterFields,
-        enablePinning: true,
-        enableAdvancedFilter: true,
-        enableRowSelection: true,
-        initialState: {
-            sorting: [{ id: "createdAt", desc: true }],
-            columnPinning: { right: ["actions"] },
-        },
-        getRowId: (originalRow) => String(originalRow.id),
-        shallow: false,
-        clearOnDefault: true,
-    })
-
-    const handleCompactChange = React.useCallback((compact: boolean) => {
-        setIsCompact(compact)
-    }, [])
-
-
-    // 선택된 행 가져오기
-    const selectedRows = table.getFilteredSelectedRowModel().rows
-    const selectedBiddingForAction = selectedRows.length > 0 ? selectedRows[0].original : null
-
-    // 개찰 가능 여부 확인
-    const canOpen = React.useMemo(() => {
-        if (!selectedBiddingForAction) return false
-
-        const now = new Date()
-        const submissionEndDate = selectedBiddingForAction.submissionEndDate ? new Date(selectedBiddingForAction.submissionEndDate) : null
-
-        // 1. 입찰 마감일이 지났으면 무조건 가능
-        if (submissionEndDate && now > submissionEndDate) return true
-
-        // 2. 입찰 기간 내 조기개찰 조건 확인
-        // - 미제출 협력사가 0이어야 함 (참여예정 = 최종제출 + 포기)
-        const participatedOrDeclined = selectedBiddingForAction.participantFinalSubmitted + selectedBiddingForAction.participantDeclined
-        const isEarlyOpenPossible = participatedOrDeclined === selectedBiddingForAction.participantExpected
-
-        return isEarlyOpenPossible
-    }, [selectedBiddingForAction])
-
-    const handleOpenBidding = React.useCallback(async () => {
-        if (!selectedBiddingForAction) return
-
-        setIsOpeningBidding(true)
-        try {
-            const result = await openBiddingAction(selectedBiddingForAction.id)
-            if (result.success) {
-                toast.success("개찰이 완료되었습니다.")
-                // 데이터 리프레시
-                window.location.reload()
-            } else {
-                toast.error(result.message || "개찰에 실패했습니다.")
-            }
-        } catch (error) {
-            toast.error("개찰 중 오류가 발생했습니다.")
-        } finally {
-            setIsOpeningBidding(false)
-        }
-    }, [selectedBiddingForAction])
-
-    return (
-        <>
-            <DataTable
-                table={table}
-                compact={isCompact}
-            >
-                <DataTableAdvancedToolbar
-                    table={table}
-                    filterFields={advancedFilterFields}
-                    shallow={false}
-                    enableCompactToggle={true}
-                    compactStorageKey="biddingsReceiveTableCompact"
-                    onCompactChange={handleCompactChange}
-                >
-                    <div className="flex items-center gap-2">
-                        <Button
-                            variant="outline"
-                            size="sm"
-                            onClick={handleOpenBidding}
-                            disabled={!selectedBiddingForAction || !canOpen || isOpeningBidding}
-                        >
-                            {isOpeningBidding && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
-                            개찰
-                        </Button>
-                    </div>
-                </DataTableAdvancedToolbar>
-            </DataTable>
-
-            {/* 사양설명회 다이얼로그 */}
-            {/* <SpecificationMeetingDialog
-                open={specMeetingDialogOpen}
-                onOpenChange={handleSpecMeetingDialogClose}
-                bidding={selectedBidding}
-            /> */}
-
-            {/* PR 문서 다이얼로그 */}
-            {/* <PrDocumentsDialog
-                open={prDocumentsDialogOpen}
-                onOpenChange={handlePrDocumentsDialogClose}
-                bidding={selectedBidding}
-            /> */}
-
-            {/* 참여 협력사 다이얼로그 */}
-            <BiddingParticipantsDialog
-                open={participantsDialogOpen}
-                onOpenChange={setParticipantsDialogOpen}
-                biddingId={selectedBiddingId}
-                participantType={selectedParticipantType}
-                companies={participantCompanies}
-            />
-        </>
-    )
-}
+"use client"
+
+import * as React from "react"
+import { useRouter } from "next/navigation"
+import { useSession } from "next-auth/react"
+import type {
+    DataTableAdvancedFilterField,
+    DataTableFilterField,
+    DataTableRowAction,
+} from "@/types/table"
+import { Button } from "@/components/ui/button"
+import { Loader2 } from "lucide-react"
+import { toast } from "sonner"
+
+import { useDataTable } from "@/hooks/use-data-table"
+import { DataTable } from "@/components/data-table/data-table"
+import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar"
+import { getBiddingsReceiveColumns } from "./biddings-receive-columns"
+import { getBiddingsForReceive } from "@/lib/bidding/service"
+import {
+    biddingStatusLabels,
+    contractTypeLabels,
+} from "@/db/schema"
+// import { SpecificationMeetingDialog, PrDocumentsDialog } from "../list/bidding-detail-dialogs"
+import { openBiddingAction } from "@/lib/bidding/actions"
+import { BiddingParticipantsDialog } from "@/components/bidding/receive/bidding-participants-dialog"
+import { getAllBiddingCompanies } from "@/lib/bidding/detail/service"
+
+type BiddingReceiveItem = {
+  id: number
+  biddingNumber: string
+  originalBiddingNumber: string | null
+  title: string
+  status: string
+  contractType: string
+  prNumber: string | null
+  submissionStartDate: Date | null
+  submissionEndDate: Date | null
+  bidPicName: string | null
+  supplyPicName: string | null
+  createdBy: string | null
+  createdAt: Date | null
+  updatedAt: Date | null
+
+  // 참여 현황
+  participantExpected: number
+  participantParticipated: number
+  participantDeclined: number
+  participantPending: number
+  participantFinalSubmitted: number
+
+  // 개찰 정보
+  openedAt: Date | null
+  openedBy: string | null
+}
+
+interface BiddingsReceiveTableProps {
+    promises: Promise<
+        [
+            Awaited<ReturnType<typeof getBiddingsForReceive>>
+        ]
+    >
+}
+
+export function BiddingsReceiveTable({ promises }: BiddingsReceiveTableProps) {
+    const [biddingsResult] = React.use(promises)
+
+    // biddingsResult에서 data와 pageCount 추출
+    const { data, pageCount } = biddingsResult
+
+    const [isCompact, setIsCompact] = React.useState<boolean>(false)
+    // const [specMeetingDialogOpen, setSpecMeetingDialogOpen] = React.useState(false)
+    // const [prDocumentsDialogOpen, setPrDocumentsDialogOpen] = React.useState(false)
+    const [selectedBidding, setSelectedBidding] = React.useState<BiddingReceiveItem | null>(null)
+
+    const [rowAction, setRowAction] = React.useState<DataTableRowAction<BiddingReceiveItem> | null>(null)
+    const [isOpeningBidding, setIsOpeningBidding] = React.useState(false)
+
+    // 협력사 다이얼로그 관련 상태
+    const [participantsDialogOpen, setParticipantsDialogOpen] = React.useState(false)
+    const [selectedParticipantType, setSelectedParticipantType] = React.useState<'expected' | 'participated' | 'declined' | 'pending' | null>(null)
+    const [selectedBiddingId, setSelectedBiddingId] = React.useState<number | null>(null)
+    const [participantCompanies, setParticipantCompanies] = React.useState<any[]>([])
+    const [isLoadingParticipants, setIsLoadingParticipants] = React.useState(false)
+
+    const router = useRouter()
+    const { data: session } = useSession()
+
+    // 협력사 클릭 핸들러
+    const handleParticipantClick = React.useCallback(async (biddingId: number, participantType: 'expected' | 'participated' | 'declined' | 'pending') => {
+        setSelectedBiddingId(biddingId)
+        setSelectedParticipantType(participantType)
+        setIsLoadingParticipants(true)
+        setParticipantsDialogOpen(true)
+
+        try {
+            // 협력사 데이터 로드 (모든 초대된 협력사)
+            const companies = await getAllBiddingCompanies(biddingId)
+            
+            console.log('Loaded companies:', companies)
+            
+            // 필터링 없이 모든 데이터 그대로 표시
+            // invitationStatus가 그대로 다이얼로그에 표시됨
+            setParticipantCompanies(companies)
+        } catch (error) {
+            console.error('Failed to load participant companies:', error)
+            toast.error('협력사 목록을 불러오는데 실패했습니다.')
+            setParticipantCompanies([])
+        } finally {
+            setIsLoadingParticipants(false)
+        }
+    }, [])
+
+    const columns = React.useMemo(
+        () => getBiddingsReceiveColumns({ setRowAction, onParticipantClick: handleParticipantClick }),
+        [setRowAction, handleParticipantClick]
+    )
+
+    // rowAction 변경 감지하여 해당 다이얼로그 열기
+    React.useEffect(() => {
+        if (rowAction) {
+            setSelectedBidding(rowAction.row.original)
+
+            switch (rowAction.type) {
+                case "view":
+                    // 상세 페이지로 이동
+                    router.push(`/evcp/bid/${rowAction.row.original.id}`)
+                    break
+                default:
+                    break
+            }
+        }
+    }, [rowAction, router])
+
+    const filterFields: DataTableFilterField<BiddingReceiveItem>[] = [
+        {
+            id: "biddingNumber",
+            label: "입찰번호",
+            placeholder: "입찰번호를 입력하세요",
+        },
+        {
+            id: "prNumber",
+            label: "P/R번호",
+            placeholder: "P/R번호를 입력하세요",
+        },
+        {
+            id: "title",
+            label: "입찰명",
+            placeholder: "입찰명을 입력하세요",
+        },
+    ]
+
+    const advancedFilterFields: DataTableAdvancedFilterField<BiddingReceiveItem>[] = [
+        { id: "title", label: "입찰명", type: "text" },
+        { id: "biddingNumber", label: "입찰번호", type: "text" },
+        { id: "bidPicName", label: "입찰담당자", type: "text" },
+        {
+            id: "status",
+            label: "진행상태",
+            type: "multi-select",
+            options: Object.entries(biddingStatusLabels).map(([value, label]) => ({
+                label,
+                value,
+            })),
+        },
+        {
+            id: "contractType",
+            label: "계약구분",
+            type: "select",
+            options: Object.entries(contractTypeLabels).map(([value, label]) => ({
+                label,
+                value,
+            })),
+        },
+        { id: "createdAt", label: "등록일", type: "date" },
+        { id: "submissionStartDate", label: "제출시작일", type: "date" },
+        { id: "submissionEndDate", label: "제출마감일", type: "date" },
+    ]
+
+    const { table } = useDataTable({
+        data,
+        columns,
+        pageCount,
+        filterFields,
+        enablePinning: true,
+        enableAdvancedFilter: true,
+        enableRowSelection: true,
+        enableMultiRowSelection: false, // 단일 선택만 가능
+        initialState: {
+            sorting: [{ id: "createdAt", desc: true }],
+            columnPinning: { right: ["actions"] },
+        },
+        getRowId: (originalRow) => String(originalRow.id),
+        shallow: false,
+        clearOnDefault: true,
+    })
+
+    const handleCompactChange = React.useCallback((compact: boolean) => {
+        setIsCompact(compact)
+    }, [])
+
+
+    // 선택된 행 가져오기
+    const selectedRows = table.getFilteredSelectedRowModel().rows
+    const selectedBiddingForAction = selectedRows.length > 0 ? selectedRows[0].original : null
+
+    // 개찰 가능 여부 확인
+    const canOpen = React.useMemo(() => {
+        if (!selectedBiddingForAction) return false
+
+        const now = new Date()
+        const submissionEndDate = selectedBiddingForAction.submissionEndDate ? new Date(selectedBiddingForAction.submissionEndDate) : null
+
+        // 1. 입찰 마감일이 지났으면 무조건 가능
+        if (submissionEndDate && now > submissionEndDate) return true
+
+        // 2. 입찰 기간 내 조기개찰 조건 확인
+        // - 미제출 협력사가 0이어야 함 (참여예정 = 최종제출 + 포기)
+        const participatedOrDeclined = selectedBiddingForAction.participantFinalSubmitted + selectedBiddingForAction.participantDeclined
+        const isEarlyOpenPossible = participatedOrDeclined === selectedBiddingForAction.participantExpected
+
+        return isEarlyOpenPossible
+    }, [selectedBiddingForAction])
+
+    const handleOpenBidding = React.useCallback(async () => {
+        if (!selectedBiddingForAction) return
+
+        setIsOpeningBidding(true)
+        try {
+            const result = await openBiddingAction(selectedBiddingForAction.id)
+            if (result.success) {
+                toast.success("개찰이 완료되었습니다.")
+                // 데이터 리프레시
+                window.location.reload()
+            } else {
+                toast.error(result.message || "개찰에 실패했습니다.")
+            }
+        } catch (error) {
+            toast.error("개찰 중 오류가 발생했습니다.")
+        } finally {
+            setIsOpeningBidding(false)
+        }
+    }, [selectedBiddingForAction])
+
+    return (
+        <>
+            <DataTable
+                table={table}
+                compact={isCompact}
+            >
+                <DataTableAdvancedToolbar
+                    table={table}
+                    filterFields={advancedFilterFields}
+                    shallow={false}
+                    enableCompactToggle={true}
+                    compactStorageKey="biddingsReceiveTableCompact"
+                    onCompactChange={handleCompactChange}
+                >
+                    <div className="flex items-center gap-2">
+                        <Button
+                            variant="outline"
+                            size="sm"
+                            onClick={handleOpenBidding}
+                            disabled={!selectedBiddingForAction || !canOpen || isOpeningBidding}
+                        >
+                            {isOpeningBidding && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+                            개찰
+                        </Button>
+                    </div>
+                </DataTableAdvancedToolbar>
+            </DataTable>
+
+            {/* 사양설명회 다이얼로그 */}
+            {/* <SpecificationMeetingDialog
+                open={specMeetingDialogOpen}
+                onOpenChange={handleSpecMeetingDialogClose}
+                bidding={selectedBidding}
+            /> */}
+
+            {/* PR 문서 다이얼로그 */}
+            {/* <PrDocumentsDialog
+                open={prDocumentsDialogOpen}
+                onOpenChange={handlePrDocumentsDialogClose}
+                bidding={selectedBidding}
+            /> */}
+
+            {/* 참여 협력사 다이얼로그 */}
+            <BiddingParticipantsDialog
+                open={participantsDialogOpen}
+                onOpenChange={setParticipantsDialogOpen}
+                biddingId={selectedBiddingId}
+                participantType={selectedParticipantType}
+                companies={participantCompanies}
+            />
+        </>
+    )
+}
diff --git a/lib/bidding/service.ts b/lib/bidding/service.ts
index 76cd31f7..d45e9286 100644
--- a/lib/bidding/service.ts
+++ b/lib/bidding/service.ts
@@ -40,7 +40,8 @@ import {
 import { revalidatePath } from 'next/cache'
 import { filterColumns } from '@/lib/filter-columns'
 import { GetBiddingsSchema, CreateBiddingSchema } from './validation'
-import { saveFile } from '../file-stroage'
+import { saveFile, saveBuffer } from '../file-stroage'
+import { decryptBufferWithServerAction } from '@/components/drm/drmUtils'
 import { getVendorPricesForBidding } from './detail/service'
 import { getPrItemsForBidding } from './pre-quote/service'
 
@@ -1913,12 +1914,14 @@ export async function updateBiddingBasicInfo(
   }
 }
 
-// 입찰 일정 업데이트
+// 입찰 일정 업데이트 (오프셋 기반)
 export async function updateBiddingSchedule(
   biddingId: number,
   schedule: {
-    submissionStartDate?: string
-    submissionEndDate?: string
+    submissionStartOffset?: number      // 시작일 오프셋 (결재 후 n일)
+    submissionStartTime?: string        // 시작 시간 (HH:MM)
+    submissionDurationDays?: number     // 기간 (시작일 + n일)
+    submissionEndTime?: string          // 마감 시간 (HH:MM)
     remarks?: string
     isUrgent?: boolean
     hasSpecificationMeeting?: boolean
@@ -1949,14 +1952,28 @@ export async function updateBiddingSchedule(
       return new Date(`${dateStr}:00+09:00`)
     }
 
+    // 시간 문자열(HH:MM)을 임시 timestamp로 변환 (1970-01-01 HH:MM:00 UTC)
+    // 결재 완료 시 실제 날짜로 계산됨
+    const timeToTimestamp = (timeStr?: string): Date | null => {
+      if (!timeStr) return null
+      const [hours, minutes] = timeStr.split(':').map(Number)
+      const date = new Date(0) // 1970-01-01 00:00:00 UTC
+      date.setUTCHours(hours, minutes, 0, 0)
+      return date
+    }
+
     return await db.transaction(async (tx) => {
       const updateData: any = {
         updatedAt: new Date(),
         updatedBy: userName,
       }
 
-      if (schedule.submissionStartDate !== undefined) updateData.submissionStartDate = parseDate(schedule.submissionStartDate) || null
-      if (schedule.submissionEndDate !== undefined) updateData.submissionEndDate = parseDate(schedule.submissionEndDate) || null
+      // 오프셋 기반 필드 저장
+      if (schedule.submissionStartOffset !== undefined) updateData.submissionStartOffset = schedule.submissionStartOffset
+      if (schedule.submissionDurationDays !== undefined) updateData.submissionDurationDays = schedule.submissionDurationDays
+      // 시간은 timestamp 필드에 임시 저장 (1970-01-01 HH:MM:00)
+      if (schedule.submissionStartTime !== undefined) updateData.submissionStartDate = timeToTimestamp(schedule.submissionStartTime)
+      if (schedule.submissionEndTime !== undefined) updateData.submissionEndDate = timeToTimestamp(schedule.submissionEndTime)
       if (schedule.remarks !== undefined) updateData.remarks = schedule.remarks
       if (schedule.isUrgent !== undefined) updateData.isUrgent = schedule.isUrgent
       if (schedule.hasSpecificationMeeting !== undefined) updateData.hasSpecificationMeeting = schedule.hasSpecificationMeeting
@@ -3240,32 +3257,34 @@ export async function increaseRoundOrRebid(biddingId: number, userId: string | u
       if (existingDocuments.length > 0) {
         for (const doc of existingDocuments) {
           try {
-            // 기존 파일을 Buffer로 읽어서 File 객체 생성
-            const { readFileSync, existsSync } = await import('fs')
+            // 기존 파일 경로 확인 및 Buffer로 읽기
+            const { readFile, access, constants } = await import('fs/promises')
             const { join } = await import('path')
 
+            // 파일 경로 정규화
             const oldFilePath = doc.filePath.startsWith('/uploads/')
+              ? join(process.cwd(), 'public', doc.filePath)
+              : doc.filePath.startsWith('/')
               ? join(process.cwd(), 'public', doc.filePath)
               : doc.filePath
 
-            if (!existsSync(oldFilePath)) {
-              console.warn(`원본 파일이 존재하지 않음: ${oldFilePath}`)
+            // 파일 존재 여부 확인
+            try {
+              await access(oldFilePath, constants.R_OK)
+            } catch {
+              console.warn(`원본 파일이 존재하지 않거나 읽을 수 없음: ${oldFilePath}`)
               continue
             }
 
-            // 파일 내용을 읽어서 Buffer 생성
-            const fileBuffer = readFileSync(oldFilePath)
-
-            // Buffer를 File 객체로 변환 (브라우저 File API 시뮬레이션)
-            const file = new File([fileBuffer], doc.fileName, {
-              type: doc.mimeType || 'application/octet-stream'
-            })
+            // 파일 내용을 Buffer로 읽기
+            const fileBuffer = await readFile(oldFilePath)
 
-            // saveFile을 사용하여 새 파일 저장
-            const saveResult = await saveFile({
-              file,
+            // saveBuffer를 사용하여 새 파일 저장 (File 객체 변환 없이 직접 저장)
+            const saveResult = await saveBuffer({
+              buffer: fileBuffer,
+              fileName: doc.fileName,
               directory: `biddings/${newBidding.id}/attachments/${doc.documentType === 'evaluation_doc' ? 'shi' : 'vendor'}`,
-              originalName: `copied_${Date.now()}_${doc.fileName}`,
+              originalName: doc.originalFileName || doc.fileName,
               userId: userName
             })
 
diff --git a/lib/bidding/vendor/components/pr-items-pricing-table.tsx b/lib/bidding/vendor/components/pr-items-pricing-table.tsx
index 5afb2b67..6910e360 100644
--- a/lib/bidding/vendor/components/pr-items-pricing-table.tsx
+++ b/lib/bidding/vendor/components/pr-items-pricing-table.tsx
@@ -4,7 +4,17 @@ import * as React from 'react'
 import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
 import { Input } from '@/components/ui/input'
 import { Label } from '@/components/ui/label'
-
+import { Button } from '@/components/ui/button'
+import { Calendar } from '@/components/ui/calendar'
+import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
+import {
+  Dialog,
+  DialogContent,
+  DialogDescription,
+  DialogFooter,
+  DialogHeader,
+  DialogTitle,
+} from '@/components/ui/dialog'
 import { Badge } from '@/components/ui/badge'
 import {
   Table,
@@ -16,10 +26,12 @@ import {
 } from '@/components/ui/table'
 import {
   Package,
-
   Download,
-  Calculator
+  Calculator,
+  CalendarIcon
 } from 'lucide-react'
+import { format } from 'date-fns'
+import { cn } from '@/lib/utils'
 import { formatDate } from '@/lib/utils'
 import { downloadFile, formatFileSize, getFileInfo } from '@/lib/file-download'
 import { getSpecDocumentsForPrItem } from '../../pre-quote/service'
@@ -186,6 +198,8 @@ export function PrItemsPricingTable({
 }: PrItemsPricingTableProps) {
   const [quotations, setQuotations] = React.useState<PrItemQuotation[]>([])
   const [specDocuments, setSpecDocuments] = React.useState<Record<number, SpecDocument[]>>({})
+  const [showBulkDateDialog, setShowBulkDateDialog] = React.useState(false)
+  const [bulkDeliveryDate, setBulkDeliveryDate] = React.useState<Date | undefined>(undefined)
 
   // 초기 견적 데이터 설정 및 SPEC 문서 로드
   React.useEffect(() => {
@@ -279,6 +293,21 @@ export function PrItemsPricingTable({
     onTotalAmountChange(totalAmount)
   }
 
+  // 일괄 납기일 적용
+  const applyBulkDeliveryDate = () => {
+    if (bulkDeliveryDate && quotations.length > 0) {
+      const formattedDate = format(bulkDeliveryDate, 'yyyy-MM-dd')
+      const updatedQuotations = quotations.map(q => ({
+        ...q,
+        proposedDeliveryDate: formattedDate
+      }))
+      
+      setQuotations(updatedQuotations)
+      onQuotationsChange(updatedQuotations)
+      setShowBulkDateDialog(false)
+      setBulkDeliveryDate(undefined)
+    }
+  }
 
   // 통화 포맷팅
   const formatCurrency = (amount: number) => {
@@ -292,12 +321,26 @@ export function PrItemsPricingTable({
   const totalAmount = quotations.reduce((sum, q) => sum + q.bidAmount, 0)
 
   return (
+    <>
     <Card>
       <CardHeader>
-        <CardTitle className="flex items-center gap-2">
-          <Package className="w-5 h-5" />
-          품목별 입찰 작성
-        </CardTitle>
+        <div className="flex items-center justify-between">
+          <CardTitle className="flex items-center gap-2">
+            <Package className="w-5 h-5" />
+            품목별 입찰 작성
+          </CardTitle>
+          {!readOnly && (
+            <Button
+              type="button"
+              variant="outline"
+              size="sm"
+              onClick={() => setShowBulkDateDialog(true)}
+            >
+              <CalendarIcon className="h-4 w-4 mr-1" />
+              전체 납품예정일 설정
+            </Button>
+          )}
+        </div>
       </CardHeader>
       <CardContent>
         <div className="space-y-4">
@@ -467,5 +510,73 @@ export function PrItemsPricingTable({
         </div>
       </CardContent>
     </Card>
+
+      {/* 일괄 납품예정일 설정 다이얼로그 */}
+      <Dialog open={showBulkDateDialog} onOpenChange={setShowBulkDateDialog}>
+        <DialogContent className="sm:max-w-md">
+          <DialogHeader>
+            <DialogTitle>전체 납품예정일 설정</DialogTitle>
+            <DialogDescription>
+              모든 PR 아이템에 동일한 납품예정일을 적용합니다.
+            </DialogDescription>
+          </DialogHeader>
+
+          <div className="space-y-4">
+            <div className="space-y-2">
+              <Label>납품예정일 선택</Label>
+              <Popover>
+                <PopoverTrigger asChild>
+                  <Button
+                    variant="outline"
+                    className={cn(
+                      "w-full justify-start text-left font-normal",
+                      !bulkDeliveryDate && "text-muted-foreground"
+                    )}
+                  >
+                    <CalendarIcon className="mr-2 h-4 w-4" />
+                    {bulkDeliveryDate ? format(bulkDeliveryDate, "yyyy-MM-dd") : "날짜 선택"}
+                  </Button>
+                </PopoverTrigger>
+                <PopoverContent className="w-auto p-0" align="start">
+                  <Calendar
+                    mode="single"
+                    selected={bulkDeliveryDate}
+                    onSelect={setBulkDeliveryDate}
+                    initialFocus
+                  />
+                </PopoverContent>
+              </Popover>
+            </div>
+
+            <div className="bg-muted/50 rounded-lg p-3">
+              <p className="text-sm text-muted-foreground">
+                선택된 날짜가 <strong>{prItems.length}개</strong>의 모든 PR 아이템에 적용됩니다.
+                기존에 설정된 납품예정일은 모두 교체됩니다.
+              </p>
+            </div>
+          </div>
+
+          <DialogFooter>
+            <Button
+              type="button"
+              variant="outline"
+              onClick={() => {
+                setShowBulkDateDialog(false)
+                setBulkDeliveryDate(undefined)
+              }}
+            >
+              취소
+            </Button>
+            <Button
+              type="button"
+              onClick={applyBulkDeliveryDate}
+              disabled={!bulkDeliveryDate}
+            >
+              전체 적용
+            </Button>
+          </DialogFooter>
+        </DialogContent>
+      </Dialog>
+    </>
   )
 }
-- 
cgit v1.2.3

