summaryrefslogtreecommitdiff
path: root/lib/bidding/pre-quote/table/bidding-pre-quote-invitation-dialog.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-09-04 08:31:31 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-09-04 08:31:31 +0000
commitb67e36df49f067cbd5ba899f9fbcc755f38d4b4f (patch)
tree5a71c5960f90d988cd509e3ef26bff497a277661 /lib/bidding/pre-quote/table/bidding-pre-quote-invitation-dialog.tsx
parentb7f54b06c1ef9e619f5358fb0a5caad9703c8905 (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.tsx185
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>
+ )
+}