diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-11-28 03:12:57 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-11-28 03:12:57 +0000 |
| commit | 9cda8482660a87fd98c9ee43f507d75ff75b4e23 (patch) | |
| tree | 67eb1fc24eec7c4e61d3154f7b09fc5349454672 /components/bidding/manage/bidding-basic-info-editor.tsx | |
| parent | f57898bd240d068301ce3ef477f52cff1234e4ee (diff) | |
(최겸) 구매 입찰 피드백 반영(90%)
Diffstat (limited to 'components/bidding/manage/bidding-basic-info-editor.tsx')
| -rw-r--r-- | components/bidding/manage/bidding-basic-info-editor.tsx | 472 |
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> |
