summaryrefslogtreecommitdiff
path: root/lib/bidding/detail/table/bidding-invitation-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/detail/table/bidding-invitation-dialog.tsx')
-rw-r--r--lib/bidding/detail/table/bidding-invitation-dialog.tsx337
1 files changed, 11 insertions, 326 deletions
diff --git a/lib/bidding/detail/table/bidding-invitation-dialog.tsx b/lib/bidding/detail/table/bidding-invitation-dialog.tsx
index ffb1fcb3..582622d9 100644
--- a/lib/bidding/detail/table/bidding-invitation-dialog.tsx
+++ b/lib/bidding/detail/table/bidding-invitation-dialog.tsx
@@ -33,7 +33,6 @@ import {
} from 'lucide-react'
import { getExistingBasicContractsForBidding } from '../../pre-quote/service'
import { getActiveContractTemplates } from '../../service'
-import { getVendorContacts } from '@/lib/vendors/service'
import { useToast } from '@/hooks/use-toast'
import { useTransition } from 'react'
import { SelectTrigger } from '@/components/ui/select'
@@ -269,47 +268,23 @@ export function BiddingInvitationDialog({
}));
setSelectedContracts(initialSelected);
- // 벤더 담당자 정보 병렬로 가져오기
- const vendorContactsPromises = selectedVendors.map(vendor =>
- getVendorContacts({
- page: 1,
- perPage: 100,
- flags: [],
- sort: [],
- filters: [],
- joinOperator: 'and',
- search: '',
- contactName: '',
- contactPosition: '',
- contactEmail: '',
- contactPhone: ''
- }, vendor.vendorId)
- .then(result => ({
- vendorId: vendor.vendorId,
- contacts: (result.data || []).map(contact => ({
- id: contact.id,
- contactName: contact.contactName,
- contactEmail: contact.contactEmail,
- contactPhone: contact.contactPhone,
- contactPosition: contact.contactPosition,
- contactDepartment: contact.contactDepartment
- }))
- }))
- .catch(() => ({
- vendorId: vendor.vendorId,
- contacts: []
- }))
- );
-
- const vendorContactsResults = await Promise.all(vendorContactsPromises);
- const vendorContactsMap = new Map(vendorContactsResults.map(result => [result.vendorId, result.contacts]));
+ // 담당자 정보는 selectedVendors에 이미 포함되어 있음
// vendorData 초기화 (담당자 정보 포함)
const initialVendorData: VendorWithContactInfo[] = selectedVendors.map(vendor => {
const hasExistingContract = typedContracts.some((ec) =>
ec.vendorId === vendor.vendorId && ec.biddingCompanyId === vendor.biddingCompanyId
);
- const vendorContacts = vendorContactsMap.get(vendor.vendorId) || [];
+
+ // contacts 정보가 이미 selectedVendors에 포함되어 있음
+ const vendorContacts = (vendor.contacts || []).map(contact => ({
+ id: contact.id,
+ contactName: contact.contactName,
+ contactEmail: contact.contactEmail,
+ contactPhone: contact.contactNumber,
+ contactPosition: null,
+ contactDepartment: null
+ }));
// 주 수신자 기본값: 벤더의 기본 이메일 (vendorEmail)
const defaultEmail = vendor.vendorEmail || (vendorContacts.length > 0 ? vendorContacts[0].contactEmail : '');
@@ -569,296 +544,6 @@ export function BiddingInvitationDialog({
)}
{/* 대상 업체 정보 - 테이블 형식 */}
- <div className="space-y-4">
- <div className="flex items-center justify-between">
- <div className="flex items-center gap-2 text-sm font-medium">
- <Building2 className="h-4 w-4" />
- 초대 대상 업체 ({vendorData.length})
- </div>
- <Badge variant="outline" className="flex items-center gap-1">
- <Users className="h-3 w-3" />
- 총 {totalRecipientCount}명
- </Badge>
- </div>
-
- {vendorData.length === 0 ? (
- <div className="text-center py-6 text-muted-foreground border rounded-lg">
- 초대 가능한 업체가 없습니다.
- </div>
- ) : (
- <div className="border rounded-lg overflow-hidden">
- <table className="w-full">
- <thead className="bg-muted/50 border-b">
- <tr>
- <th className="text-left p-2 text-xs font-medium">No.</th>
- <th className="text-left p-2 text-xs font-medium">업체명</th>
- <th className="text-left p-2 text-xs font-medium">주 수신자</th>
- <th className="text-left p-2 text-xs font-medium">CC</th>
- <th className="text-left p-2 text-xs font-medium">작업</th>
- </tr>
- </thead>
- <tbody>
- {vendorData.map((vendor, index) => {
- const allContacts = vendor.contacts || [];
- const allEmails = [
- // 벤더의 기본 이메일을 첫 번째로 표시
- ...(vendor.vendorEmail ? [{
- value: vendor.vendorEmail,
- label: `${vendor.vendorEmail}`,
- email: vendor.vendorEmail,
- type: 'vendor' as const
- }] : []),
- // 담당자 이메일들
- ...allContacts.map(c => ({
- value: c.contactEmail,
- label: `${c.contactName} ${c.contactPosition ? `(${c.contactPosition})` : ''}`,
- email: c.contactEmail,
- type: 'contact' as const
- })),
- // 커스텀 이메일들
- ...vendor.customEmails.map(c => ({
- value: c.email,
- label: c.name || c.email,
- email: c.email,
- type: 'custom' as const
- }))
- ];
-
- const ccEmails = allEmails.filter(e => e.value !== vendor.selectedMainEmail);
- const selectedMainEmailInfo = allEmails.find(e => e.value === vendor.selectedMainEmail);
- const isFormOpen = showCustomEmailForm[vendor.vendorId];
-
- return (
- <React.Fragment key={vendor.vendorId}>
- <tr className="border-b hover:bg-muted/20">
- <td className="p-2">
- <div className="flex items-center gap-1">
- <div className="flex items-center justify-center w-5 h-5 rounded-full bg-primary/10 text-primary text-xs font-medium">
- {index + 1}
- </div>
- </div>
- </td>
- <td className="p-2">
- <div className="space-y-1">
- <div className="font-medium text-sm">{vendor.vendorName}</div>
- <div className="flex items-center gap-1">
- <Badge variant="outline" className="text-xs">
- {vendor.vendorCountry || vendor.vendorCode}
- </Badge>
- </div>
- </div>
- </td>
- <td className="p-2">
- <Select
- value={vendor.selectedMainEmail}
- onValueChange={(value) => updateVendor(vendor.vendorId, { selectedMainEmail: value })}
- >
- <SelectTrigger className="h-7 text-xs w-[200px]">
- <SelectValue placeholder="선택하세요">
- {selectedMainEmailInfo && (
- <div className="flex items-center gap-1">
- {selectedMainEmailInfo.type === 'custom' && <UserPlus className="h-3 w-3 text-green-500" />}
- <span className="truncate">{selectedMainEmailInfo.label}</span>
- </div>
- )}
- </SelectValue>
- </SelectTrigger>
- <SelectContent>
- {allEmails.map((email) => (
- <SelectItem key={email.value} value={email.value} className="text-xs">
- <div className="flex items-center gap-1">
- {email.type === 'custom' && <UserPlus className="h-3 w-3 text-green-500" />}
- <span>{email.label}</span>
- </div>
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- {!vendor.selectedMainEmail && (
- <span className="text-xs text-red-500">필수</span>
- )}
- </td>
- <td className="p-2">
- <Popover>
- <PopoverTrigger asChild>
- <Button variant="outline" className="h-7 text-xs">
- {vendor.additionalEmails.length > 0
- ? `${vendor.additionalEmails.length}명`
- : "선택"
- }
- <ChevronDown className="ml-1 h-3 w-3" />
- </Button>
- </PopoverTrigger>
- <PopoverContent className="w-48 p-2">
- <div className="max-h-48 overflow-y-auto space-y-1">
- {ccEmails.map((email) => (
- <div key={email.value} className="flex items-center space-x-1 p-1">
- <Checkbox
- checked={vendor.additionalEmails.includes(email.value)}
- onCheckedChange={() => toggleAdditionalEmail(vendor.vendorId, email.value)}
- className="h-3 w-3"
- />
- <label className="text-xs cursor-pointer flex-1 truncate">
- {email.label}
- </label>
- </div>
- ))}
- </div>
- </PopoverContent>
- </Popover>
- </td>
- <td className="p-2">
- <div className="flex items-center gap-1">
- <Button
- variant={isFormOpen ? "default" : "ghost"}
- size="sm"
- className="h-6 w-6 p-0"
- onClick={() => {
- setShowCustomEmailForm(prev => ({
- ...prev,
- [vendor.vendorId]: !prev[vendor.vendorId]
- }));
- }}
- >
- {isFormOpen ? <X className="h-3 w-3" /> : <Plus className="h-3 w-3" />}
- </Button>
- {vendor.customEmails.length > 0 && (
- <Badge variant="secondary" className="text-xs">
- +{vendor.customEmails.length}
- </Badge>
- )}
- </div>
- </td>
- </tr>
-
- {/* 인라인 수신자 추가 폼 */}
- {isFormOpen && (
- <tr className="bg-muted/10 border-b">
- <td colSpan={5} className="p-4">
- <div className="space-y-3">
- <div className="flex items-center justify-between mb-2">
- <div className="flex items-center gap-2 text-sm font-medium">
- <UserPlus className="h-4 w-4" />
- 수신자 추가 - {vendor.vendorName}
- </div>
- <Button
- variant="ghost"
- size="sm"
- className="h-6 w-6 p-0"
- onClick={() => setShowCustomEmailForm(prev => ({
- ...prev,
- [vendor.vendorId]: false
- }))}
- >
- <X className="h-3 w-3" />
- </Button>
- </div>
-
- <div className="flex gap-2 items-end">
- <div className="w-[150px]">
- <Label className="text-xs mb-1 block">이름 (선택)</Label>
- <Input
- placeholder="홍길동"
- className="h-8 text-sm"
- value={customEmailInputs[vendor.vendorId]?.name || ''}
- onChange={(e) => setCustomEmailInputs(prev => ({
- ...prev,
- [vendor.vendorId]: {
- ...prev[vendor.vendorId],
- name: e.target.value
- }
- }))}
- />
- </div>
- <div className="flex-1">
- <Label className="text-xs mb-1 block">이메일 <span className="text-red-500">*</span></Label>
- <Input
- type="email"
- placeholder="example@company.com"
- className="h-8 text-sm"
- value={customEmailInputs[vendor.vendorId]?.email || ''}
- onChange={(e) => setCustomEmailInputs(prev => ({
- ...prev,
- [vendor.vendorId]: {
- ...prev[vendor.vendorId],
- email: e.target.value
- }
- }))}
- onKeyPress={(e) => {
- if (e.key === 'Enter') {
- e.preventDefault();
- addCustomEmail(vendor.vendorId);
- }
- }}
- />
- </div>
- <Button
- size="sm"
- className="h-8 px-4"
- onClick={() => addCustomEmail(vendor.vendorId)}
- disabled={!customEmailInputs[vendor.vendorId]?.email}
- >
- <Plus className="h-3 w-3 mr-1" />
- 추가
- </Button>
- <Button
- variant="outline"
- size="sm"
- className="h-8 px-4"
- onClick={() => {
- setCustomEmailInputs(prev => ({
- ...prev,
- [vendor.vendorId]: { email: '', name: '' }
- }));
- setShowCustomEmailForm(prev => ({
- ...prev,
- [vendor.vendorId]: false
- }));
- }}
- >
- 취소
- </Button>
- </div>
-
- {/* 추가된 커스텀 이메일 목록 */}
- {vendor.customEmails.length > 0 && (
- <div className="mt-3 pt-3 border-t">
- <div className="text-xs text-muted-foreground mb-2">추가된 수신자 목록</div>
- <div className="grid grid-cols-2 xl:grid-cols-3 gap-2">
- {vendor.customEmails.map((custom) => (
- <div key={custom.id} className="flex items-center justify-between bg-background rounded-md p-2">
- <div className="flex items-center gap-2 min-w-0">
- <UserPlus className="h-3 w-3 text-green-500 flex-shrink-0" />
- <div className="min-w-0">
- <div className="text-sm font-medium truncate">{custom.name}</div>
- <div className="text-xs text-muted-foreground truncate">{custom.email}</div>
- </div>
- </div>
- <Button
- variant="ghost"
- size="sm"
- className="h-6 w-6 p-0 flex-shrink-0"
- onClick={() => removeCustomEmail(vendor.vendorId, custom.id)}
- >
- <X className="h-3 w-3" />
- </Button>
- </div>
- ))}
- </div>
- </div>
- )}
- </div>
- </td>
- </tr>
- )}
- </React.Fragment>
- );
- })}
- </tbody>
- </table>
- </div>
- )}
- </div>
<Separator />