summaryrefslogtreecommitdiff
path: root/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rfq-last/vendor/batch-update-conditions-dialog.tsx')
-rw-r--r--lib/rfq-last/vendor/batch-update-conditions-dialog.tsx1121
1 files changed, 1121 insertions, 0 deletions
diff --git a/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx b/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx
new file mode 100644
index 00000000..1b8fa528
--- /dev/null
+++ b/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx
@@ -0,0 +1,1121 @@
+"use client";
+
+import * as React from "react";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import * as z from "zod";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/components/ui/command";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger
+} from "@/components/ui/popover";
+import { Textarea } from "@/components/ui/textarea";
+import { Switch } from "@/components/ui/switch";
+import { Calendar } from "@/components/ui/calendar";
+import { CalendarIcon, Loader2, Info, Package, Check, ChevronsUpDown } from "lucide-react";
+import { format } from "date-fns";
+import { ko } from "date-fns/locale";
+import { cn } from "@/lib/utils";
+import { toast } from "sonner";
+import { updateVendorConditionsBatch } from "../service";
+import { Badge } from "@/components/ui/badge";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { Checkbox } from "@/components/ui/checkbox";
+import {
+ getIncotermsForSelection,
+ getPaymentTermsForSelection,
+ getPlaceOfShippingForSelection,
+ getPlaceOfDestinationForSelection
+} from "@/lib/procurement-select/service";
+
+interface BatchUpdateConditionsDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ rfqId: number;
+ rfqCode: string;
+ selectedVendors: Array<{
+ id: number;
+ vendorName: string;
+ vendorCode: string;
+ }>;
+ onSuccess: () => void;
+}
+
+// 타입 정의
+interface SelectOption {
+ id: number;
+ code: string;
+ description: string;
+}
+
+// 폼 스키마
+const formSchema = z.object({
+ currency: z.string().optional(),
+ paymentTermsCode: z.string().optional(),
+ incotermsCode: z.string().optional(),
+ incotermsDetail: z.string().optional(),
+ contractDuration: z.string().optional(),
+ taxCode: z.string().optional(),
+ placeOfShipping: z.string().optional(),
+ placeOfDestination: z.string().optional(),
+ deliveryDate: z.date().optional(),
+ materialPriceRelatedYn: z.boolean().default(false),
+ sparepartYn: z.boolean().default(false),
+ firstYn: z.boolean().default(false),
+ firstDescription: z.string().optional(),
+ sparepartDescription: z.string().optional(),
+});
+
+type FormValues = z.infer<typeof formSchema>;
+
+const currencies = ["USD", "EUR", "KRW", "JPY", "CNY"];
+
+export function BatchUpdateConditionsDialog({
+ open,
+ onOpenChange,
+ rfqId,
+ rfqCode,
+ selectedVendors,
+ onSuccess,
+}: BatchUpdateConditionsDialogProps) {
+ const [isLoading, setIsLoading] = React.useState(false);
+
+ // Select 옵션들 상태
+ const [incoterms, setIncoterms] = React.useState<SelectOption[]>([]);
+ const [paymentTerms, setPaymentTerms] = React.useState<SelectOption[]>([]);
+ const [shippingPlaces, setShippingPlaces] = React.useState<SelectOption[]>([]);
+ const [destinationPlaces, setDestinationPlaces] = React.useState<SelectOption[]>([]);
+
+ // 로딩 상태
+ const [incotermsLoading, setIncotermsLoading] = React.useState(false);
+ const [paymentTermsLoading, setPaymentTermsLoading] = React.useState(false);
+ const [shippingLoading, setShippingLoading] = React.useState(false);
+ const [destinationLoading, setDestinationLoading] = React.useState(false);
+
+ // Popover 열림 상태
+ const [incotermsOpen, setIncotermsOpen] = React.useState(false);
+ const [paymentTermsOpen, setPaymentTermsOpen] = React.useState(false);
+ const [shippingOpen, setShippingOpen] = React.useState(false);
+ const [destinationOpen, setDestinationOpen] = React.useState(false);
+ const [calendarOpen, setCalendarOpen] = React.useState(false);
+
+ // 체크박스로 각 필드 업데이트 여부 관리
+ const [fieldsToUpdate, setFieldsToUpdate] = React.useState({
+ currency: false,
+ paymentTermsCode: false,
+ incoterms: false,
+ deliveryDate: false,
+ contractDuration: false,
+ taxCode: false,
+ shipping: false,
+ materialPrice: false,
+ sparepart: false,
+ first: false,
+ });
+
+ // 폼 초기화
+ const form = useForm<FormValues>({
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ currency: "",
+ paymentTermsCode: "",
+ incotermsCode: "",
+ incotermsDetail: "",
+ contractDuration: "",
+ taxCode: "",
+ placeOfShipping: "",
+ placeOfDestination: "",
+ materialPriceRelatedYn: false,
+ sparepartYn: false,
+ firstYn: false,
+ firstDescription: "",
+ sparepartDescription: "",
+ },
+ });
+
+ // 데이터 로드 함수들
+ const loadIncoterms = React.useCallback(async () => {
+ setIncotermsLoading(true);
+ try {
+ const data = await getIncotermsForSelection();
+ setIncoterms(data);
+ } catch (error) {
+ console.error("Failed to load incoterms:", error);
+ toast.error("Incoterms 목록을 불러오는데 실패했습니다.");
+ } finally {
+ setIncotermsLoading(false);
+ }
+ }, []);
+
+ const loadPaymentTerms = React.useCallback(async () => {
+ setPaymentTermsLoading(true);
+ try {
+ const data = await getPaymentTermsForSelection();
+ setPaymentTerms(data);
+ } catch (error) {
+ console.error("Failed to load payment terms:", error);
+ toast.error("결제조건 목록을 불러오는데 실패했습니다.");
+ } finally {
+ setPaymentTermsLoading(false);
+ }
+ }, []);
+
+ const loadShippingPlaces = React.useCallback(async () => {
+ setShippingLoading(true);
+ try {
+ const data = await getPlaceOfShippingForSelection();
+ setShippingPlaces(data);
+ } catch (error) {
+ console.error("Failed to load shipping places:", error);
+ toast.error("선적지 목록을 불러오는데 실패했습니다.");
+ } finally {
+ setShippingLoading(false);
+ }
+ }, []);
+
+ const loadDestinationPlaces = React.useCallback(async () => {
+ setDestinationLoading(true);
+ try {
+ const data = await getPlaceOfDestinationForSelection();
+ setDestinationPlaces(data);
+ } catch (error) {
+ console.error("Failed to load destination places:", error);
+ toast.error("도착지 목록을 불러오는데 실패했습니다.");
+ } finally {
+ setDestinationLoading(false);
+ }
+ }, []);
+
+ // 초기 데이터 로드
+ React.useEffect(() => {
+ if (open) {
+ loadIncoterms();
+ loadPaymentTerms();
+ loadShippingPlaces();
+ loadDestinationPlaces();
+ }
+ }, [open, loadIncoterms, loadPaymentTerms, loadShippingPlaces, loadDestinationPlaces]);
+
+ // 다이얼로그 닫힐 때 초기화
+ React.useEffect(() => {
+ if (!open) {
+ form.reset();
+ setFieldsToUpdate({
+ currency: false,
+ paymentTermsCode: false,
+ incoterms: false,
+ deliveryDate: false,
+ contractDuration: false,
+ taxCode: false,
+ shipping: false,
+ materialPrice: false,
+ sparepart: false,
+ first: false,
+ });
+ }
+ }, [open, form]);
+
+ // 제출 처리
+ const onSubmit = async (data: FormValues) => {
+ const hasFieldsToUpdate = Object.values(fieldsToUpdate).some(v => v);
+ if (!hasFieldsToUpdate) {
+ toast.error("최소 1개 이상의 변경할 항목을 선택해주세요.");
+ return;
+ }
+
+ // 선택된 필드만 포함하여 conditions 객체 생성
+ const conditions: any = {};
+
+ if (fieldsToUpdate.currency && data.currency) {
+ conditions.currency = data.currency;
+ }
+ if (fieldsToUpdate.paymentTermsCode && data.paymentTermsCode) {
+ conditions.paymentTermsCode = data.paymentTermsCode;
+ }
+ if (fieldsToUpdate.incoterms) {
+ if (data.incotermsCode) conditions.incotermsCode = data.incotermsCode;
+ if (data.incotermsDetail) conditions.incotermsDetail = data.incotermsDetail;
+ }
+ if (fieldsToUpdate.deliveryDate && data.deliveryDate) {
+ conditions.deliveryDate = data.deliveryDate;
+ }
+ if (fieldsToUpdate.contractDuration) {
+ conditions.contractDuration = data.contractDuration;
+ }
+ if (fieldsToUpdate.taxCode) {
+ conditions.taxCode = data.taxCode;
+ }
+ if (fieldsToUpdate.shipping) {
+ conditions.placeOfShipping = data.placeOfShipping;
+ conditions.placeOfDestination = data.placeOfDestination;
+ }
+ if (fieldsToUpdate.materialPrice) {
+ conditions.materialPriceRelatedYn = data.materialPriceRelatedYn;
+ }
+ if (fieldsToUpdate.sparepart) {
+ conditions.sparepartYn = data.sparepartYn;
+ if (data.sparepartYn) {
+ conditions.sparepartDescription = data.sparepartDescription;
+ }
+ }
+ if (fieldsToUpdate.first) {
+ conditions.firstYn = data.firstYn;
+ if (data.firstYn) {
+ conditions.firstDescription = data.firstDescription;
+ }
+ }
+
+ setIsLoading(true);
+
+ try {
+ const vendorIds = selectedVendors.map(v => v.id);
+ const result = await updateVendorConditionsBatch({
+ rfqId,
+ vendorIds,
+ conditions,
+ });
+
+ if (result.success) {
+ toast.success(result.data?.message || "조건이 성공적으로 업데이트되었습니다.");
+ onSuccess();
+ onOpenChange(false);
+ } else {
+ toast.error(result.error || "조건 업데이트에 실패했습니다.");
+ }
+ } catch (error) {
+ console.error("Submit error:", error);
+ toast.error("오류가 발생했습니다.");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const getUpdateCount = () => {
+ return Object.values(fieldsToUpdate).filter(v => v).length;
+ };
+
+ // 선택된 옵션 찾기 헬퍼 함수들
+ const selectedIncoterm = incoterms.find(i => i.code === form.watch("incotermsCode"));
+ const selectedPaymentTerm = paymentTerms.find(p => p.code === form.watch("paymentTermsCode"));
+ const selectedShipping = shippingPlaces.find(s => s.code === form.watch("placeOfShipping"));
+ const selectedDestination = destinationPlaces.find(d => d.code === form.watch("placeOfDestination"));
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="max-w-3xl h-[90vh] p-0 flex flex-col">
+ {/* 헤더 */}
+ <DialogHeader className="p-6 pb-0">
+ <DialogTitle>조건 일괄 설정</DialogTitle>
+ <DialogDescription>
+ 선택한 {selectedVendors.length}개 벤더에 동일한 조건을 적용합니다.
+ 변경하려는 항목만 체크하고 값을 입력하세요.
+ </DialogDescription>
+ </DialogHeader>
+
+ <Form {...form}>
+ <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col flex-1 min-h-0">
+ {/* 스크롤 가능한 컨텐츠 영역 */}
+ <ScrollArea className="flex-1 px-6">
+ <div className="grid gap-4 py-4">
+ {/* 선택된 벤더 정보 */}
+ <Card>
+ <CardHeader className="pb-3">
+ <div className="flex items-center justify-between">
+ <CardTitle className="text-lg flex items-center gap-2">
+ <Package className="h-5 w-5" />
+ 대상 벤더
+ </CardTitle>
+ <Badge>{selectedVendors.length}개</Badge>
+ </div>
+ </CardHeader>
+ <CardContent>
+ <div className="flex flex-wrap gap-2">
+ {selectedVendors.map((vendor) => (
+ <Badge key={vendor.id} variant="secondary">
+ {vendor.vendorCode} - {vendor.vendorName}
+ </Badge>
+ ))}
+ </div>
+ </CardContent>
+ </Card>
+
+ {/* 안내 메시지 */}
+ <Alert>
+ <Info className="h-4 w-4" />
+ <AlertDescription>
+ 체크박스를 선택한 항목만 업데이트됩니다.
+ 선택하지 않은 항목은 기존 값이 유지됩니다.
+ </AlertDescription>
+ </Alert>
+
+ {/* 기본 조건 설정 */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="text-lg">기본 조건</CardTitle>
+ </CardHeader>
+ <CardContent className="space-y-4">
+ {/* 통화 */}
+ <div className="flex items-center gap-4">
+ <Checkbox
+ checked={fieldsToUpdate.currency}
+ onCheckedChange={(checked) =>
+ setFieldsToUpdate({ ...fieldsToUpdate, currency: !!checked })
+ }
+ />
+ <FormField
+ control={form.control}
+ name="currency"
+ render={({ field }) => (
+ <FormItem className="flex-1 grid grid-cols-3 items-center gap-4">
+ <FormLabel className={cn(
+ "text-right",
+ !fieldsToUpdate.currency && "text-muted-foreground"
+ )}>
+ 통화
+ </FormLabel>
+ <div className="col-span-2">
+ <FormControl>
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button
+ variant="outline"
+ role="combobox"
+ className="w-full justify-between"
+ disabled={!fieldsToUpdate.currency}
+ >
+ {field.value || "통화 선택"}
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-full p-0" align="start">
+ <Command>
+ <CommandInput placeholder="통화 검색..." />
+ <CommandList>
+ <CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
+ <CommandGroup>
+ {currencies.map((currency) => (
+ <CommandItem
+ key={currency}
+ value={currency}
+ onSelect={() => field.onChange(currency)}
+ >
+ {currency}
+ <Check
+ className={cn(
+ "ml-auto h-4 w-4",
+ currency === field.value ? "opacity-100" : "opacity-0"
+ )}
+ />
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
+ </FormControl>
+ <FormMessage />
+ </div>
+ </FormItem>
+ )}
+ />
+ </div>
+
+ {/* 결제 조건 */}
+ <div className="flex items-center gap-4">
+ <Checkbox
+ checked={fieldsToUpdate.paymentTermsCode}
+ onCheckedChange={(checked) =>
+ setFieldsToUpdate({ ...fieldsToUpdate, paymentTermsCode: !!checked })
+ }
+ />
+ <FormField
+ control={form.control}
+ name="paymentTermsCode"
+ render={({ field }) => (
+ <FormItem className="flex-1 grid grid-cols-3 items-center gap-4">
+ <FormLabel className={cn(
+ "text-right",
+ !fieldsToUpdate.paymentTermsCode && "text-muted-foreground"
+ )}>
+ 결제 조건
+ </FormLabel>
+ <div className="col-span-2">
+ <Popover open={paymentTermsOpen} onOpenChange={setPaymentTermsOpen}>
+ <PopoverTrigger asChild>
+ <FormControl>
+ <Button
+ variant="outline"
+ role="combobox"
+ aria-expanded={paymentTermsOpen}
+ className="w-full justify-between"
+ disabled={!fieldsToUpdate.paymentTermsCode || paymentTermsLoading}
+ >
+ {selectedPaymentTerm ? (
+ <span className="truncate">
+ {selectedPaymentTerm.code} - {selectedPaymentTerm.description}
+ </span>
+ ) : (
+ <span className="text-muted-foreground">
+ {paymentTermsLoading ? "로딩 중..." : "결제조건 선택"}
+ </span>
+ )}
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+ </Button>
+ </FormControl>
+ </PopoverTrigger>
+ <PopoverContent className="w-full p-0" align="start">
+ <Command>
+ <CommandInput placeholder="코드 또는 설명으로 검색..." />
+ <CommandList>
+ <CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
+ <CommandGroup>
+ {paymentTerms.map((term) => (
+ <CommandItem
+ key={term.id}
+ value={`${term.code} ${term.description}`}
+ onSelect={() => {
+ field.onChange(term.code);
+ setPaymentTermsOpen(false);
+ }}
+ >
+ <div className="flex items-center gap-2 w-full">
+ <span className="font-medium">{term.code}</span>
+ <span className="text-muted-foreground">-</span>
+ <span className="truncate">{term.description}</span>
+ <Check
+ className={cn(
+ "ml-auto h-4 w-4",
+ term.code === field.value ? "opacity-100" : "opacity-0"
+ )}
+ />
+ </div>
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
+ <FormMessage />
+ </div>
+ </FormItem>
+ )}
+ />
+ </div>
+
+ {/* 인코텀즈 */}
+ <div className="flex items-start gap-4">
+ <Checkbox
+ className="mt-3"
+ checked={fieldsToUpdate.incoterms}
+ onCheckedChange={(checked) =>
+ setFieldsToUpdate({ ...fieldsToUpdate, incoterms: !!checked })
+ }
+ />
+ <div className="flex-1 grid grid-cols-3 gap-4">
+ <Label className={cn(
+ "text-right pt-2",
+ !fieldsToUpdate.incoterms && "text-muted-foreground"
+ )}>
+ 인코텀즈
+ </Label>
+ <div className="col-span-2 space-y-2">
+ <FormField
+ control={form.control}
+ name="incotermsCode"
+ render={({ field }) => (
+ <FormItem>
+ <Popover open={incotermsOpen} onOpenChange={setIncotermsOpen}>
+ <PopoverTrigger asChild>
+ <FormControl>
+ <Button
+ variant="outline"
+ role="combobox"
+ aria-expanded={incotermsOpen}
+ className="w-full justify-between"
+ disabled={!fieldsToUpdate.incoterms || incotermsLoading}
+ >
+ {selectedIncoterm ? (
+ <span className="truncate">
+ {selectedIncoterm.code} - {selectedIncoterm.description}
+ </span>
+ ) : (
+ <span className="text-muted-foreground">
+ {incotermsLoading ? "로딩 중..." : "인코텀즈 선택"}
+ </span>
+ )}
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+ </Button>
+ </FormControl>
+ </PopoverTrigger>
+ <PopoverContent className="w-full p-0" align="start">
+ <Command>
+ <CommandInput placeholder="코드 또는 설명으로 검색..." />
+ <CommandList>
+ <CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
+ <CommandGroup>
+ {incoterms.map((incoterm) => (
+ <CommandItem
+ key={incoterm.id}
+ value={`${incoterm.code} ${incoterm.description}`}
+ onSelect={() => {
+ field.onChange(incoterm.code);
+ setIncotermsOpen(false);
+ }}
+ >
+ <div className="flex items-center gap-2 w-full">
+ <span className="font-medium">{incoterm.code}</span>
+ <span className="text-muted-foreground">-</span>
+ <span className="truncate">{incoterm.description}</span>
+ <Check
+ className={cn(
+ "ml-auto h-4 w-4",
+ incoterm.code === field.value ? "opacity-100" : "opacity-0"
+ )}
+ />
+ </div>
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ {/* <FormField
+ control={form.control}
+ name="incotermsDetail"
+ render={({ field }) => (
+ <FormItem>
+ <FormControl>
+ <Input
+ placeholder="인코텀즈 상세 (예: 부산항)"
+ {...field}
+ disabled={!fieldsToUpdate.incoterms}
+ />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ /> */}
+ </div>
+ </div>
+ </div>
+
+ {/* 납기일 */}
+ {!rfqCode.startsWith("F") && (
+ <div className="flex items-center gap-4">
+ <Checkbox
+ checked={fieldsToUpdate.deliveryDate}
+ onCheckedChange={(checked) =>
+ setFieldsToUpdate({ ...fieldsToUpdate, deliveryDate: !!checked })
+ }
+ />
+ <FormField
+ control={form.control}
+ name="deliveryDate"
+ render={({ field }) => (
+ <FormItem className="flex-1 grid grid-cols-3 items-center gap-4">
+ <FormLabel className={cn(
+ "text-right",
+ !fieldsToUpdate.deliveryDate && "text-muted-foreground"
+ )}>
+ 납기일
+ </FormLabel>
+ <div className="col-span-2">
+ <Popover open={calendarOpen} onOpenChange={setCalendarOpen}>
+ <PopoverTrigger asChild>
+ <FormControl>
+ <Button
+ variant="outline"
+ className={cn(
+ "w-full justify-start text-left font-normal",
+ !field.value && "text-muted-foreground"
+ )}
+ disabled={!fieldsToUpdate.deliveryDate}
+ >
+ <CalendarIcon className="mr-2 h-4 w-4" />
+ {field.value ? (
+ format(field.value, "yyyy-MM-dd", { locale: ko })
+ ) : (
+ <span>날짜를 선택하세요</span>
+ )}
+ </Button>
+ </FormControl>
+ </PopoverTrigger>
+ <PopoverContent className="w-auto p-0">
+ <Calendar
+ mode="single"
+ selected={field.value}
+ onSelect={(date) => {
+ field.onChange(date);
+ setCalendarOpen(false);
+ }}
+ initialFocus
+ />
+ </PopoverContent>
+ </Popover>
+ <FormMessage />
+ </div>
+ </FormItem>
+ )}
+ />
+ </div>
+ )}
+
+ {/* 계약 기간 */}
+ {rfqCode.startsWith("F") && (
+ <div className="flex items-center gap-4">
+ <Checkbox
+ checked={fieldsToUpdate.contractDuration}
+ onCheckedChange={(checked) =>
+ setFieldsToUpdate({ ...fieldsToUpdate, contractDuration: !!checked })
+ }
+ />
+ <FormField
+ control={form.control}
+ name="contractDuration"
+ render={({ field }) => (
+ <FormItem className="flex-1 grid grid-cols-3 items-center gap-4">
+ <FormLabel className={cn(
+ "text-right",
+ !fieldsToUpdate.contractDuration && "text-muted-foreground"
+ )}>
+ 계약 기간
+ </FormLabel>
+ <div className="col-span-2">
+ <FormControl>
+ <Input
+ placeholder="예: 12개월"
+ {...field}
+ disabled={!fieldsToUpdate.contractDuration}
+ />
+ </FormControl>
+ <FormMessage />
+ </div>
+ </FormItem>
+ )}
+ />
+ </div>
+ )}
+
+ {/* 세금 코드 */}
+ <div className="flex items-center gap-4">
+ <Checkbox
+ checked={fieldsToUpdate.taxCode}
+ onCheckedChange={(checked) =>
+ setFieldsToUpdate({ ...fieldsToUpdate, taxCode: !!checked })
+ }
+ />
+ <FormField
+ control={form.control}
+ name="taxCode"
+ render={({ field }) => (
+ <FormItem className="flex-1 grid grid-cols-3 items-center gap-4">
+ <FormLabel className={cn(
+ "text-right",
+ !fieldsToUpdate.taxCode && "text-muted-foreground"
+ )}>
+ 세금 코드
+ </FormLabel>
+ <div className="col-span-2">
+ <FormControl>
+ <Input
+ {...field}
+ disabled={!fieldsToUpdate.taxCode}
+ />
+ </FormControl>
+ <FormMessage />
+ </div>
+ </FormItem>
+ )}
+ />
+ </div>
+
+ {/* 선적지/도착지 */}
+ <div className="flex items-start gap-4">
+ <Checkbox
+ className="mt-3"
+ checked={fieldsToUpdate.shipping}
+ onCheckedChange={(checked) =>
+ setFieldsToUpdate({ ...fieldsToUpdate, shipping: !!checked })
+ }
+ />
+ <div className="flex-1 space-y-2">
+ <FormField
+ control={form.control}
+ name="placeOfShipping"
+ render={({ field }) => (
+ <FormItem className="grid grid-cols-3 items-center gap-4">
+ <FormLabel className={cn(
+ "text-right",
+ !fieldsToUpdate.shipping && "text-muted-foreground"
+ )}>
+ 선적지
+ </FormLabel>
+ <div className="col-span-2">
+ <Popover open={shippingOpen} onOpenChange={setShippingOpen}>
+ <PopoverTrigger asChild>
+ <FormControl>
+ <Button
+ variant="outline"
+ role="combobox"
+ aria-expanded={shippingOpen}
+ className="w-full justify-between"
+ disabled={!fieldsToUpdate.shipping || shippingLoading}
+ >
+ {selectedShipping ? (
+ <span className="truncate">
+ {selectedShipping.code} - {selectedShipping.description}
+ </span>
+ ) : (
+ <span className="text-muted-foreground">
+ {shippingLoading ? "로딩 중..." : "선적지 선택"}
+ </span>
+ )}
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+ </Button>
+ </FormControl>
+ </PopoverTrigger>
+ <PopoverContent className="w-full p-0" align="start">
+ <Command>
+ <CommandInput placeholder="선적지 검색..." />
+ <CommandList>
+ <CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
+ <CommandGroup>
+ {shippingPlaces.map((place) => (
+ <CommandItem
+ key={place.id}
+ value={`${place.code} ${place.description}`}
+ onSelect={() => {
+ field.onChange(place.code);
+ setShippingOpen(false);
+ }}
+ >
+ <div className="flex items-center gap-2 w-full">
+ <span className="font-medium">{place.code}</span>
+ <span className="text-muted-foreground">-</span>
+ <span className="truncate">{place.description}</span>
+ <Check
+ className={cn(
+ "ml-auto h-4 w-4",
+ place.code === field.value ? "opacity-100" : "opacity-0"
+ )}
+ />
+ </div>
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
+ <FormMessage />
+ </div>
+ </FormItem>
+ )}
+ />
+
+ <FormField
+ control={form.control}
+ name="placeOfDestination"
+ render={({ field }) => (
+ <FormItem className="grid grid-cols-3 items-center gap-4">
+ <FormLabel className={cn(
+ "text-right",
+ !fieldsToUpdate.shipping && "text-muted-foreground"
+ )}>
+ 도착지
+ </FormLabel>
+ <div className="col-span-2">
+ <Popover open={destinationOpen} onOpenChange={setDestinationOpen}>
+ <PopoverTrigger asChild>
+ <FormControl>
+ <Button
+ variant="outline"
+ role="combobox"
+ aria-expanded={destinationOpen}
+ className="w-full justify-between"
+ disabled={!fieldsToUpdate.shipping || destinationLoading}
+ >
+ {selectedDestination ? (
+ <span className="truncate">
+ {selectedDestination.code} - {selectedDestination.description}
+ </span>
+ ) : (
+ <span className="text-muted-foreground">
+ {destinationLoading ? "로딩 중..." : "도착지 선택"}
+ </span>
+ )}
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+ </Button>
+ </FormControl>
+ </PopoverTrigger>
+ <PopoverContent className="w-full p-0" align="start">
+ <Command>
+ <CommandInput placeholder="도착지 검색..." />
+ <CommandList>
+ <CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
+ <CommandGroup>
+ {destinationPlaces.map((place) => (
+ <CommandItem
+ key={place.id}
+ value={`${place.code} ${place.description}`}
+ onSelect={() => {
+ field.onChange(place.code);
+ setDestinationOpen(false);
+ }}
+ >
+ <div className="flex items-center gap-2 w-full">
+ <span className="font-medium">{place.code}</span>
+ <span className="text-muted-foreground">-</span>
+ <span className="truncate">{place.description}</span>
+ <Check
+ className={cn(
+ "ml-auto h-4 w-4",
+ place.code === field.value ? "opacity-100" : "opacity-0"
+ )}
+ />
+ </div>
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
+ <FormMessage />
+ </div>
+ </FormItem>
+ )}
+ />
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+
+ {/* 추가 옵션 */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="text-lg">추가 옵션</CardTitle>
+ </CardHeader>
+ <CardContent className="space-y-4">
+ {/* 연동제 적용 */}
+ <div className="flex items-center gap-4">
+ <Checkbox
+ checked={fieldsToUpdate.materialPrice}
+ onCheckedChange={(checked) =>
+ setFieldsToUpdate({ ...fieldsToUpdate, materialPrice: !!checked })
+ }
+ />
+ <FormField
+ control={form.control}
+ name="materialPriceRelatedYn"
+ render={({ field }) => (
+ <FormItem className="flex-1 flex items-center justify-between">
+ <div className="space-y-0.5">
+ <FormLabel className={cn(
+ !fieldsToUpdate.materialPrice && "text-muted-foreground"
+ )}>
+ 연동제 적용
+ </FormLabel>
+ <div className="text-sm text-muted-foreground">
+ 원자재 가격 연동 여부
+ </div>
+ </div>
+ <FormControl>
+ <Switch
+ checked={field.value}
+ onCheckedChange={field.onChange}
+ disabled={!fieldsToUpdate.materialPrice}
+ />
+ </FormControl>
+ </FormItem>
+ )}
+ />
+ </div>
+
+ {/* Spare Part */}
+ <div className="space-y-2">
+ <div className="flex items-center gap-4">
+ <Checkbox
+ checked={fieldsToUpdate.sparepart}
+ onCheckedChange={(checked) =>
+ setFieldsToUpdate({ ...fieldsToUpdate, sparepart: !!checked })
+ }
+ />
+ <FormField
+ control={form.control}
+ name="sparepartYn"
+ render={({ field }) => (
+ <FormItem className="flex-1 flex items-center justify-between">
+ <div className="space-y-0.5">
+ <FormLabel className={cn(
+ !fieldsToUpdate.sparepart && "text-muted-foreground"
+ )}>
+ Spare Part
+ </FormLabel>
+ <div className="text-sm text-muted-foreground">
+ 예비 부품 요구사항
+ </div>
+ </div>
+ <FormControl>
+ <Switch
+ checked={field.value}
+ onCheckedChange={field.onChange}
+ disabled={!fieldsToUpdate.sparepart}
+ />
+ </FormControl>
+ </FormItem>
+ )}
+ />
+ </div>
+ {form.watch("sparepartYn") && fieldsToUpdate.sparepart && (
+ <FormField
+ control={form.control}
+ name="sparepartDescription"
+ render={({ field }) => (
+ <FormItem className="ml-7">
+ <FormControl>
+ <Textarea
+ placeholder="Spare Part 요구사항을 입력하세요..."
+ {...field}
+ disabled={!fieldsToUpdate.sparepart}
+ />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ )}
+ </div>
+
+ {/* 초도품 관리 */}
+ <div className="space-y-2">
+ <div className="flex items-center gap-4">
+ <Checkbox
+ checked={fieldsToUpdate.first}
+ onCheckedChange={(checked) =>
+ setFieldsToUpdate({ ...fieldsToUpdate, first: !!checked })
+ }
+ />
+ <FormField
+ control={form.control}
+ name="firstYn"
+ render={({ field }) => (
+ <FormItem className="flex-1 flex items-center justify-between">
+ <div className="space-y-0.5">
+ <FormLabel className={cn(
+ !fieldsToUpdate.first && "text-muted-foreground"
+ )}>
+ 초도품 관리
+ </FormLabel>
+ <div className="text-sm text-muted-foreground">
+ 초도품 관리 요구사항
+ </div>
+ </div>
+ <FormControl>
+ <Switch
+ checked={field.value}
+ onCheckedChange={field.onChange}
+ disabled={!fieldsToUpdate.first}
+ />
+ </FormControl>
+ </FormItem>
+ )}
+ />
+ </div>
+ {form.watch("firstYn") && fieldsToUpdate.first && (
+ <FormField
+ control={form.control}
+ name="firstDescription"
+ render={({ field }) => (
+ <FormItem className="ml-7">
+ <FormControl>
+ <Textarea
+ placeholder="초도품 관리 요구사항을 입력하세요..."
+ {...field}
+ disabled={!fieldsToUpdate.first}
+ />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ )}
+ </div>
+ </CardContent>
+ </Card>
+ </div>
+ </ScrollArea>
+
+ {/* 푸터 */}
+ <DialogFooter className="p-6 pt-4 border-t">
+ <div className="flex items-center justify-between w-full">
+ <div className="text-sm text-muted-foreground">
+ {getUpdateCount() > 0
+ ? `${getUpdateCount()}개 항목 선택됨`
+ : '변경할 항목을 선택하세요'
+ }
+ </div>
+ <div className="flex gap-2">
+ <Button
+ type="button"
+ variant="outline"
+ onClick={() => onOpenChange(false)}
+ disabled={isLoading}
+ >
+ 취소
+ </Button>
+ <Button
+ type="submit"
+ disabled={isLoading || getUpdateCount() === 0}
+ >
+ {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+ {getUpdateCount() > 0
+ ? `${getUpdateCount()}개 항목 업데이트`
+ : '조건 업데이트'
+ }
+ </Button>
+ </div>
+ </div>
+ </DialogFooter>
+ </form>
+ </Form>
+ </DialogContent>
+ </Dialog>
+ );
+} \ No newline at end of file