summaryrefslogtreecommitdiff
path: root/lib/general-contracts/main/create-general-contract-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/general-contracts/main/create-general-contract-dialog.tsx')
-rw-r--r--lib/general-contracts/main/create-general-contract-dialog.tsx479
1 files changed, 479 insertions, 0 deletions
diff --git a/lib/general-contracts/main/create-general-contract-dialog.tsx b/lib/general-contracts/main/create-general-contract-dialog.tsx
new file mode 100644
index 00000000..b2f538c3
--- /dev/null
+++ b/lib/general-contracts/main/create-general-contract-dialog.tsx
@@ -0,0 +1,479 @@
+"use client"
+
+import * as React from "react"
+import { useRouter } from "next/navigation"
+import { useSession } from "next-auth/react"
+import { Plus } from "lucide-react"
+import { toast } from "sonner"
+import { Button } from "@/components/ui/button"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Textarea } from "@/components/ui/textarea"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { Calendar } from "@/components/ui/calendar"
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
+import { CalendarIcon } from "lucide-react"
+import { format } from "date-fns"
+import { ko } from "date-fns/locale"
+import { cn } from "@/lib/utils"
+import { createContract, getVendors } from "@/lib/general-contracts/service"
+import {
+ GENERAL_CONTRACT_CATEGORIES,
+ GENERAL_CONTRACT_TYPES,
+ GENERAL_EXECUTION_METHODS,
+ GENERAL_CONTRACT_SCOPES
+} from "@/lib/general-contracts/types"
+
+interface CreateContractForm {
+ contractNumber: string
+ name: string
+ category: string
+ type: string
+ executionMethod: string
+ selectionMethod: string
+ vendorId: number | null
+ startDate: Date | undefined
+ endDate: Date | undefined
+ validityEndDate: Date | undefined
+ // contractScope: string
+ // specificationType: string
+ notes: string
+ linkedRfqOrItb: string
+ linkedBidNumber: string
+ linkedPoNumber: string
+}
+
+export function CreateGeneralContractDialog() {
+ const router = useRouter()
+ const { data: session } = useSession()
+ const [open, setOpen] = React.useState(false)
+ const [isLoading, setIsLoading] = React.useState(false)
+ const [vendors, setVendors] = React.useState<Array<{ id: number; vendorName: string; vendorCode: string | null }>>([])
+
+ const [form, setForm] = React.useState<CreateContractForm>({
+ contractNumber: '',
+ name: '',
+ category: '',
+ type: '',
+ executionMethod: '',
+ selectionMethod: '',
+ vendorId: null,
+ startDate: undefined,
+ endDate: undefined,
+ validityEndDate: undefined,
+ // contractScope: '',
+ // specificationType: '',
+ notes: '',
+ linkedRfqOrItb: '',
+ linkedBidNumber: '',
+ linkedPoNumber: '',
+ })
+
+ // 업체 목록 조회
+ React.useEffect(() => {
+ const fetchVendors = async () => {
+ try {
+ const vendorList = await getVendors()
+ setVendors(vendorList)
+ } catch (error) {
+ console.error('Error fetching vendors:', error)
+ }
+ }
+ fetchVendors()
+ }, [])
+
+ const generateContractNumber = () => {
+ const now = new Date()
+ const year = now.getFullYear()
+ const month = String(now.getMonth() + 1).padStart(2, '0')
+ const day = String(now.getDate()).padStart(2, '0')
+ const time = String(now.getHours()).padStart(2, '0') + String(now.getMinutes()).padStart(2, '0')
+ return `CT${year}${month}${day}${time}`
+ }
+
+ const handleSubmit = async () => {
+ // 필수 필드 검증
+ if (!form.name || !form.category || !form.type || !form.executionMethod ||
+ !form.vendorId || !form.startDate || !form.endDate) {
+ toast.error("필수 항목을 모두 입력해주세요.")
+ return
+ }
+
+ if (!form.validityEndDate) {
+ setForm(prev => ({ ...prev, validityEndDate: form.endDate }))
+ }
+
+ try {
+ setIsLoading(true)
+
+ const contractData = {
+ contractNumber: generateContractNumber(),
+ name: form.name,
+ category: form.category,
+ type: form.type,
+ executionMethod: form.executionMethod,
+ selectionMethod: form.selectionMethod,
+ vendorId: form.vendorId!,
+ startDate: form.startDate!.toISOString().split('T')[0],
+ endDate: form.endDate!.toISOString().split('T')[0],
+ validityEndDate: (form.validityEndDate || form.endDate!).toISOString().split('T')[0],
+ // contractScope: form.contractScope,
+ // specificationType: form.specificationType,
+ status: 'Draft',
+ registeredById: session?.user?.id ? parseInt(session.user.id) : 3,
+ lastUpdatedById: session?.user?.id ? parseInt(session.user.id) : 3,
+ notes: form.notes,
+ linkedRfqOrItb: form.linkedRfqOrItb,
+ linkedBidNumber: form.linkedBidNumber,
+ linkedPoNumber: form.linkedPoNumber,
+ }
+
+ const newContract = await createContract(contractData)
+
+ toast.success("새 계약이 생성되었습니다.")
+ setOpen(false)
+ resetForm()
+
+ // 상세 페이지로 이동
+ router.refresh()
+ } catch (error) {
+ console.error('Error creating contract:', error)
+ toast.error("계약 생성 중 오류가 발생했습니다.")
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ const resetForm = () => {
+ setForm({
+ contractNumber: '',
+ name: '',
+ category: '',
+ type: '',
+ executionMethod: '',
+ selectionMethod: '',
+ vendorId: null,
+ startDate: undefined,
+ endDate: undefined,
+ validityEndDate: undefined,
+ // contractScope: '',
+ // specificationType: '',
+ notes: '',
+ linkedRfqOrItb: '',
+ linkedBidNumber: '',
+ linkedPoNumber: '',
+ })
+ }
+
+ return (
+ <Dialog open={open} onOpenChange={(newOpen) => {
+ setOpen(newOpen)
+ if (!newOpen) resetForm()
+ }}>
+ <DialogTrigger asChild>
+ <Button size="sm">
+ <Plus className="mr-2 h-4 w-4" />
+ 신규등록
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
+ <DialogHeader>
+ <DialogTitle>새 계약 등록</DialogTitle>
+ <DialogDescription>
+ 새로운 계약의 기본 정보를 입력하세요.
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="grid gap-4 py-4">
+ <div className="grid grid-cols-1 gap-4">
+ {/* <div className="grid gap-2">
+ <Label htmlFor="contractNumber">계약번호</Label>
+ <Input
+ id="contractNumber"
+ value={form.contractNumber}
+ onChange={(e) => setForm(prev => ({ ...prev, contractNumber: e.target.value }))}
+ placeholder="자동 생성됩니다"
+ />
+ </div> */}
+
+ <div className="grid gap-2">
+ <Label htmlFor="name">계약명 *</Label>
+ <Input
+ id="name"
+ value={form.name}
+ onChange={(e) => setForm(prev => ({ ...prev, name: e.target.value }))}
+ placeholder="계약명을 입력하세요"
+ />
+ </div>
+ </div>
+
+ <div className="grid grid-cols-3 gap-4">
+ <div className="grid gap-2">
+ <Label htmlFor="category">계약구분 *</Label>
+ <Select value={form.category} onValueChange={(value) => setForm(prev => ({ ...prev, category: value }))}>
+ <SelectTrigger>
+ <SelectValue placeholder="계약구분 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ {GENERAL_CONTRACT_CATEGORIES.map((category) => (
+ <SelectItem key={category} value={category}>
+ {category}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+
+ <div className="grid gap-2">
+ <Label htmlFor="type">계약종류 *</Label>
+ <Select value={form.type} onValueChange={(value) => setForm(prev => ({ ...prev, type: value }))}>
+ <SelectTrigger>
+ <SelectValue placeholder="계약종류 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ {GENERAL_CONTRACT_TYPES.map((type) => {
+ const typeLabels = {
+ 'UP': '자재단가계약',
+ 'LE': '임대차계약',
+ 'IL': '개별운송계약',
+ 'AL': '연간운송계약',
+ 'OS': '외주용역계약',
+ 'OW': '도급계약',
+ 'IS': '검사계약',
+ 'LO': 'LOI',
+ 'FA': 'FA',
+ 'SC': '납품합의계약',
+ 'OF': '클레임상계계약',
+ 'AW': '사전작업합의',
+ 'AD': '사전납품합의',
+ 'AM': '설계계약',
+ 'SC_SELL': '폐기물매각계약'
+ }
+ return (
+ <SelectItem key={type} value={type}>
+ {type} - {typeLabels[type as keyof typeof typeLabels]}
+ </SelectItem>
+ )
+ })}
+ </SelectContent>
+ </Select>
+ </div>
+
+ <div className="grid gap-2">
+ <Label htmlFor="executionMethod">체결방식 *</Label>
+ <Select value={form.executionMethod} onValueChange={(value) => setForm(prev => ({ ...prev, executionMethod: value }))}>
+ <SelectTrigger>
+ <SelectValue placeholder="체결방식 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ {GENERAL_EXECUTION_METHODS.map((method) => (
+ <SelectItem key={method} value={method}>
+ {method}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+ </div>
+
+ <div className="grid gap-2">
+ <Label htmlFor="vendor">협력업체 *</Label>
+ <Select value={form.vendorId?.toString()} onValueChange={(value) => setForm(prev => ({ ...prev, vendorId: parseInt(value) }))}>
+ <SelectTrigger>
+ <SelectValue placeholder="협력업체 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ {vendors.map((vendor) => (
+ <SelectItem key={vendor.id} value={vendor.id.toString()}>
+ {vendor.vendorName} ({vendor.vendorCode})
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+
+ <div className="grid grid-cols-3 gap-4">
+ <div className="grid gap-2">
+ <Label>계약시작일 *</Label>
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button
+ variant="outline"
+ className={cn(
+ "justify-start text-left font-normal",
+ !form.startDate && "text-muted-foreground"
+ )}
+ >
+ <CalendarIcon className="mr-2 h-4 w-4" />
+ {form.startDate ? format(form.startDate, "yyyy-MM-dd", { locale: ko }) : "날짜 선택"}
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-auto p-0">
+ <Calendar
+ mode="single"
+ selected={form.startDate}
+ onSelect={(date) => setForm(prev => ({ ...prev, startDate: date }))}
+ initialFocus
+ />
+ </PopoverContent>
+ </Popover>
+ </div>
+
+ <div className="grid gap-2">
+ <Label>계약종료일 *</Label>
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button
+ variant="outline"
+ className={cn(
+ "justify-start text-left font-normal",
+ !form.endDate && "text-muted-foreground"
+ )}
+ >
+ <CalendarIcon className="mr-2 h-4 w-4" />
+ {form.endDate ? format(form.endDate, "yyyy-MM-dd", { locale: ko }) : "날짜 선택"}
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-auto p-0">
+ <Calendar
+ mode="single"
+ selected={form.endDate}
+ onSelect={(date) => setForm(prev => ({ ...prev, endDate: date }))}
+ initialFocus
+ />
+ </PopoverContent>
+ </Popover>
+ </div>
+
+ <div className="grid gap-2">
+ <Label>유효기간종료일</Label>
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button
+ variant="outline"
+ className={cn(
+ "justify-start text-left font-normal",
+ !form.validityEndDate && "text-muted-foreground"
+ )}
+ >
+ <CalendarIcon className="mr-2 h-4 w-4" />
+ {form.validityEndDate ? format(form.validityEndDate, "yyyy-MM-dd", { locale: ko }) : "날짜 선택"}
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-auto p-0">
+ <Calendar
+ mode="single"
+ selected={form.validityEndDate}
+ onSelect={(date) => setForm(prev => ({ ...prev, validityEndDate: date }))}
+ initialFocus
+ />
+ </PopoverContent>
+ </Popover>
+ </div>
+ </div>
+
+ {/* <div className="grid grid-cols-2 gap-4">
+ <div className="grid gap-2">
+ <Label htmlFor="contractScope">계약확정범위 *</Label>
+ <Select value={form.contractScope} onValueChange={(value) => setForm(prev => ({ ...prev, contractScope: value }))}>
+ <SelectTrigger>
+ <SelectValue placeholder="계약확정범위 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ {GENERAL_CONTRACT_SCOPES.map((scope) => (
+ <SelectItem key={scope} value={scope}>
+ {scope}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+
+ <div className="grid gap-2">
+ <Label htmlFor="specificationType">사양 *</Label>
+ <Select value={form.specificationType} onValueChange={(value) => setForm(prev => ({ ...prev, specificationType: value }))}>
+ <SelectTrigger>
+ <SelectValue placeholder="사양 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ <SelectItem value="첨부파일">첨부파일</SelectItem>
+ <SelectItem value="직접입력">직접입력</SelectItem>
+ <SelectItem value="기타">기타</SelectItem>
+ </SelectContent>
+ </Select>
+ </div>
+ </div> */}
+
+ <div className="grid grid-cols-2 gap-4">
+ <div className="grid gap-2">
+ <Label htmlFor="linkedRfqOrItb">연계 견적/입찰번호</Label>
+ <Input
+ id="linkedRfqOrItb"
+ value={form.linkedRfqOrItb}
+ onChange={(e) => setForm(prev => ({ ...prev, linkedRfqOrItb: e.target.value }))}
+ placeholder="연계 견적/입찰번호를 입력하세요"
+ />
+ </div>
+
+ <div className="grid gap-2">
+ <Label htmlFor="linkedBidNumber">연계 BID번호</Label>
+ <Input
+ id="linkedBidNumber"
+ value={form.linkedBidNumber}
+ onChange={(e) => setForm(prev => ({ ...prev, linkedBidNumber: e.target.value }))}
+ placeholder="연계 BID번호를 입력하세요"
+ />
+ </div>
+ </div>
+
+ <div className="grid gap-2">
+ <Label htmlFor="linkedPoNumber">연계PO번호</Label>
+ <Input
+ id="linkedPoNumber"
+ value={form.linkedPoNumber}
+ onChange={(e) => setForm(prev => ({ ...prev, linkedPoNumber: e.target.value }))}
+ placeholder="연계PO번호를 입력하세요"
+ />
+ </div>
+
+ <div className="grid gap-2">
+ <Label htmlFor="notes">비고</Label>
+ <Textarea
+ id="notes"
+ value={form.notes}
+ onChange={(e) => setForm(prev => ({ ...prev, notes: e.target.value }))}
+ placeholder="비고사항을 입력하세요"
+ rows={3}
+ />
+ </div>
+ </div>
+
+ <DialogFooter>
+ <Button
+ type="button"
+ variant="outline"
+ onClick={() => setOpen(false)}
+ >
+ 취소
+ </Button>
+ <Button
+ type="button"
+ onClick={handleSubmit}
+ disabled={isLoading}
+ >
+ {isLoading ? '생성 중...' : '생성'}
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
+}