summaryrefslogtreecommitdiff
path: root/lib/techsales-rfq/table/detail-table
diff options
context:
space:
mode:
Diffstat (limited to 'lib/techsales-rfq/table/detail-table')
-rw-r--r--lib/techsales-rfq/table/detail-table/add-vendor-dialog.tsx213
-rw-r--r--lib/techsales-rfq/table/detail-table/quotation-contacts-view-dialog.tsx94
-rw-r--r--lib/techsales-rfq/table/detail-table/quotation-history-dialog.tsx26
-rw-r--r--lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx1
4 files changed, 186 insertions, 148 deletions
diff --git a/lib/techsales-rfq/table/detail-table/add-vendor-dialog.tsx b/lib/techsales-rfq/table/detail-table/add-vendor-dialog.tsx
index ea982407..438ee840 100644
--- a/lib/techsales-rfq/table/detail-table/add-vendor-dialog.tsx
+++ b/lib/techsales-rfq/table/detail-table/add-vendor-dialog.tsx
@@ -362,99 +362,128 @@ export function AddVendorDialog({
)
// 벤더 구분자 설정 UI
- const renderVendorFlagsStep = () => (
- <div className="space-y-4">
- <div className="text-sm text-muted-foreground">
- 선택된 벤더들의 구분자를 설정해주세요. 각 벤더별로 여러 구분자를 선택할 수 있습니다.
- </div>
-
- {selectedVendorData.length > 0 ? (
- <div className="space-y-4">
- {selectedVendorData.map((vendor) => (
- <Card key={vendor.id}>
- <CardHeader className="pb-3">
- <CardTitle className="text-base">{vendor.vendorName}</CardTitle>
- <CardDescription>
- {vendor.vendorCode || 'N/A'} {vendor.country && `• ${vendor.country}`}
- </CardDescription>
- </CardHeader>
- <CardContent className="space-y-3">
- <div className="grid grid-cols-2 gap-3">
- <div className="flex items-center space-x-2">
- <Checkbox
- id={`customer-preferred-${vendor.id}`}
- checked={vendorFlags[vendor.id.toString()]?.isCustomerPreferred || false}
- onCheckedChange={(checked) =>
- handleVendorFlagChange(vendor.id, 'isCustomerPreferred', checked as boolean)
- }
- />
- <label
- htmlFor={`customer-preferred-${vendor.id}`}
- className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
- >
- 고객(선주) 선호 벤더
- </label>
- </div>
-
- <div className="flex items-center space-x-2">
- <Checkbox
- id={`new-discovery-${vendor.id}`}
- checked={vendorFlags[vendor.id.toString()]?.isNewDiscovery || false}
- onCheckedChange={(checked) =>
- handleVendorFlagChange(vendor.id, 'isNewDiscovery', checked as boolean)
- }
- />
- <label
- htmlFor={`new-discovery-${vendor.id}`}
- className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
- >
- 신규 발굴 벤더
- </label>
- </div>
-
- <div className="flex items-center space-x-2">
- <Checkbox
- id={`project-approved-${vendor.id}`}
- checked={vendorFlags[vendor.id.toString()]?.isProjectApproved || false}
- onCheckedChange={(checked) =>
- handleVendorFlagChange(vendor.id, 'isProjectApproved', checked as boolean)
- }
- />
- <label
- htmlFor={`project-approved-${vendor.id}`}
- className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
- >
- Project Approved Vendor
- </label>
- </div>
-
- <div className="flex items-center space-x-2">
- <Checkbox
- id={`shi-proposal-${vendor.id}`}
- checked={vendorFlags[vendor.id.toString()]?.isShiProposal || false}
- onCheckedChange={(checked) =>
- handleVendorFlagChange(vendor.id, 'isShiProposal', checked as boolean)
- }
- />
- <label
- htmlFor={`shi-proposal-${vendor.id}`}
- className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
- >
- SHI Proposal Vendor
- </label>
- </div>
- </div>
- </CardContent>
- </Card>
- ))}
- </div>
- ) : (
- <div className="text-center py-8 text-muted-foreground border rounded-md">
- 선택된 벤더가 없습니다
+ const renderVendorFlagsStep = () => {
+ const isShipRfq = selectedRfq?.rfqType === 'SHIP'
+
+ return (
+ <div className="space-y-4">
+ <div className="text-sm text-muted-foreground">
+ 선택된 벤더들의 구분자를 설정해주세요. 각 벤더별로 여러 구분자를 선택할 수 있습니다.
</div>
- )}
- </div>
- )
+
+ {selectedVendorData.length > 0 ? (
+ <div className="space-y-4">
+ {selectedVendorData.map((vendor) => (
+ <Card key={vendor.id}>
+ <CardHeader className="pb-3">
+ <CardTitle className="text-base">{vendor.vendorName}</CardTitle>
+ <CardDescription>
+ {vendor.vendorCode || 'N/A'} {vendor.country && `• ${vendor.country}`}
+ </CardDescription>
+ </CardHeader>
+ <CardContent className="space-y-3">
+ <div className="grid grid-cols-2 gap-3">
+ {/* 조선 RFQ인 경우: 고객 선호벤더, 신규발굴벤더, SHI Proposal Vendor 표시 */}
+ {isShipRfq && (
+ <>
+ <div className="flex items-center space-x-2">
+ <Checkbox
+ id={`customer-preferred-${vendor.id}`}
+ checked={vendorFlags[vendor.id.toString()]?.isCustomerPreferred || false}
+ onCheckedChange={(checked) =>
+ handleVendorFlagChange(vendor.id, 'isCustomerPreferred', checked as boolean)
+ }
+ />
+ <label
+ htmlFor={`customer-preferred-${vendor.id}`}
+ className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+ >
+ 고객(선주) 선호 벤더
+ </label>
+ </div>
+
+ <div className="flex items-center space-x-2">
+ <Checkbox
+ id={`new-discovery-${vendor.id}`}
+ checked={vendorFlags[vendor.id.toString()]?.isNewDiscovery || false}
+ onCheckedChange={(checked) =>
+ handleVendorFlagChange(vendor.id, 'isNewDiscovery', checked as boolean)
+ }
+ />
+ <label
+ htmlFor={`new-discovery-${vendor.id}`}
+ className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+ >
+ 신규 발굴 벤더
+ </label>
+ </div>
+ <div className="flex items-center space-x-2">
+ <Checkbox
+ id={`shi-proposal-${vendor.id}`}
+ checked={vendorFlags[vendor.id.toString()]?.isShiProposal || false}
+ onCheckedChange={(checked) =>
+ handleVendorFlagChange(vendor.id, 'isShiProposal', checked as boolean)
+ }
+ />
+ <label
+ htmlFor={`shi-proposal-${vendor.id}`}
+ className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+ >
+ SHI Proposal Vendor
+ </label>
+ </div>
+ </>
+ )}
+
+ {/* 조선 RFQ가 아닌 경우: Project Approved Vendor, SHI Proposal Vendor 표시 */}
+ {!isShipRfq && (
+ <>
+ <div className="flex items-center space-x-2">
+ <Checkbox
+ id={`project-approved-${vendor.id}`}
+ checked={vendorFlags[vendor.id.toString()]?.isProjectApproved || false}
+ onCheckedChange={(checked) =>
+ handleVendorFlagChange(vendor.id, 'isProjectApproved', checked as boolean)
+ }
+ />
+ <label
+ htmlFor={`project-approved-${vendor.id}`}
+ className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+ >
+ Project Approved Vendor
+ </label>
+ </div>
+
+ <div className="flex items-center space-x-2">
+ <Checkbox
+ id={`shi-proposal-${vendor.id}`}
+ checked={vendorFlags[vendor.id.toString()]?.isShiProposal || false}
+ onCheckedChange={(checked) =>
+ handleVendorFlagChange(vendor.id, 'isShiProposal', checked as boolean)
+ }
+ />
+ <label
+ htmlFor={`shi-proposal-${vendor.id}`}
+ className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+ >
+ SHI Proposal Vendor
+ </label>
+ </div>
+ </>
+ )}
+ </div>
+ </CardContent>
+ </Card>
+ ))}
+ </div>
+ ) : (
+ <div className="text-center py-8 text-muted-foreground border rounded-md">
+ 선택된 벤더가 없습니다
+ </div>
+ )}
+ </div>
+ )
+ }
return (
<Dialog open={open} onOpenChange={onOpenChange}>
diff --git a/lib/techsales-rfq/table/detail-table/quotation-contacts-view-dialog.tsx b/lib/techsales-rfq/table/detail-table/quotation-contacts-view-dialog.tsx
index 61c97b1b..608b5670 100644
--- a/lib/techsales-rfq/table/detail-table/quotation-contacts-view-dialog.tsx
+++ b/lib/techsales-rfq/table/detail-table/quotation-contacts-view-dialog.tsx
@@ -109,60 +109,62 @@ export function QuotationContactsViewDialog({
</div>
) : (
<div className="space-y-3">
- {contacts.map((contact) => (
- <div
- key={contact.id}
- className="flex items-center justify-between p-4 border rounded-lg bg-gray-50"
- >
- <div className="flex items-center gap-3">
- <User className="size-4 text-muted-foreground" />
- <div>
- <div className="flex items-center gap-2">
- <span className="font-medium">{contact.contactName}</span>
- {contact.isPrimary && (
- <Badge variant="secondary" className="text-xs">
- 주담당자
- </Badge>
+ {contacts
+ .filter((contact) => contact.contactTitle) // contactTitle이 있는 담당자만 필터링 (체크표시된 담당자)
+ .map((contact) => (
+ <div
+ key={contact.id}
+ className="flex items-center justify-between p-4 border rounded-lg bg-gray-50"
+ >
+ <div className="flex items-center gap-3">
+ <User className="size-4 text-muted-foreground" />
+ <div>
+ <div className="flex items-center gap-2">
+ <span className="font-medium">{contact.contactName}</span>
+ {contact.isPrimary && (
+ <Badge variant="secondary" className="text-xs">
+ 주담당자
+ </Badge>
+ )}
+ </div>
+ {contact.contactPosition && (
+ <p className="text-sm text-muted-foreground">
+ {contact.contactPosition}
+ </p>
+ )}
+ {contact.contactTitle && (
+ <p className="text-sm text-muted-foreground">
+ {contact.contactTitle}
+ </p>
+ )}
+ {contact.contactCountry && (
+ <p className="text-xs text-muted-foreground">
+ {contact.contactCountry}
+ </p>
)}
</div>
- {contact.contactPosition && (
- <p className="text-sm text-muted-foreground">
- {contact.contactPosition}
- </p>
- )}
- {contact.contactTitle && (
- <p className="text-sm text-muted-foreground">
- {contact.contactTitle}
- </p>
- )}
- {contact.contactCountry && (
- <p className="text-xs text-muted-foreground">
- {contact.contactCountry}
- </p>
- )}
- </div>
- </div>
-
- <div className="flex flex-col items-end gap-1 text-sm">
- <div className="flex items-center gap-1">
- <Mail className="size-4 text-muted-foreground" />
- <span>{contact.contactEmail}</span>
</div>
- {contact.contactPhone && (
+
+ <div className="flex flex-col items-end gap-1 text-sm">
<div className="flex items-center gap-1">
- <Phone className="size-4 text-muted-foreground" />
- <span>{contact.contactPhone}</span>
+ <Mail className="size-4 text-muted-foreground" />
+ <span>{contact.contactEmail}</span>
+ </div>
+ {contact.contactPhone && (
+ <div className="flex items-center gap-1">
+ <Phone className="size-4 text-muted-foreground" />
+ <span>{contact.contactPhone}</span>
+ </div>
+ )}
+ <div className="text-xs text-muted-foreground">
+ 발송일: {new Date(contact.createdAt).toLocaleDateString('ko-KR')}
</div>
- )}
- <div className="text-xs text-muted-foreground">
- 발송일: {new Date(contact.createdAt).toLocaleDateString('ko-KR')}
</div>
</div>
- </div>
- ))}
-
+ ))}
+
<div className="text-center pt-4 text-sm text-muted-foreground border-t">
- 총 {contacts.length}명의 담당자에게 발송됨
+ 총 {contacts.filter((contact) => contact.contactTitle).length}명의 담당자에게 발송됨
</div>
</div>
)}
diff --git a/lib/techsales-rfq/table/detail-table/quotation-history-dialog.tsx b/lib/techsales-rfq/table/detail-table/quotation-history-dialog.tsx
index 7d972b91..023d3599 100644
--- a/lib/techsales-rfq/table/detail-table/quotation-history-dialog.tsx
+++ b/lib/techsales-rfq/table/detail-table/quotation-history-dialog.tsx
@@ -78,6 +78,7 @@ interface QuotationHistoryDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
quotationId: number | null
+ isInternal?: boolean // 내부 사용자인지 여부 (partners가 아니면 내부)
}
const statusConfig = {
@@ -88,15 +89,16 @@ const statusConfig = {
"Rejected": { label: "거절됨", color: "bg-red-100 text-red-800" },
}
-function QuotationCard({
- data,
- version,
- isCurrent = false,
- revisedBy,
+function QuotationCard({
+ data,
+ version,
+ isCurrent = false,
+ revisedBy,
revisedAt,
attachments,
revisionId,
revisionNote,
+ isInternal = false,
}: {
data: QuotationSnapshot | QuotationHistoryData["current"]
version: number
@@ -106,6 +108,7 @@ function QuotationCard({
attachments?: QuotationAttachment[]
revisionId?: number
revisionNote?: string | null
+ isInternal?: boolean
}) {
const statusInfo = statusConfig[data.status as keyof typeof statusConfig] ||
{ label: data.status || "알 수 없음", color: "bg-gray-100 text-gray-800" }
@@ -171,7 +174,7 @@ function QuotationCard({
</div>
)}
- {revisionId && (
+ {revisionId && isInternal && (
<div>
<p className="text-sm font-medium text-muted-foreground mt-2">SHI Comment</p>
<textarea
@@ -241,10 +244,11 @@ function QuotationCard({
)
}
-export function QuotationHistoryDialog({
- open,
- onOpenChange,
- quotationId
+export function QuotationHistoryDialog({
+ open,
+ onOpenChange,
+ quotationId,
+ isInternal = false
}: QuotationHistoryDialogProps) {
const [data, setData] = useState<QuotationHistoryData | null>(null)
const [isLoading, setIsLoading] = useState(false)
@@ -312,6 +316,7 @@ export function QuotationHistoryDialog({
version={data.current.quotationVersion || 1}
isCurrent={true}
attachments={data.current.attachments}
+ isInternal={isInternal}
/>
{/* 이전 버전들 (스냅샷) - SHI Comment 포함 */}
@@ -326,6 +331,7 @@ export function QuotationHistoryDialog({
attachments={revision.attachments}
revisionId={revision.id}
revisionNote={revision.revisionNote}
+ isInternal={isInternal}
/>
))
) : (
diff --git a/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx b/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx
index fe6f84e0..8ce55d56 100644
--- a/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx
+++ b/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx
@@ -781,6 +781,7 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps
open={historyDialogOpen}
onOpenChange={setHistoryDialogOpen}
quotationId={selectedQuotationId}
+ isInternal={true}
/>
{/* 견적서 첨부파일 Sheet */}