diff options
Diffstat (limited to 'lib/bidding/detail/table')
3 files changed, 15 insertions, 330 deletions
diff --git a/lib/bidding/detail/table/bidding-detail-vendor-edit-dialog.tsx b/lib/bidding/detail/table/bidding-detail-vendor-edit-dialog.tsx index 6e5481f4..5bc85fdb 100644 --- a/lib/bidding/detail/table/bidding-detail-vendor-edit-dialog.tsx +++ b/lib/bidding/detail/table/bidding-detail-vendor-edit-dialog.tsx @@ -22,7 +22,7 @@ interface BiddingDetailVendorEditDialogProps { open: boolean onOpenChange: (open: boolean) => void onSuccess: () => void - biddingAwardCount?: string // 낙찰수 정보 추가 + biddingAwardCount?: string // 낙찰업체 수 정보 추가 biddingStatus?: string // 입찰 상태 정보 추가 allVendors?: QuotationVendor[] // 전체 벤더 목록 추가 } @@ -55,7 +55,7 @@ export function BiddingDetailVendorEditDialog({ // vendor가 변경되면 폼 데이터 업데이트 React.useEffect(() => { if (vendor) { - // 낙찰수가 단수인 경우 발주비율을 100%로 자동 설정 + // 낙찰업체 수가 단수인 경우 발주비율을 100%로 자동 설정 const defaultAwardRatio = biddingAwardCount === 'single' ? 100 : (vendor.awardRatio || 0) setFormData({ awardRatio: defaultAwardRatio, 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 34ee690f..53fe05f9 100644 --- a/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx +++ b/lib/bidding/detail/table/bidding-detail-vendor-toolbar-actions.tsx @@ -286,14 +286,14 @@ export function BiddingDetailVendorToolbarActions({ bidding.status === 'bidding_disposal') && ( <div className="h-4 w-px bg-border mx-1" /> )} - <Button + {/* <Button variant="outline" size="sm" onClick={handleDocumentUpload} > <FileText className="mr-2 h-4 w-4" /> 입찰문서 등록 - </Button> + </Button> */} </div> 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 /> |
