diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-04 08:31:31 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-04 08:31:31 +0000 |
| commit | b67e36df49f067cbd5ba899f9fbcc755f38d4b4f (patch) | |
| tree | 5a71c5960f90d988cd509e3ef26bff497a277661 /lib/bidding/pre-quote/table/bidding-pre-quote-invitation-dialog.tsx | |
| parent | b7f54b06c1ef9e619f5358fb0a5caad9703c8905 (diff) | |
(대표님, 최겸, 임수민) 작업사항 커밋
Diffstat (limited to 'lib/bidding/pre-quote/table/bidding-pre-quote-invitation-dialog.tsx')
| -rw-r--r-- | lib/bidding/pre-quote/table/bidding-pre-quote-invitation-dialog.tsx | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/lib/bidding/pre-quote/table/bidding-pre-quote-invitation-dialog.tsx b/lib/bidding/pre-quote/table/bidding-pre-quote-invitation-dialog.tsx new file mode 100644 index 00000000..84824c1e --- /dev/null +++ b/lib/bidding/pre-quote/table/bidding-pre-quote-invitation-dialog.tsx @@ -0,0 +1,185 @@ +'use client' + +import * as React from 'react' +import { Button } from '@/components/ui/button' +import { Checkbox } from '@/components/ui/checkbox' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { Badge } from '@/components/ui/badge' +import { BiddingCompany } from './bidding-pre-quote-vendor-columns' +import { sendPreQuoteInvitations } from '../service' +import { useToast } from '@/hooks/use-toast' +import { useTransition } from 'react' +import { Mail, Building2 } from 'lucide-react' + +interface BiddingPreQuoteInvitationDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + companies: BiddingCompany[] + onSuccess: () => void +} + +export function BiddingPreQuoteInvitationDialog({ + open, + onOpenChange, + companies, + onSuccess +}: BiddingPreQuoteInvitationDialogProps) { + const { toast } = useToast() + const [isPending, startTransition] = useTransition() + const [selectedCompanyIds, setSelectedCompanyIds] = React.useState<number[]>([]) + + // 초대 가능한 업체들 (pending 상태인 업체들) + const invitableCompanies = companies.filter(company => + company.invitationStatus === 'pending' && company.companyName + ) + + const handleSelectAll = (checked: boolean) => { + if (checked) { + setSelectedCompanyIds(invitableCompanies.map(company => company.id)) + } else { + setSelectedCompanyIds([]) + } + } + + const handleSelectCompany = (companyId: number, checked: boolean) => { + if (checked) { + setSelectedCompanyIds(prev => [...prev, companyId]) + } else { + setSelectedCompanyIds(prev => prev.filter(id => id !== companyId)) + } + } + + const handleSendInvitations = () => { + if (selectedCompanyIds.length === 0) { + toast({ + title: '알림', + description: '초대를 발송할 업체를 선택해주세요.', + variant: 'default', + }) + return + } + + startTransition(async () => { + const response = await sendPreQuoteInvitations(selectedCompanyIds) + + if (response.success) { + toast({ + title: '성공', + description: response.message, + }) + setSelectedCompanyIds([]) + onOpenChange(false) + onSuccess() + } else { + toast({ + title: '오류', + description: response.error, + variant: 'destructive', + }) + } + }) + } + + const handleOpenChange = (open: boolean) => { + onOpenChange(open) + if (!open) { + setSelectedCompanyIds([]) + } + } + + return ( + <Dialog open={open} onOpenChange={handleOpenChange}> + <DialogContent className="sm:max-w-[600px]"> + <DialogHeader> + <DialogTitle className="flex items-center gap-2"> + <Mail className="w-5 h-5" /> + 사전견적 초대 발송 + </DialogTitle> + <DialogDescription> + 선택한 업체들에게 사전견적 요청을 발송합니다. + </DialogDescription> + </DialogHeader> + + <div className="py-4"> + {invitableCompanies.length === 0 ? ( + <div className="text-center py-8 text-muted-foreground"> + 초대 가능한 업체가 없습니다. + </div> + ) : ( + <> + {/* 전체 선택 */} + <div className="flex items-center space-x-2 mb-4 pb-2 border-b"> + <Checkbox + id="select-all" + checked={selectedCompanyIds.length === invitableCompanies.length} + onCheckedChange={handleSelectAll} + /> + <label htmlFor="select-all" className="font-medium"> + 전체 선택 ({invitableCompanies.length}개 업체) + </label> + </div> + + {/* 업체 목록 */} + <div className="space-y-3 max-h-80 overflow-y-auto"> + {invitableCompanies.map((company) => ( + <div key={company.id} className="flex items-center space-x-3 p-3 border rounded-lg"> + <Checkbox + id={`company-${company.id}`} + checked={selectedCompanyIds.includes(company.id)} + onCheckedChange={(checked) => handleSelectCompany(company.id, !!checked)} + /> + <div className="flex-1"> + <div className="flex items-center gap-2"> + <Building2 className="w-4 h-4" /> + <span className="font-medium">{company.companyName}</span> + <Badge variant="outline" className="text-xs"> + {company.companyCode} + </Badge> + </div> + {company.notes && ( + <p className="text-sm text-muted-foreground mt-1"> + {company.notes} + </p> + )} + </div> + <Badge variant="outline"> + 대기중 + </Badge> + </div> + ))} + </div> + + {selectedCompanyIds.length > 0 && ( + <div className="mt-4 p-3 bg-primary/5 rounded-lg"> + <p className="text-sm text-primary"> + <strong>{selectedCompanyIds.length}개 업체</strong>에 사전견적 초대를 발송합니다. + </p> + </div> + )} + </> + )} + </div> + + <DialogFooter> + <Button variant="outline" onClick={() => handleOpenChange(false)}> + 취소 + </Button> + <Button + onClick={handleSendInvitations} + disabled={isPending || selectedCompanyIds.length === 0} + > + <Mail className="w-4 h-4 mr-2" /> + 초대 발송 + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + ) +} |
