From 14f61e24947fb92dd71ec0a7196a6e815f8e66da Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 21 Jul 2025 07:54:26 +0000 Subject: (최겸)기술영업 RFQ 담당자 초대, 요구사항 반영 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/techsales-rfq/table/create-rfq-hull-dialog.tsx | 1294 ++++++++++---------- 1 file changed, 647 insertions(+), 647 deletions(-) (limited to 'lib/techsales-rfq/table/create-rfq-hull-dialog.tsx') diff --git a/lib/techsales-rfq/table/create-rfq-hull-dialog.tsx b/lib/techsales-rfq/table/create-rfq-hull-dialog.tsx index 23c57491..5870c785 100644 --- a/lib/techsales-rfq/table/create-rfq-hull-dialog.tsx +++ b/lib/techsales-rfq/table/create-rfq-hull-dialog.tsx @@ -1,648 +1,648 @@ -"use client" - -import * as React from "react" -import { toast } from "sonner" -import { ArrowUpDown, CheckSquare, Plus, Search, Square, X, Loader2 } from "lucide-react" -import { Input } from "@/components/ui/input" -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 { Button } from "@/components/ui/button" -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog" -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - FormDescription, -} from "@/components/ui/form" -import { zodResolver } from "@hookform/resolvers/zod" -import { useForm } from "react-hook-form" -import * as z from "zod" -import { EstimateProjectSelector } from "@/components/BidProjectSelector" -import { type Project } from "@/lib/rfqs/service" -import { createTechSalesHullRfq } from "@/lib/techsales-rfq/service" -import { useSession } from "next-auth/react" -import { Separator } from "@/components/ui/separator" -import { - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import { cn } from "@/lib/utils" -import { ScrollArea } from "@/components/ui/scroll-area" -// import { -// Table, -// TableBody, -// TableCell, -// TableHead, -// TableHeader, -// TableRow, -// } from "@/components/ui/table" - -// 공종 타입 import -import { - getOffshoreHullWorkTypes, - getAllOffshoreHullItemsForCache, - type OffshoreHullWorkType, - type OffshoreHullTechItem, -} from "@/lib/items-tech/service" - -// 해양 HULL 아이템 타입 정의 (이미 service에서 import하므로 제거) - -// 유효성 검증 스키마 -const createHullRfqSchema = z.object({ - biddingProjectId: z.number({ - required_error: "프로젝트를 선택해주세요.", - }), - itemIds: z.array(z.number()).min(1, { - message: "적어도 하나의 아이템을 선택해야 합니다.", - }), - dueDate: z.date({ - required_error: "마감일을 선택해주세요.", - }), - description: z.string().optional(), -}) - -// 폼 데이터 타입 -type CreateHullRfqFormValues = z.infer - -// 공종 타입 정의 -interface WorkTypeOption { - code: OffshoreHullWorkType - name: string -} - -interface CreateHullRfqDialogProps { - onCreated?: () => void; -} - -export function CreateHullRfqDialog({ onCreated }: CreateHullRfqDialogProps) { - const { data: session } = useSession() - - const [isProcessing, setIsProcessing] = React.useState(false) - const [isDialogOpen, setIsDialogOpen] = React.useState(false) - const [selectedProject, setSelectedProject] = React.useState(null) - - // 검색 및 필터링 상태 - const [itemSearchQuery, setItemSearchQuery] = React.useState("") - const [selectedWorkType, setSelectedWorkType] = React.useState(null) - const [selectedItems, setSelectedItems] = React.useState([]) - - // 데이터 상태 - const [workTypes, setWorkTypes] = React.useState([]) - const [allItems, setAllItems] = React.useState([]) - const [isLoadingItems, setIsLoadingItems] = React.useState(false) - const [dataLoadError, setDataLoadError] = React.useState(null) - const [retryCount, setRetryCount] = React.useState(0) - - // 데이터 로딩 함수 - const loadData = React.useCallback(async (isRetry = false) => { - try { - if (!isRetry) { - setIsLoadingItems(true) - setDataLoadError(null) - } - - console.log(`해양 Hull RFQ 데이터 로딩 시작... ${isRetry ? `(재시도 ${retryCount + 1}회)` : ''}`) - - const [workTypesResult, hullItemsResult] = await Promise.all([ - getOffshoreHullWorkTypes(), - getAllOffshoreHullItemsForCache() - ]) - - console.log("Hull - WorkTypes 결과:", workTypesResult) - console.log("Hull - Items 결과:", hullItemsResult) - - // WorkTypes 설정 - if (Array.isArray(workTypesResult)) { - setWorkTypes(workTypesResult) - } else { - throw new Error("공종 데이터를 불러올 수 없습니다.") - } - - // Hull Items 설정 - if (hullItemsResult.data && Array.isArray(hullItemsResult.data)) { - setAllItems(hullItemsResult.data as OffshoreHullTechItem[]) - console.log("Hull 아이템 설정 완료:", hullItemsResult.data.length, "개") - } else { - throw new Error(hullItemsResult.error || "Hull 아이템 데이터를 불러올 수 없습니다.") - } - - // 성공 시 재시도 카운터 리셋 - setRetryCount(0) - setDataLoadError(null) - console.log("해양 Hull RFQ 데이터 로딩 완료") - - } catch (error) { - const errorMessage = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.' - console.error("해양 Hull RFQ 데이터 로딩 오류:", errorMessage) - - setDataLoadError(errorMessage) - - // 3회까지 자동 재시도 (500ms 간격) - if (retryCount < 2) { - console.log(`${500 * (retryCount + 1)}ms 후 재시도...`) - setTimeout(() => { - setRetryCount(prev => prev + 1) - loadData(true) - }, 500 * (retryCount + 1)) - } else { - // 재시도 실패 시 사용자에게 알림 - toast.error(`데이터 로딩에 실패했습니다: ${errorMessage}`) - } - } finally { - if (!isRetry) { - setIsLoadingItems(false) - } - } - }, [retryCount]) - - // 다이얼로그가 열릴 때마다 데이터 로딩 - React.useEffect(() => { - if (isDialogOpen) { - setDataLoadError(null) - setRetryCount(0) - loadData() - } - }, [isDialogOpen, loadData]) - - // 수동 새로고침 함수 - const handleRefreshData = React.useCallback(() => { - setDataLoadError(null) - setRetryCount(0) - loadData() - }, [loadData]) - - // RFQ 생성 폼 - const form = useForm({ - resolver: zodResolver(createHullRfqSchema), - defaultValues: { - biddingProjectId: undefined, - itemIds: [], - dueDate: undefined, - description: "", - } - }) - - // 필터링된 아이템 목록 가져오기 - const availableItems = React.useMemo(() => { - let filtered = [...allItems] - - // 공종 필터 - if (selectedWorkType) { - filtered = filtered.filter(item => item.workType === selectedWorkType as OffshoreHullTechItem['workType']) - } - - // 검색어 필터 - if (itemSearchQuery && itemSearchQuery.trim()) { - const query = itemSearchQuery.toLowerCase().trim() - filtered = filtered.filter(item => - item.itemCode.toLowerCase().includes(query) || - (item.itemList && item.itemList.toLowerCase().includes(query)) || - (item.subItemList && item.subItemList.toLowerCase().includes(query)) - ) - } - - return filtered - }, [allItems, itemSearchQuery, selectedWorkType]) - - // 프로젝트 선택 처리 - const handleProjectSelect = (project: Project) => { - setSelectedProject(project) - form.setValue("biddingProjectId", project.id) - // 선택 초기화 - setSelectedItems([]) - setSelectedWorkType(null) - setItemSearchQuery("") - form.setValue("itemIds", []) - } - - // 아이템 선택/해제 처리 - const handleItemToggle = (item: OffshoreHullTechItem) => { - const isSelected = selectedItems.some(selected => selected.id === item.id) - - if (isSelected) { - const newSelectedItems = selectedItems.filter(selected => selected.id !== item.id) - setSelectedItems(newSelectedItems) - form.setValue("itemIds", newSelectedItems.map(item => item.id)) - } else { - const newSelectedItems = [...selectedItems, item] - setSelectedItems(newSelectedItems) - form.setValue("itemIds", newSelectedItems.map(item => item.id)) - } - } - - // RFQ 생성 함수 - const handleCreateRfq = async (data: CreateHullRfqFormValues) => { - try { - setIsProcessing(true) - - // 사용자 인증 확인 - if (!session?.user?.id) { - throw new Error("로그인이 필요합니다") - } - - // 해양 Hull RFQ 생성 - 1:N 관계로 한 번에 생성 - const result = await createTechSalesHullRfq({ - biddingProjectId: data.biddingProjectId, - itemIds: data.itemIds, - dueDate: data.dueDate, - description: data.description, - createdBy: Number(session.user.id), - }) - - if (result.error) { - throw new Error(result.error) - } - - // 성공적으로 생성되면 다이얼로그 닫기 및 메시지 표시 - toast.success(`${selectedItems.length}개 아이템으로 해양 Hull RFQ가 성공적으로 생성되었습니다`) - - setIsDialogOpen(false) - form.reset({ - biddingProjectId: undefined, - itemIds: [], - dueDate: undefined, - description: "", - }) - setSelectedProject(null) - setItemSearchQuery("") - setSelectedWorkType(null) - setSelectedItems([]) - setDataLoadError(null) - setRetryCount(0) - - // 생성 후 콜백 실행 - if (onCreated) { - onCreated() - } - - } catch (error) { - console.error("해양 Hull RFQ 생성 오류:", error) - toast.error(`해양 Hull RFQ 생성 중 오류가 발생했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`) - } finally { - setIsProcessing(false) - } - } - - return ( - { - setIsDialogOpen(open) - if (!open) { - form.reset({ - biddingProjectId: undefined, - itemIds: [], - dueDate: undefined, - description: "", - }) - setSelectedProject(null) - setItemSearchQuery("") - setSelectedWorkType(null) - setSelectedItems([]) - setDataLoadError(null) - setRetryCount(0) - } - }} - > - - - - - - 해양 Hull RFQ 생성 - - -
-
- - {/* 프로젝트 선택 */} -
- ( - - 입찰 프로젝트 - - - - - - )} - /> - - {/* RFQ 설명 */} - ( - - RFQ Title - - - - - - )} - /> - - {/* 마감일 설정 */} - ( - - 마감일 - - - - - - - - - date < new Date() || date < new Date("1900-01-01") - } - initialFocus - /> - - - - - )} - /> - - - -
- {/* 아이템 선택 영역 */} -
-
- 해양 Hull 아이템 선택 - - 해양 Hull 아이템을 선택하세요 - -
- - {/* 데이터 로딩 에러 표시 */} - {dataLoadError && ( -
-
-
- - {dataLoadError} -
- -
-
- )} - - {/* 아이템 검색 및 필터 */} -
-
-
- - setItemSearchQuery(e.target.value)} - className="pl-8 pr-8" - disabled={isLoadingItems || dataLoadError !== null} - /> - {itemSearchQuery && ( - - )} -
- - {/* 공종 필터 */} - - - - - - setSelectedWorkType(null)} - > - 전체 공종 - - {workTypes.map(workType => ( - setSelectedWorkType(workType.code)} - > - {workType.name} - - ))} - - -
-
- - {/* 아이템 목록 */} -
- -
- {dataLoadError ? ( -
-
-
- -
-

데이터 로딩에 실패했습니다

-

{dataLoadError}

-
- -
-
-
- ) : isLoadingItems ? ( -
- - 아이템을 불러오는 중... - {retryCount > 0 && ( -

재시도 {retryCount}회

- )} -
- ) : availableItems.length > 0 ? ( - [...availableItems] - .sort((a, b) => { - const aName = a.itemList || 'zzz' - const bName = b.itemList || 'zzz' - return aName.localeCompare(bName, 'ko', { numeric: true }) - }) - .map((item) => { - const isSelected = selectedItems.some(selected => selected.id === item.id) - - return ( -
handleItemToggle(item)} - > -
- {isSelected ? ( - - ) : ( - - )} -
- {/* Hull 아이템 표시: "item_list / sub_item_list" / item_code / 공종 */} -
- {item.itemList || '아이템명 없음'} - {item.subItemList && ` / ${item.subItemList}`} -
-
- {item.itemCode || '아이템코드 없음'} -
-
- 공종: {item.workType} -
-
-
-
- ) - }) - ) : ( -
- {itemSearchQuery ? "검색 결과가 없습니다" : "아이템이 없습니다"} -
- )} -
-
-
-
-
-
-
- -
- - {/* Footer - Sticky 버튼 영역 */} -
-
- - -
-
-
-
- ) +"use client" + +import * as React from "react" +import { toast } from "sonner" +import { ArrowUpDown, CheckSquare, Plus, Search, Square, X, Loader2 } from "lucide-react" +import { Input } from "@/components/ui/input" +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 { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + FormDescription, +} from "@/components/ui/form" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import * as z from "zod" +import { EstimateProjectSelector } from "@/components/BidProjectSelector" +import { type Project } from "@/lib/rfqs/service" +import { createTechSalesHullRfq } from "@/lib/techsales-rfq/service" +import { useSession } from "next-auth/react" +import { Separator } from "@/components/ui/separator" +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { cn } from "@/lib/utils" +import { ScrollArea } from "@/components/ui/scroll-area" +// import { +// Table, +// TableBody, +// TableCell, +// TableHead, +// TableHeader, +// TableRow, +// } from "@/components/ui/table" + +// 공종 타입 import +import { + getOffshoreHullWorkTypes, + getAllOffshoreHullItemsForCache, + type OffshoreHullWorkType, + type OffshoreHullTechItem, +} from "@/lib/items-tech/service" + +// 해양 HULL 아이템 타입 정의 (이미 service에서 import하므로 제거) + +// 유효성 검증 스키마 +const createHullRfqSchema = z.object({ + biddingProjectId: z.number({ + required_error: "프로젝트를 선택해주세요.", + }), + itemIds: z.array(z.number()).min(1, { + message: "적어도 하나의 아이템을 선택해야 합니다.", + }), + dueDate: z.date({ + required_error: "마감일을 선택해주세요.", + }), + description: z.string().optional(), +}) + +// 폼 데이터 타입 +type CreateHullRfqFormValues = z.infer + +// 공종 타입 정의 +interface WorkTypeOption { + code: OffshoreHullWorkType + name: string +} + +interface CreateHullRfqDialogProps { + onCreated?: () => void; +} + +export function CreateHullRfqDialog({ onCreated }: CreateHullRfqDialogProps) { + const { data: session } = useSession() + + const [isProcessing, setIsProcessing] = React.useState(false) + const [isDialogOpen, setIsDialogOpen] = React.useState(false) + const [selectedProject, setSelectedProject] = React.useState(null) + + // 검색 및 필터링 상태 + const [itemSearchQuery, setItemSearchQuery] = React.useState("") + const [selectedWorkType, setSelectedWorkType] = React.useState(null) + const [selectedItems, setSelectedItems] = React.useState([]) + + // 데이터 상태 + const [workTypes, setWorkTypes] = React.useState([]) + const [allItems, setAllItems] = React.useState([]) + const [isLoadingItems, setIsLoadingItems] = React.useState(false) + const [dataLoadError, setDataLoadError] = React.useState(null) + const [retryCount, setRetryCount] = React.useState(0) + + // 데이터 로딩 함수 + const loadData = React.useCallback(async (isRetry = false) => { + try { + if (!isRetry) { + setIsLoadingItems(true) + setDataLoadError(null) + } + + console.log(`해양 Hull RFQ 데이터 로딩 시작... ${isRetry ? `(재시도 ${retryCount + 1}회)` : ''}`) + + const [workTypesResult, hullItemsResult] = await Promise.all([ + getOffshoreHullWorkTypes(), + getAllOffshoreHullItemsForCache() + ]) + + console.log("Hull - WorkTypes 결과:", workTypesResult) + console.log("Hull - Items 결과:", hullItemsResult) + + // WorkTypes 설정 + if (Array.isArray(workTypesResult)) { + setWorkTypes(workTypesResult) + } else { + throw new Error("공종 데이터를 불러올 수 없습니다.") + } + + // Hull Items 설정 + if (hullItemsResult.data && Array.isArray(hullItemsResult.data)) { + setAllItems(hullItemsResult.data as OffshoreHullTechItem[]) + console.log("Hull 아이템 설정 완료:", hullItemsResult.data.length, "개") + } else { + throw new Error(hullItemsResult.error || "Hull 아이템 데이터를 불러올 수 없습니다.") + } + + // 성공 시 재시도 카운터 리셋 + setRetryCount(0) + setDataLoadError(null) + console.log("해양 Hull RFQ 데이터 로딩 완료") + + } catch (error) { + const errorMessage = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.' + console.error("해양 Hull RFQ 데이터 로딩 오류:", errorMessage) + + setDataLoadError(errorMessage) + + // 3회까지 자동 재시도 (500ms 간격) + if (retryCount < 2) { + console.log(`${500 * (retryCount + 1)}ms 후 재시도...`) + setTimeout(() => { + setRetryCount(prev => prev + 1) + loadData(true) + }, 500 * (retryCount + 1)) + } else { + // 재시도 실패 시 사용자에게 알림 + toast.error(`데이터 로딩에 실패했습니다: ${errorMessage}`) + } + } finally { + if (!isRetry) { + setIsLoadingItems(false) + } + } + }, [retryCount]) + + // 다이얼로그가 열릴 때마다 데이터 로딩 + React.useEffect(() => { + if (isDialogOpen) { + setDataLoadError(null) + setRetryCount(0) + loadData() + } + }, [isDialogOpen, loadData]) + + // 수동 새로고침 함수 + const handleRefreshData = React.useCallback(() => { + setDataLoadError(null) + setRetryCount(0) + loadData() + }, [loadData]) + + // RFQ 생성 폼 + const form = useForm({ + resolver: zodResolver(createHullRfqSchema), + defaultValues: { + biddingProjectId: undefined, + itemIds: [], + dueDate: undefined, + description: "", + } + }) + + // 필터링된 아이템 목록 가져오기 + const availableItems = React.useMemo(() => { + let filtered = [...allItems] + + // 공종 필터 + if (selectedWorkType) { + filtered = filtered.filter(item => item.workType === selectedWorkType as OffshoreHullTechItem['workType']) + } + + // 검색어 필터 + if (itemSearchQuery && itemSearchQuery.trim()) { + const query = itemSearchQuery.toLowerCase().trim() + filtered = filtered.filter(item => + item.itemCode.toLowerCase().includes(query) || + (item.itemList && item.itemList.toLowerCase().includes(query)) || + (item.subItemList && item.subItemList.toLowerCase().includes(query)) + ) + } + + return filtered + }, [allItems, itemSearchQuery, selectedWorkType]) + + // 프로젝트 선택 처리 + const handleProjectSelect = (project: Project) => { + setSelectedProject(project) + form.setValue("biddingProjectId", project.id) + // 선택 초기화 + setSelectedItems([]) + setSelectedWorkType(null) + setItemSearchQuery("") + form.setValue("itemIds", []) + } + + // 아이템 선택/해제 처리 + const handleItemToggle = (item: OffshoreHullTechItem) => { + const isSelected = selectedItems.some(selected => selected.id === item.id) + + if (isSelected) { + const newSelectedItems = selectedItems.filter(selected => selected.id !== item.id) + setSelectedItems(newSelectedItems) + form.setValue("itemIds", newSelectedItems.map(item => item.id)) + } else { + const newSelectedItems = [...selectedItems, item] + setSelectedItems(newSelectedItems) + form.setValue("itemIds", newSelectedItems.map(item => item.id)) + } + } + + // RFQ 생성 함수 + const handleCreateRfq = async (data: CreateHullRfqFormValues) => { + try { + setIsProcessing(true) + + // 사용자 인증 확인 + if (!session?.user?.id) { + throw new Error("로그인이 필요합니다") + } + + // 해양 Hull RFQ 생성 - 1:N 관계로 한 번에 생성 + const result = await createTechSalesHullRfq({ + biddingProjectId: data.biddingProjectId, + itemIds: data.itemIds, + dueDate: data.dueDate, + description: data.description, + createdBy: Number(session.user.id), + }) + + if (result.error) { + throw new Error(result.error) + } + + // 성공적으로 생성되면 다이얼로그 닫기 및 메시지 표시 + toast.success(`${selectedItems.length}개 아이템으로 해양 Hull RFQ가 성공적으로 생성되었습니다`) + + setIsDialogOpen(false) + form.reset({ + biddingProjectId: undefined, + itemIds: [], + dueDate: undefined, + description: "", + }) + setSelectedProject(null) + setItemSearchQuery("") + setSelectedWorkType(null) + setSelectedItems([]) + setDataLoadError(null) + setRetryCount(0) + + // 생성 후 콜백 실행 + if (onCreated) { + onCreated() + } + + } catch (error) { + console.error("해양 Hull RFQ 생성 오류:", error) + toast.error(`해양 Hull RFQ 생성 중 오류가 발생했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`) + } finally { + setIsProcessing(false) + } + } + + return ( + { + setIsDialogOpen(open) + if (!open) { + form.reset({ + biddingProjectId: undefined, + itemIds: [], + dueDate: undefined, + description: "", + }) + setSelectedProject(null) + setItemSearchQuery("") + setSelectedWorkType(null) + setSelectedItems([]) + setDataLoadError(null) + setRetryCount(0) + } + }} + > + + + + + + 해양 Hull RFQ 생성 + + +
+
+ + {/* 프로젝트 선택 */} +
+ ( + + 입찰 프로젝트 + + + + + + )} + /> + + {/* RFQ 설명 */} + ( + + RFQ Title + + + + + + )} + /> + + {/* 마감일 설정 */} + ( + + 마감일 + + + + + + + + + date < new Date() || date < new Date("1900-01-01") + } + initialFocus + /> + + + + + )} + /> + + + +
+ {/* 아이템 선택 영역 */} +
+
+ 해양 Hull 아이템 선택 + + 해양 Hull 아이템을 선택하세요 + +
+ + {/* 데이터 로딩 에러 표시 */} + {dataLoadError && ( +
+
+
+ + {dataLoadError} +
+ +
+
+ )} + + {/* 아이템 검색 및 필터 */} +
+
+
+ + setItemSearchQuery(e.target.value)} + className="pl-8 pr-8" + disabled={isLoadingItems || dataLoadError !== null} + /> + {itemSearchQuery && ( + + )} +
+ + {/* 공종 필터 */} + + + + + + setSelectedWorkType(null)} + > + 전체 공종 + + {workTypes.map(workType => ( + setSelectedWorkType(workType.code)} + > + {workType.name} + + ))} + + +
+
+ + {/* 아이템 목록 */} +
+ +
+ {dataLoadError ? ( +
+
+
+ +
+

데이터 로딩에 실패했습니다

+

{dataLoadError}

+
+ +
+
+
+ ) : isLoadingItems ? ( +
+ + 아이템을 불러오는 중... + {retryCount > 0 && ( +

재시도 {retryCount}회

+ )} +
+ ) : availableItems.length > 0 ? ( + [...availableItems] + .sort((a, b) => { + const aCode = a.itemCode || 'zzz' + const bCode = b.itemCode || 'zzz' + return aCode.localeCompare(bCode, 'ko', { numeric: true }) + }) + .map((item) => { + const isSelected = selectedItems.some(selected => selected.id === item.id) + + return ( +
handleItemToggle(item)} + > +
+ {isSelected ? ( + + ) : ( + + )} +
+ {/* Hull 아이템 표시: "item_list / sub_item_list" / item_code / 공종 */} +
+ {item.itemList || '아이템명 없음'} + {item.subItemList && ` / ${item.subItemList}`} +
+
+ {item.itemCode || '아이템코드 없음'} +
+
+ 공종: {item.workType} +
+
+
+
+ ) + }) + ) : ( +
+ {itemSearchQuery ? "검색 결과가 없습니다" : "아이템이 없습니다"} +
+ )} +
+
+
+
+
+
+
+ +
+ + {/* Footer - Sticky 버튼 영역 */} +
+
+ + +
+
+
+
+ ) } \ No newline at end of file -- cgit v1.2.3