'use client' import * as React from 'react' import { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Label } from '@/components/ui/label' import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group' import { Input } from '@/components/ui/input' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { ScrollArea } from '@/components/ui/scroll-area' import { Calendar, Users, Clock, CheckCircle, XCircle, Download, } from 'lucide-react' import { formatDate } from '@/lib/utils' import { updatePartnerAttendance, getSpecificationMeetingForPartners } from '../detail/service' import { useToast } from '@/hooks/use-toast' import { useTransition } from 'react' import { useRouter } from 'next/navigation' interface PartnersSpecificationMeetingDialogProps { biddingDetail: { id: number biddingNumber: string title: string preQuoteDate: string | null biddingRegistrationDate: string | null evaluationDate: string | null hasSpecificationMeeting?: boolean // 사양설명회 여부 추가 } | null biddingCompanyId: number isAttending: boolean | null open: boolean onOpenChange: (open: boolean) => void } export function PartnersSpecificationMeetingDialog({ biddingDetail, biddingCompanyId, isAttending, open, onOpenChange, }: PartnersSpecificationMeetingDialogProps) { const { toast } = useToast() const [isPending, startTransition] = useTransition() const [isLoading, setIsLoading] = React.useState(false) const [meetingData, setMeetingData] = React.useState(null) const router = useRouter() // 폼 상태 const [attendance, setAttendance] = React.useState('') const [attendeeCount, setAttendeeCount] = React.useState('') const [representativeName, setRepresentativeName] = React.useState('') const [representativePhone, setRepresentativePhone] = React.useState('') // 사양설명회 정보 가져오기 React.useEffect(() => { if (open && biddingDetail) { fetchMeetingData() } }, [open, biddingDetail]) const fetchMeetingData = async () => { if (!biddingDetail) return setIsLoading(true) try { const result = await getSpecificationMeetingForPartners(biddingDetail.id) if (result.success) { setMeetingData(result.data) } else { toast({ title: '오류', description: result.error, variant: 'destructive', }) } } catch (error) { console.error('사양설명회 정보를 불러오는데 실패했습니다.', error) toast({ title: '오류', description: '사양설명회 정보를 불러오는데 실패했습니다.', variant: 'destructive', }) } finally { setIsLoading(false) } } // 다이얼로그 열릴 때 기존 값으로 초기화 React.useEffect(() => { if (open) { if (isAttending === true) { setAttendance('attending') } else if (isAttending === false) { setAttendance('not_attending') } else { setAttendance('') } setAttendeeCount('') setRepresentativeName('') setRepresentativePhone('') } }, [open, isAttending]) const handleSubmit = () => { if (!attendance) { toast({ title: '선택 필요', description: '참석 여부를 선택해주세요.', variant: 'destructive', }) return } // 참석하는 경우 필수 정보 체크 if (attendance === 'attending') { if (!attendeeCount || !representativeName || !representativePhone) { toast({ title: '필수 정보 누락', description: '참석인원수, 참석자 대표 이름, 연락처를 모두 입력해주세요.', variant: 'destructive', }) return } const countNum = parseInt(attendeeCount) if (isNaN(countNum) || countNum < 1) { toast({ title: '잘못된 입력', description: '참석인원수는 1 이상의 숫자를 입력해주세요.', variant: 'destructive', }) return } } startTransition(async () => { const attendanceData = { isAttending: attendance === 'attending', attendeeCount: attendance === 'attending' ? parseInt(attendeeCount) : undefined, representativeName: attendance === 'attending' ? representativeName : undefined, representativePhone: attendance === 'attending' ? representativePhone : undefined, } const result = await updatePartnerAttendance( biddingCompanyId, attendanceData ) if (result.success) { toast({ title: '성공', description: result.message, }) // 참석하는 경우 이메일 발송 알림 if (attendance === 'attending' && result.data) { toast({ title: '참석 알림 발송', description: '사양설명회 담당자에게 참석 알림이 발송되었습니다.', }) } router.refresh() onOpenChange(false) } else { toast({ title: '오류', description: result.error, variant: 'destructive', }) } }) } const handleFileDownload = async (filePath: string, fileName: string) => { try { const { downloadFile } = await import('@/lib/file-download') await downloadFile(filePath, fileName) } catch (error) { console.error('파일 다운로드 실패:', error) toast({ title: '다운로드 실패', description: '파일 다운로드 중 오류가 발생했습니다.', variant: 'destructive', }) } } if (!biddingDetail) return null // 사양설명회가 없는 경우 if (biddingDetail.hasSpecificationMeeting === false) { return ( 사양설명회 정보

사양설명회 없음

해당 입찰 건은 사양설명회가 없습니다.

) } return ( 사양설명회 {biddingDetail.title}의 사양설명회 정보 및 참석 여부를 확인해주세요. {isLoading ? (

로딩 중...

) : (
{/* 사양설명회 기본 정보 */} {meetingData && ( 설명회 정보
설명회 일정: {meetingData.meetingDate ? formatDate(meetingData.meetingDate, 'KR') : '미정'}
설명회 담당자:
{meetingData.contactPerson && (
{meetingData.contactPerson}
)} {meetingData.contactEmail && (
{meetingData.contactEmail}
)} {meetingData.contactPhone && (
{meetingData.contactPhone}
)}
설명회 자료:
{meetingData.documents && meetingData.documents.length > 0 ? ( meetingData.documents.map((doc: any) => ( )) ) : ( 첨부파일 없음 )}
)} {/* 참석 여부 입력 폼 */} 참석 여부
{attendance === 'attending' && (
setAttendeeCount(e.target.value)} placeholder="명" className="h-8 text-sm" />
setRepresentativeName(e.target.value)} placeholder="홍길동 과장" className="h-8 text-sm" />
setRepresentativePhone(e.target.value)} placeholder="010-0000-0000" className="h-8 text-sm" />
)}
{/* 현재 상태 표시 */} {isAttending !== null && (
현재 상태: {isAttending ? '참석' : '불참'} ({formatDate(new Date().toISOString(), 'KR')} 기준)
)}
)}
) }