1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
|
"use client"
import * as React from "react"
import { toast } from "sonner"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { type ProjectSeries } from "@/db/schema/projects"
import { getProjectSeriesForProject } from "@/lib/bidding-projects/service"
// 기본적인 RFQ 타입 정의 (rfq-table.tsx와 일치)
interface TechSalesRfq {
id: number
rfqCode: string | null
itemId: number
itemName: string | null
materialCode: string | null
dueDate: Date
rfqSendDate: Date | null
status: "RFQ Created" | "RFQ Vendor Assignned" | "RFQ Sent" | "Quotation Analysis" | "Closed"
picCode: string | null
remark: string | null
cancelReason: string | null
createdAt: Date
updatedAt: Date
createdBy: number | null
createdByName: string
updatedBy: number | null
updatedByName: string
sentBy: number | null
sentByName: string | null
pspid: string
projNm: string
sector: string
projMsrm: number
ptypeNm: string
attachmentCount: number
quotationCount: number
}
// K/L 날짜를 4분기로 변환하는 함수
function convertKLToQuarter(klDate: string | null): string {
if (!klDate) return "정보 없음"
try {
// YYYYMMDD 형식의 날짜를 파싱
const year = parseInt(klDate.substring(0, 4))
const month = parseInt(klDate.substring(4, 6))
// 4분기 계산 (1-3월: 1Q, 4-6월: 2Q, 7-9월: 3Q, 10-12월: 4Q)
const quarter = Math.ceil(month / 3)
return `${year} ${quarter}Q`
} catch (error) {
console.error("K/L 날짜 변환 오류:", error)
return "날짜 오류"
}
}
interface ProjectDetailDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
selectedRfq: TechSalesRfq | null
}
export function ProjectDetailDialog({
open,
onOpenChange,
selectedRfq,
}: ProjectDetailDialogProps) {
const [projectSeries, setProjectSeries] = React.useState<ProjectSeries[]>([])
const [isLoadingSeries, setIsLoadingSeries] = React.useState(false)
React.useEffect(() => {
async function loadSeries() {
if (!selectedRfq?.pspid) return
setIsLoadingSeries(true)
try {
const result = await getProjectSeriesForProject(selectedRfq.pspid)
setProjectSeries(result)
} catch (error) {
console.error("프로젝트 시리즈 로드 오류:", error)
toast.error("프로젝트 시리즈 로드 실패")
} finally {
setIsLoadingSeries(false)
}
}
if (open && selectedRfq) {
loadSeries()
}
}, [selectedRfq, open])
if (!selectedRfq) {
return null
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl w-[80vw] max-h-[80vh] overflow-hidden flex flex-col">
<DialogHeader className="border-b pb-4">
<DialogTitle className="flex items-center gap-2">
프로젝트 상세정보
<Badge variant="outline">{selectedRfq.pspid}</Badge>
</DialogTitle>
<DialogDescription className="space-y-1">
<div className="flex items-center gap-2 text-base font-medium">
<span>RFQ:</span>
<Badge variant="secondary">{selectedRfq.rfqCode || "미할당"}</Badge>
<span>|</span>
<span>자재:</span>
<span className="text-foreground">{selectedRfq.materialCode || "N/A"}</span>
</div>
<div className="text-sm text-muted-foreground">
{selectedRfq.projNm} - {selectedRfq.ptypeNm} ({selectedRfq.itemName || "자재명 없음"})
</div>
</DialogDescription>
</DialogHeader>
<div className="space-y-6 p-1 overflow-y-auto">
{/* 기본 프로젝트 정보 */}
<div className="space-y-4">
<h3 className="text-lg font-semibold">기본 정보</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<div className="text-sm font-medium text-muted-foreground">프로젝트 ID</div>
<div className="text-sm">{selectedRfq.pspid}</div>
</div>
<div className="space-y-2">
<div className="text-sm font-medium text-muted-foreground">프로젝트명</div>
<div className="text-sm">{selectedRfq.projNm}</div>
</div>
<div className="space-y-2">
<div className="text-sm font-medium text-muted-foreground">선종</div>
<div className="text-sm">{selectedRfq.ptypeNm}</div>
</div>
<div className="space-y-2">
<div className="text-sm font-medium text-muted-foreground">척수</div>
<div className="text-sm">{selectedRfq.projMsrm}</div>
</div>
<div className="space-y-2">
<div className="text-sm font-medium text-muted-foreground">섹터</div>
<div className="text-sm">{selectedRfq.sector}</div>
</div>
</div>
</div>
{/* 시리즈 정보 */}
<div className="space-y-4">
<h3 className="text-lg font-semibold">시리즈 정보</h3>
<div className="bg-gray-50 rounded-lg p-4">
{isLoadingSeries ? (
<div className="text-center py-4 text-gray-500">
시리즈 정보 로딩 중...
</div>
) : projectSeries.length > 0 ? (
<div className="grid grid-cols-1 gap-3">
{projectSeries.map((series) => (
<div key={series.sersNo} className="bg-white rounded border p-3">
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 text-sm">
<div>
<span className="font-medium text-gray-700">시리즈번호:</span>
<div className="text-gray-900">{series.sersNo}</div>
</div>
<div>
<span className="font-medium text-gray-700">K/L (Keel Laying):</span>
<div className="text-gray-900">{convertKLToQuarter(series.klDt)}</div>
</div>
<div>
<span className="font-medium text-gray-700">도크코드:</span>
<div className="text-gray-900">{series.dockNo || "N/A"}</div>
</div>
<div>
<span className="font-medium text-gray-700">도크명:</span>
<div className="text-gray-900">{series.dockNm || "N/A"}</div>
</div>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-4 text-gray-500">
시리즈 데이터가 없습니다.
</div>
)}
</div>
</div>
</div>
{/* 닫기 버튼 */}
<div className="sticky bottom-0 left-0 z-20 bg-background border-t pt-4 mt-4">
<div className="flex justify-end">
<Button variant="outline" onClick={() => onOpenChange(false)}>
닫기
</Button>
</div>
</div>
</DialogContent>
</Dialog>
)
}
|