summaryrefslogtreecommitdiff
path: root/components/bidding/manage/bidding-basic-info-editor.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'components/bidding/manage/bidding-basic-info-editor.tsx')
-rw-r--r--components/bidding/manage/bidding-basic-info-editor.tsx472
1 files changed, 323 insertions, 149 deletions
diff --git a/components/bidding/manage/bidding-basic-info-editor.tsx b/components/bidding/manage/bidding-basic-info-editor.tsx
index 90923825..27a2c097 100644
--- a/components/bidding/manage/bidding-basic-info-editor.tsx
+++ b/components/bidding/manage/bidding-basic-info-editor.tsx
@@ -2,7 +2,7 @@
import * as React from 'react'
import { useForm } from 'react-hook-form'
-import { ChevronRight, Upload, FileText, Eye, User, Building, Calendar, DollarSign } from 'lucide-react'
+import { ChevronRight, Upload, FileText, Eye, User, Building, Calendar, DollarSign, Check, ChevronsUpDown } from 'lucide-react'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
@@ -25,6 +25,20 @@ import { Input } from '@/components/ui/input'
import { Textarea } from '@/components/ui/textarea'
import { Switch } from '@/components/ui/switch'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from '@/components/ui/command'
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@/components/ui/popover'
+import { cn } from '@/lib/utils'
// CreateBiddingInput 타입 정의가 없으므로 CreateBiddingSchema를 확장하여 사용합니다.
import { getBiddingById, updateBiddingBasicInfo, getBiddingConditions, getBiddingNotice, updateBiddingConditions, getBiddingNoticeTemplate } from '@/lib/bidding/service'
import { getPurchaseGroupCodes } from '@/components/common/selectors/purchase-group-code'
@@ -270,7 +284,7 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
}
// Procurement 데이터 로드
- const [paymentTermsData, incotermsData, shippingData, destinationData, purchaseGroupCodes, procurementManagers] = await Promise.all([
+ const [paymentTermsData, incotermsData, shippingData, destinationData] = await Promise.all([
getPaymentTermsForSelection().catch(() => []),
getIncotermsForSelection().catch(() => []),
getPlaceOfShippingForSelection().catch(() => []),
@@ -284,14 +298,20 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
DISPLAY_NAME: bidding.bidPicName || '',
PURCHASE_GROUP_CODE: bidding.bidPicCode || '',
user: {
- id: bidding.bidPicUserId || undefined,
+ id: bidding.bidPicId || undefined,
+ name: bidding.bidPicName || '',
+ email: '',
+ employeeNumber: null,
}
})
setSelectedSupplyPic({
DISPLAY_NAME: bidding.supplyPicName || '',
PROCUREMENT_MANAGER_CODE: bidding.supplyPicCode || '',
user: {
- id: bidding.supplyPicUserId || undefined,
+ id: bidding.supplyPicId || undefined,
+ name: bidding.supplyPicName || '',
+ email: '',
+ employeeNumber: null,
}
})
// // 입찰담당자 및 조달담당자 초기 선택값 설정
@@ -554,7 +574,7 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
<FormField control={form.control} name="biddingType" render={({ field }) => (
<FormItem>
<FormLabel>입찰유형</FormLabel>
- <Select onValueChange={field.onChange} value={field.value}>
+ <Select onValueChange={field.onChange} value={field.value} disabled={readonly}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="입찰유형 선택" />
@@ -575,7 +595,7 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
<FormField control={form.control} name="contractType" render={({ field }) => (
<FormItem>
<FormLabel>계약구분</FormLabel>
- <Select onValueChange={field.onChange} value={field.value}>
+ <Select onValueChange={field.onChange} value={field.value} disabled={readonly}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="계약구분 선택" />
@@ -603,7 +623,7 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
<FormItem>
<FormLabel>기타 입찰유형 <span className="text-red-500">*</span></FormLabel>
<FormControl>
- <Input placeholder="직접 입력하세요" {...field} />
+ <Input placeholder="직접 입력하세요" {...field} disabled={readonly} />
</FormControl>
<FormMessage />
</FormItem>
@@ -656,7 +676,7 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
<FormField control={form.control} name="awardCount" render={({ field }) => (
<FormItem>
<FormLabel>낙찰업체 수</FormLabel>
- <Select onValueChange={field.onChange} value={field.value}>
+ <Select onValueChange={field.onChange} value={field.value} disabled={readonly}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="낙찰업체 수 선택" />
@@ -691,6 +711,7 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
field.onChange(code.DISPLAY_NAME || '')
}}
placeholder="입찰담당자 선택"
+ disabled={readonly}
/>
</FormControl>
<FormMessage />
@@ -711,6 +732,7 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
field.onChange(manager.DISPLAY_NAME || '')
}}
placeholder="조달담당자 선택"
+ disabled={readonly}
/>
</FormControl>
<FormMessage />
@@ -723,7 +745,7 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
<Building className="h-3 w-3" />
구매조직 <span className="text-red-500">*</span>
</FormLabel>
- <Select onValueChange={field.onChange} value={field.value}>
+ <Select onValueChange={field.onChange} value={field.value} disabled={readonly}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="구매조직 선택" />
@@ -747,7 +769,7 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
<FormField control={form.control} name="currency" render={({ field }) => (
<FormItem>
<FormLabel>통화</FormLabel>
- <Select onValueChange={field.onChange} value={field.value}>
+ <Select onValueChange={field.onChange} value={field.value} disabled={readonly}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="통화 선택" />
@@ -770,7 +792,7 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
<FormField control={form.control} name="noticeType" render={({ field }) => (
<FormItem>
<FormLabel>구매유형 <span className="text-red-500">*</span></FormLabel>
- <Select onValueChange={field.onChange} value={field.value}>
+ <Select onValueChange={field.onChange} value={field.value} disabled={readonly}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="구매유형 선택" />
@@ -801,7 +823,13 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
계약기간 시작
</FormLabel>
<FormControl>
- <Input type="date" {...field} />
+ <Input
+ type="date"
+ {...field}
+ disabled={readonly}
+ min="1900-01-01"
+ max="2100-12-31"
+ />
</FormControl>
<FormMessage />
</FormItem>
@@ -814,7 +842,13 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
계약기간 종료
</FormLabel>
<FormControl>
- <Input type="date" {...field} />
+ <Input
+ type="date"
+ {...field}
+ disabled={readonly}
+ min="1900-01-01"
+ max="2100-12-31"
+ />
</FormControl>
<FormMessage />
</FormItem>
@@ -853,91 +887,173 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
{/* 1행: SHI 지급조건, SHI 매입부가가치세 */}
<div className="grid grid-cols-2 gap-4 mb-4">
- <div>
+ <div className="flex flex-col space-y-2">
<FormLabel>SHI 지급조건 <span className="text-red-500">*</span></FormLabel>
- <Select
- value={biddingConditions.paymentTerms}
- onValueChange={(value) => {
- setBiddingConditions(prev => ({
- ...prev,
- paymentTerms: value
- }))
- }}
- >
- <SelectTrigger>
- <SelectValue placeholder="지급조건 선택" />
- </SelectTrigger>
- <SelectContent>
- {paymentTermsOptions.length > 0 ? (
- paymentTermsOptions.map((option) => (
- <SelectItem key={option.code} value={option.code}>
- {option.code} {option.description && `(${option.description})`}
- </SelectItem>
- ))
- ) : (
- <SelectItem value="loading" disabled>
- 데이터를 불러오는 중...
- </SelectItem>
- )}
- </SelectContent>
- </Select>
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button
+ variant="outline"
+ role="combobox"
+ className={cn(
+ "justify-between",
+ !biddingConditions.paymentTerms && "text-muted-foreground"
+ )}
+ disabled={readonly}
+ >
+ {biddingConditions.paymentTerms
+ ? paymentTermsOptions.find((option) => option.code === biddingConditions.paymentTerms)
+ ? `${paymentTermsOptions.find((option) => option.code === biddingConditions.paymentTerms)?.code} ${paymentTermsOptions.find((option) => option.code === biddingConditions.paymentTerms)?.description ? `(${paymentTermsOptions.find((option) => option.code === biddingConditions.paymentTerms)?.description})` : ''}`
+ : "지급조건 선택"
+ : "지급조건 선택"}
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-[400px] p-0">
+ <Command>
+ <CommandInput placeholder="지급조건 검색..." />
+ <CommandList>
+ <CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
+ <CommandGroup>
+ {paymentTermsOptions.map((option) => (
+ <CommandItem
+ key={option.code}
+ value={`${option.code} ${option.description || ''}`}
+ onSelect={() => {
+ setBiddingConditions(prev => ({
+ ...prev,
+ paymentTerms: option.code
+ }))
+ }}
+ >
+ <Check
+ className={cn(
+ "mr-2 h-4 w-4",
+ option.code === biddingConditions.paymentTerms
+ ? "opacity-100"
+ : "opacity-0"
+ )}
+ />
+ {option.code} {option.description && `(${option.description})`}
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
</div>
- <div>
+ <div className="flex flex-col space-y-2">
<FormLabel>SHI 매입부가가치세 <span className="text-red-500">*</span></FormLabel>
- <Select
- value={biddingConditions.taxConditions}
- onValueChange={(value) => {
- setBiddingConditions(prev => ({
- ...prev,
- taxConditions: value
- }))
- }}
- >
- <SelectTrigger>
- <SelectValue placeholder="세금조건 선택" />
- </SelectTrigger>
- <SelectContent>
- {TAX_CONDITIONS.map((condition) => (
- <SelectItem key={condition.code} value={condition.code}>
- {condition.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button
+ variant="outline"
+ role="combobox"
+ className={cn(
+ "justify-between",
+ !biddingConditions.taxConditions && "text-muted-foreground"
+ )}
+ disabled={readonly}
+ >
+ {biddingConditions.taxConditions
+ ? TAX_CONDITIONS.find((condition) => condition.code === biddingConditions.taxConditions)?.name
+ : "세금조건 선택"}
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-[400px] p-0">
+ <Command>
+ <CommandInput placeholder="세금조건 검색..." />
+ <CommandList>
+ <CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
+ <CommandGroup>
+ {TAX_CONDITIONS.map((condition) => (
+ <CommandItem
+ key={condition.code}
+ value={`${condition.code} ${condition.name}`}
+ onSelect={() => {
+ setBiddingConditions(prev => ({
+ ...prev,
+ taxConditions: condition.code
+ }))
+ }}
+ >
+ <Check
+ className={cn(
+ "mr-2 h-4 w-4",
+ condition.code === biddingConditions.taxConditions
+ ? "opacity-100"
+ : "opacity-0"
+ )}
+ />
+ {condition.name}
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
</div>
</div>
{/* 2행: SHI 인도조건, SHI 인도조건2 */}
<div className="grid grid-cols-2 gap-4 mb-4">
- <div>
+ <div className="flex flex-col space-y-2">
<FormLabel>SHI 인도조건 <span className="text-red-500">*</span></FormLabel>
- <Select
- value={biddingConditions.incoterms}
- onValueChange={(value) => {
- setBiddingConditions(prev => ({
- ...prev,
- incoterms: value
- }))
- }}
- >
- <SelectTrigger>
- <SelectValue placeholder="인코텀즈 선택" />
- </SelectTrigger>
- <SelectContent>
- {incotermsOptions.length > 0 ? (
- incotermsOptions.map((option) => (
- <SelectItem key={option.code} value={option.code}>
- {option.code} {option.description && `(${option.description})`}
- </SelectItem>
- ))
- ) : (
- <SelectItem value="loading" disabled>
- 데이터를 불러오는 중...
- </SelectItem>
- )}
- </SelectContent>
- </Select>
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button
+ variant="outline"
+ role="combobox"
+ className={cn(
+ "justify-between",
+ !biddingConditions.incoterms && "text-muted-foreground"
+ )}
+ disabled={readonly}
+ >
+ {biddingConditions.incoterms
+ ? incotermsOptions.find((option) => option.code === biddingConditions.incoterms)
+ ? `${incotermsOptions.find((option) => option.code === biddingConditions.incoterms)?.code} ${incotermsOptions.find((option) => option.code === biddingConditions.incoterms)?.description ? `(${incotermsOptions.find((option) => option.code === biddingConditions.incoterms)?.description})` : ''}`
+ : "인코텀즈 선택"
+ : "인코텀즈 선택"}
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-[400px] p-0">
+ <Command>
+ <CommandInput placeholder="인코텀즈 검색..." />
+ <CommandList>
+ <CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
+ <CommandGroup>
+ {incotermsOptions.map((option) => (
+ <CommandItem
+ key={option.code}
+ value={`${option.code} ${option.description || ''}`}
+ onSelect={() => {
+ setBiddingConditions(prev => ({
+ ...prev,
+ incoterms: option.code
+ }))
+ }}
+ >
+ <Check
+ className={cn(
+ "mr-2 h-4 w-4",
+ option.code === biddingConditions.incoterms
+ ? "opacity-100"
+ : "opacity-0"
+ )}
+ />
+ {option.code} {option.description && `(${option.description})`}
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
</div>
<div>
@@ -951,70 +1067,123 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
incotermsOption: e.target.value
}))
}}
+ disabled={readonly}
/>
</div>
</div>
{/* 3행: SHI 선적지, SHI 하역지 */}
<div className="grid grid-cols-2 gap-4 mb-4">
- <div>
+ <div className="flex flex-col space-y-2">
<FormLabel>SHI 선적지</FormLabel>
- <Select
- value={biddingConditions.shippingPort}
- onValueChange={(value) => {
- setBiddingConditions(prev => ({
- ...prev,
- shippingPort: value
- }))
- }}
- >
- <SelectTrigger>
- <SelectValue placeholder="선적지 선택" />
- </SelectTrigger>
- <SelectContent>
- {shippingPlaces.length > 0 ? (
- shippingPlaces.map((place) => (
- <SelectItem key={place.code} value={place.code}>
- {place.code} {place.description && `(${place.description})`}
- </SelectItem>
- ))
- ) : (
- <SelectItem value="loading" disabled>
- 데이터를 불러오는 중...
- </SelectItem>
- )}
- </SelectContent>
- </Select>
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button
+ variant="outline"
+ role="combobox"
+ className={cn(
+ "justify-between",
+ !biddingConditions.shippingPort && "text-muted-foreground"
+ )}
+ disabled={readonly}
+ >
+ {biddingConditions.shippingPort
+ ? shippingPlaces.find((place) => place.code === biddingConditions.shippingPort)
+ ? `${shippingPlaces.find((place) => place.code === biddingConditions.shippingPort)?.code} ${shippingPlaces.find((place) => place.code === biddingConditions.shippingPort)?.description ? `(${shippingPlaces.find((place) => place.code === biddingConditions.shippingPort)?.description})` : ''}`
+ : "선적지 선택"
+ : "선적지 선택"}
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-[400px] p-0">
+ <Command>
+ <CommandInput placeholder="선적지 검색..." />
+ <CommandList>
+ <CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
+ <CommandGroup>
+ {shippingPlaces.map((place) => (
+ <CommandItem
+ key={place.code}
+ value={`${place.code} ${place.description || ''}`}
+ onSelect={() => {
+ setBiddingConditions(prev => ({
+ ...prev,
+ shippingPort: place.code
+ }))
+ }}
+ >
+ <Check
+ className={cn(
+ "mr-2 h-4 w-4",
+ place.code === biddingConditions.shippingPort
+ ? "opacity-100"
+ : "opacity-0"
+ )}
+ />
+ {place.code} {place.description && `(${place.description})`}
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
</div>
- <div>
+ <div className="flex flex-col space-y-2">
<FormLabel>SHI 하역지</FormLabel>
- <Select
- value={biddingConditions.destinationPort}
- onValueChange={(value) => {
- setBiddingConditions(prev => ({
- ...prev,
- destinationPort: value
- }))
- }}
- >
- <SelectTrigger>
- <SelectValue placeholder="하역지 선택" />
- </SelectTrigger>
- <SelectContent>
- {destinationPlaces.length > 0 ? (
- destinationPlaces.map((place) => (
- <SelectItem key={place.code} value={place.code}>
- {place.code} {place.description && `(${place.description})`}
- </SelectItem>
- ))
- ) : (
- <SelectItem value="loading" disabled>
- 데이터를 불러오는 중...
- </SelectItem>
- )}
- </SelectContent>
- </Select>
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button
+ variant="outline"
+ role="combobox"
+ className={cn(
+ "justify-between",
+ !biddingConditions.destinationPort && "text-muted-foreground"
+ )}
+ disabled={readonly}
+ >
+ {biddingConditions.destinationPort
+ ? destinationPlaces.find((place) => place.code === biddingConditions.destinationPort)
+ ? `${destinationPlaces.find((place) => place.code === biddingConditions.destinationPort)?.code} ${destinationPlaces.find((place) => place.code === biddingConditions.destinationPort)?.description ? `(${destinationPlaces.find((place) => place.code === biddingConditions.destinationPort)?.description})` : ''}`
+ : "하역지 선택"
+ : "하역지 선택"}
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-[400px] p-0">
+ <Command>
+ <CommandInput placeholder="하역지 검색..." />
+ <CommandList>
+ <CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
+ <CommandGroup>
+ {destinationPlaces.map((place) => (
+ <CommandItem
+ key={place.code}
+ value={`${place.code} ${place.description || ''}`}
+ onSelect={() => {
+ setBiddingConditions(prev => ({
+ ...prev,
+ destinationPort: place.code
+ }))
+ }}
+ >
+ <Check
+ className={cn(
+ "mr-2 h-4 w-4",
+ place.code === biddingConditions.destinationPort
+ ? "opacity-100"
+ : "opacity-0"
+ )}
+ />
+ {place.code} {place.description && `(${place.description})`}
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
</div>
</div>
@@ -1045,6 +1214,7 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
}))
}}
id="price-adjustment"
+ disabled={readonly}
/>
<FormLabel htmlFor="price-adjustment" className="text-sm">
{biddingConditions.isPriceAdjustmentApplicable ? "적용" : "미적용"}
@@ -1067,7 +1237,7 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
}))
}}
rows={3}
- readOnly={readonly}
+ disabled={readonly}
/>
</div>
</div>
@@ -1135,15 +1305,16 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
}}
onDropRejected={() => {
toast({
- title: "File upload rejected",
- description: "Please check file size and type.",
+ title: "파일 업로드 거부",
+ description: "파일 크기와 유형을 확인해주세요.",
variant: "destructive",
})
}}
+ disabled={readonly}
>
<DropzoneZone>
<DropzoneUploadIcon className="mx-auto h-12 w-12 text-muted-foreground" />
- <DropzoneTitle className="text-lg font-medium">
+ <DropzoneTitle>
파일을 드래그하거나 클릭하여 업로드
</DropzoneTitle>
<DropzoneDescription className="text-sm text-muted-foreground">
@@ -1194,6 +1365,7 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
variant="ghost"
size="sm"
onClick={() => handleDeleteDocument(doc.id)}
+ disabled={readonly}
>
삭제
</Button>
@@ -1227,15 +1399,16 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
}}
onDropRejected={() => {
toast({
- title: "File upload rejected",
- description: "Please check file size and type.",
+ title: "파일 업로드 거부",
+ description: "파일 크기와 유형을 확인해주세요.",
variant: "destructive",
})
}}
+ disabled={readonly}
>
<DropzoneZone>
<DropzoneUploadIcon className="mx-auto h-12 w-12 text-muted-foreground" />
- <DropzoneTitle className="text-lg font-medium">
+ <DropzoneTitle>
파일을 드래그하거나 클릭하여 업로드
</DropzoneTitle>
<DropzoneDescription className="text-sm text-muted-foreground">
@@ -1281,6 +1454,7 @@ export function BiddingBasicInfoEditor({ biddingId, readonly = false }: BiddingB
variant="ghost"
size="sm"
onClick={() => handleDeleteDocument(doc.id)}
+ disabled={readonly}
>
삭제
</Button>