diff options
| -rw-r--r-- | components/common/selectors/procurement-manager/README.md | 18 | ||||
| -rw-r--r-- | components/form-data/form-data-table.tsx | 2 | ||||
| -rw-r--r-- | components/form-data/sedp-compare-dialog.tsx | 2 | ||||
| -rw-r--r-- | components/vendor-data/sidebar copy.tsx | 256 | ||||
| -rw-r--r-- | components/vendor-data/sidebar.tsx | 211 | ||||
| -rw-r--r-- | lib/tags/table/tags-table-toolbar-actions.tsx | 64 |
6 files changed, 458 insertions, 95 deletions
diff --git a/components/common/selectors/procurement-manager/README.md b/components/common/selectors/procurement-manager/README.md new file mode 100644 index 00000000..5f8e4021 --- /dev/null +++ b/components/common/selectors/procurement-manager/README.md @@ -0,0 +1,18 @@ +# 조달담당자 선택기 + +```sql +-- oracle code +SELECT NM.CD -- "조달코드", + NM.CDNM -- "담당자명", + NM.GRP_DSC -- "부서명", + USR_DF_CHAR_1 -- "사번", + USR_DF_CHAR_2 -- "부서코드", + USR_DF_CHK_1 -- "사용" +FROM CMCTB_CD CD, + CMCTB_CDNM NM +WHERE CD.CD_CLF = NM.CD_CLF + AND CD.CD = NM.CD + AND CD.CD2 = NM.CD2 + AND CD.CD3 = NM.CD3 + AND CD.CD_CLF = 'MMK010' +```
\ No newline at end of file diff --git a/components/form-data/form-data-table.tsx b/components/form-data/form-data-table.tsx index 9dbcb627..0f55c559 100644 --- a/components/form-data/form-data-table.tsx +++ b/components/form-data/form-data-table.tsx @@ -779,7 +779,7 @@ export default function DynamicTable({ <> <div className="mb-6"> - <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-6"> + <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-5"> {/* Total Tags Card - 클릭 시 전체 보기 */} <Card className={`cursor-pointer transition-all ${activeFilter === null ? 'ring-2 ring-primary' : 'hover:shadow-lg' diff --git a/components/form-data/sedp-compare-dialog.tsx b/components/form-data/sedp-compare-dialog.tsx index 9cc08657..60642145 100644 --- a/components/form-data/sedp-compare-dialog.tsx +++ b/components/form-data/sedp-compare-dialog.tsx @@ -318,7 +318,7 @@ export function SEDPCompareDialog({ // Compare attributes const attributeComparisons = columnsJSON - .filter(col => col.key !== "TAG_NO" && col.key !== "TAG_DESC" && col.key !== "status") + .filter(col => col.key !== "TAG_NO" && col.key !== "TAG_DESC" && col.key !== "status"&& col.key !== "CLS_ID") .map(col => { const localValue = localItem[col.key]; const sedpValue = sedpItem.attributes.get(col.key); diff --git a/components/vendor-data/sidebar copy.tsx b/components/vendor-data/sidebar copy.tsx new file mode 100644 index 00000000..a6b35a9d --- /dev/null +++ b/components/vendor-data/sidebar copy.tsx @@ -0,0 +1,256 @@ +"use client" + +import * as React from "react" +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { ScrollArea } from "@/components/ui/scroll-area" +import { Separator } from "@/components/ui/separator" +import { + Tooltip, + TooltipTrigger, + TooltipContent, +} from "@/components/ui/tooltip" +import { Package2, FormInput } from "lucide-react" +import { useRouter, usePathname } from "next/navigation" +import { Skeleton } from "@/components/ui/skeleton" +import { type FormInfo } from "@/lib/forms/services" + +interface PackageData { + itemId: number + itemName: string +} + +interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> { + isCollapsed: boolean + packages: PackageData[] + selectedPackageId: number | null + selectedProjectId: number | null + selectedContractId: number | null + onSelectPackage: (itemId: number) => void + forms?: FormInfo[] // 선택적 속성으로 변경 + onSelectForm: (formName: string) => void + isLoadingForms?: boolean + mode: "IM" | "ENG" // 새로 추가: 현재 선택된 모드 +} + +export function Sidebar({ + className, + isCollapsed, + packages, + selectedPackageId, + selectedProjectId, + selectedContractId, + onSelectPackage, + forms, + // selectedForm, // 사용되지 않음 + onSelectForm, + isLoadingForms = false, + mode = "IM", // 기본값 설정 +}: SidebarProps) { + const router = useRouter() + const rawPathname = usePathname() + const pathname = rawPathname ?? "" + + + /** + * --------------------------- + * 1) URL에서 현재 패키지 / 폼 코드 추출 + * --------------------------- + */ + const segments = pathname.split("/").filter(Boolean) + // 예) "/partners/vendor-data/tag/123" => ["partners","vendor-data","tag","123"] + + let currentItemId: number | null = null + let currentFormCode: string | null = null + + const tagIndex = segments.indexOf("tag") + if (tagIndex !== -1 && segments[tagIndex + 1]) { + // tag 뒤에 오는 값이 패키지 itemId + currentItemId = parseInt(segments[tagIndex + 1], 10) + } + + const formIndex = segments.indexOf("form") + if (formIndex !== -1) { + // form 뒤 첫 파라미터 => itemId, 그 다음 파라미터 => formCode + const itemSegment = segments[formIndex + 1] + const codeSegment = segments[formIndex + 2] + + if (itemSegment) { + currentItemId = parseInt(itemSegment, 10) + } + if (codeSegment) { + currentFormCode = codeSegment + } + } + + /** + * --------------------------- + * 2) 패키지 클릭 핸들러 + * --------------------------- + */ + const handlePackageClick = (itemId: number) => { + // 상위 컴포넌트 상태 업데이트만 수행 + // 라우팅은 하지 않음 (프로젝트 선택 상태 유지) + onSelectPackage(itemId) + } + + /** + * --------------------------- + * 3) 폼 클릭 핸들러 (mode 추가) + * --------------------------- + */ + const handleFormClick = (form: FormInfo) => { + // 패키지 ID 선택 전략 + let packageId: number; + + if (mode === "ENG") { + // ENG 모드에서는 첫 번째 패키지 ID 또는 현재 URL에서 추출한 ID 사용 + packageId = 0; + } else { + // IM 모드에서는 반드시 선택된 패키지 ID 필요 + if (selectedPackageId === null) return; + packageId = selectedPackageId; + } + + // 상위 컴포넌트 상태 업데이트 + onSelectForm(form.formName) + + // 해당 폼 페이지로 라우팅 + // 예: /vendor-data/form/[packageId]/[formCode] + const baseSegments = segments.slice(0, segments.indexOf("vendor-data") + 1).join("/") + // 모드 정보를 쿼리 파라미터로 추가 + router.push(`/${baseSegments}/form/${packageId}/${form.formCode}/${selectedProjectId}/${selectedContractId}?mode=${mode}`) + } + + return ( + <div className={cn("pb-12", className)}> + <div className="space-y-4 py-4"> + {/* ---------- 패키지(Items) 목록 - IM 모드에서만 표시 ---------- */} + {mode === "IM" && ( + <> + <div className="py-1"> + <h2 className="relative px-7 text-lg font-semibold tracking-tight"> + {isCollapsed ? "P" : "Package Lists"} + </h2> + <ScrollArea className="h-[150px] px-1"> + <div className="space-y-1 p-2"> + {packages.map((pkg) => { + // URL 기준으로 active 여부 판단 + const isActive = pkg.itemId === currentItemId + + return ( + <div key={pkg.itemId}> + {isCollapsed ? ( + <Tooltip delayDuration={0}> + <TooltipTrigger asChild> + <Button + variant="ghost" + className={cn( + "w-full justify-start font-normal", + isActive && "bg-accent text-accent-foreground" + )} + onClick={() => handlePackageClick(pkg.itemId)} + > + <Package2 className="mr-2 h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent side="right"> + {pkg.itemName} + </TooltipContent> + </Tooltip> + ) : ( + <Button + variant="ghost" + className={cn( + "w-full justify-start font-normal", + isActive && "bg-accent text-accent-foreground" + )} + onClick={() => handlePackageClick(pkg.itemId)} + > + <Package2 className="mr-2 h-4 w-4" /> + {pkg.itemName} + </Button> + )} + </div> + ) + })} + </div> + </ScrollArea> + </div> + <Separator /> + </> + )} + + {/* ---------- 폼 목록 ---------- */} + <div className="py-1"> + <h2 className="relative px-7 text-lg font-semibold tracking-tight"> + {isCollapsed ? "F" : "Form Lists"} + </h2> + <ScrollArea className={cn( + "px-1", + // IM 모드는 더 작은 높이, ENG 모드는 더 큰 높이 + mode === "IM" ? "h-[300px]" : "h-[450px]" + )}> + <div className="space-y-1 p-2"> + {isLoadingForms ? ( + // 로딩 중 스켈레톤 UI 표시 + Array.from({ length: 3 }).map((_, index) => ( + <div key={`form-skeleton-${index}`} className="px-2 py-1.5"> + <Skeleton className="h-8 w-full" /> + </div> + )) + ) : !forms || forms.length === 0 ? ( + <p className="text-sm text-muted-foreground px-2"> + (No forms loaded) + </p> + ) : ( + forms.map((form) => { + // URL 기준으로 active 여부 판단 + const isActive = form.formCode === currentFormCode + + // IM 모드에서만 패키지 선택 여부에 따라 비활성화 + const isDisabled = mode === "IM" && currentItemId === null; + + return isCollapsed ? ( + <Tooltip key={form.formCode} delayDuration={0}> + <TooltipTrigger asChild> + <Button + variant="ghost" + className={cn( + "w-full justify-start font-normal", + isActive && "bg-accent text-accent-foreground" + )} + onClick={() => handleFormClick(form)} + disabled={isDisabled} + > + <FormInput className="mr-2 h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent side="right"> + {form.formName} + </TooltipContent> + </Tooltip> + ) : ( + <Button + key={form.formCode} + variant="ghost" + className={cn( + "w-full justify-start font-normal", + isActive && "bg-accent text-accent-foreground" + )} + onClick={() => handleFormClick(form)} + disabled={isDisabled} + > + <FormInput className="mr-2 h-4 w-4" /> + {form.formName} + </Button> + ) + }) + )} + </div> + </ScrollArea> + </div> + </div> + </div> + ) +}
\ No newline at end of file diff --git a/components/vendor-data/sidebar.tsx b/components/vendor-data/sidebar.tsx index a6b35a9d..2e633442 100644 --- a/components/vendor-data/sidebar.tsx +++ b/components/vendor-data/sidebar.tsx @@ -10,7 +10,7 @@ import { TooltipTrigger, TooltipContent, } from "@/components/ui/tooltip" -import { Package2, FormInput } from "lucide-react" +import { Package2, FormInput, ChevronRight, ChevronDown } from "lucide-react" import { useRouter, usePathname } from "next/navigation" import { Skeleton } from "@/components/ui/skeleton" import { type FormInfo } from "@/lib/forms/services" @@ -27,10 +27,10 @@ interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> { selectedProjectId: number | null selectedContractId: number | null onSelectPackage: (itemId: number) => void - forms?: FormInfo[] // 선택적 속성으로 변경 + forms?: FormInfo[] onSelectForm: (formName: string) => void isLoadingForms?: boolean - mode: "IM" | "ENG" // 새로 추가: 현재 선택된 모드 + mode: "IM" | "ENG" } export function Sidebar({ @@ -42,36 +42,34 @@ export function Sidebar({ selectedContractId, onSelectPackage, forms, - // selectedForm, // 사용되지 않음 onSelectForm, isLoadingForms = false, - mode = "IM", // 기본값 설정 + mode = "IM", }: SidebarProps) { const router = useRouter() const rawPathname = usePathname() const pathname = rawPathname ?? "" + + // ENG 모드에서 각 폼의 확장/축소 상태 관리 + const [expandedForms, setExpandedForms] = React.useState<Set<string>>(new Set()) - /** * --------------------------- * 1) URL에서 현재 패키지 / 폼 코드 추출 * --------------------------- */ - const segments = pathname.split("/").filter(Boolean) - // 예) "/partners/vendor-data/tag/123" => ["partners","vendor-data","tag","123"] + const segments = pathname.split("/").filter(Boolean) let currentItemId: number | null = null let currentFormCode: string | null = null const tagIndex = segments.indexOf("tag") if (tagIndex !== -1 && segments[tagIndex + 1]) { - // tag 뒤에 오는 값이 패키지 itemId currentItemId = parseInt(segments[tagIndex + 1], 10) } const formIndex = segments.indexOf("form") if (formIndex !== -1) { - // form 뒤 첫 파라미터 => itemId, 그 다음 파라미터 => formCode const itemSegment = segments[formIndex + 1] const codeSegment = segments[formIndex + 2] @@ -85,41 +83,50 @@ export function Sidebar({ /** * --------------------------- - * 2) 패키지 클릭 핸들러 + * 2) 패키지 클릭 핸들러 (IM 모드) * --------------------------- */ const handlePackageClick = (itemId: number) => { - // 상위 컴포넌트 상태 업데이트만 수행 - // 라우팅은 하지 않음 (프로젝트 선택 상태 유지) onSelectPackage(itemId) } /** * --------------------------- - * 3) 폼 클릭 핸들러 (mode 추가) + * 3) 폼 클릭 핸들러 (IM 모드) * --------------------------- */ const handleFormClick = (form: FormInfo) => { - // 패키지 ID 선택 전략 - let packageId: number; - - if (mode === "ENG") { - // ENG 모드에서는 첫 번째 패키지 ID 또는 현재 URL에서 추출한 ID 사용 - packageId = 0; - } else { + if (mode === "IM") { // IM 모드에서는 반드시 선택된 패키지 ID 필요 if (selectedPackageId === null) return; - packageId = selectedPackageId; + + onSelectForm(form.formName) + + const baseSegments = segments.slice(0, segments.indexOf("vendor-data") + 1).join("/") + router.push(`/${baseSegments}/form/${selectedPackageId}/${form.formCode}/${selectedProjectId}/${selectedContractId}?mode=${mode}`) + } else { + // ENG 모드에서는 폼을 클릭하면 확장/축소만 토글 + const newExpanded = new Set(expandedForms) + if (newExpanded.has(form.formCode)) { + newExpanded.delete(form.formCode) + } else { + newExpanded.add(form.formCode) + } + setExpandedForms(newExpanded) } + } - // 상위 컴포넌트 상태 업데이트 + /** + * --------------------------- + * 4) 패키지 클릭 핸들러 (ENG 모드) + * --------------------------- + */ + const handlePackageUnderFormClick = (form: FormInfo, pkg: PackageData) => { onSelectForm(form.formName) - - // 해당 폼 페이지로 라우팅 - // 예: /vendor-data/form/[packageId]/[formCode] + onSelectPackage(pkg.itemId) + const baseSegments = segments.slice(0, segments.indexOf("vendor-data") + 1).join("/") - // 모드 정보를 쿼리 파라미터로 추가 - router.push(`/${baseSegments}/form/${packageId}/${form.formCode}/${selectedProjectId}/${selectedContractId}?mode=${mode}`) + router.push(`/${baseSegments}/form/${pkg.itemId}/${form.formCode}/${selectedProjectId}/${selectedContractId}?mode=${mode}`) } return ( @@ -135,7 +142,6 @@ export function Sidebar({ <ScrollArea className="h-[150px] px-1"> <div className="space-y-1 p-2"> {packages.map((pkg) => { - // URL 기준으로 active 여부 판단 const isActive = pkg.itemId === currentItemId return ( @@ -181,19 +187,17 @@ export function Sidebar({ </> )} - {/* ---------- 폼 목록 ---------- */} + {/* ---------- 폼 목록 (IM 모드) / 폼과 패키지 목록 (ENG 모드) ---------- */} <div className="py-1"> <h2 className="relative px-7 text-lg font-semibold tracking-tight"> {isCollapsed ? "F" : "Form Lists"} </h2> <ScrollArea className={cn( "px-1", - // IM 모드는 더 작은 높이, ENG 모드는 더 큰 높이 mode === "IM" ? "h-[300px]" : "h-[450px]" )}> <div className="space-y-1 p-2"> {isLoadingForms ? ( - // 로딩 중 스켈레톤 UI 표시 Array.from({ length: 3 }).map((_, index) => ( <div key={`form-skeleton-${index}`} className="px-2 py-1.5"> <Skeleton className="h-8 w-full" /> @@ -205,45 +209,118 @@ export function Sidebar({ </p> ) : ( forms.map((form) => { - // URL 기준으로 active 여부 판단 - const isActive = form.formCode === currentFormCode + const isFormActive = form.formCode === currentFormCode + const isExpanded = expandedForms.has(form.formCode) + + // IM 모드 + if (mode === "IM") { + const isDisabled = currentItemId === null - // IM 모드에서만 패키지 선택 여부에 따라 비활성화 - const isDisabled = mode === "IM" && currentItemId === null; + return isCollapsed ? ( + <Tooltip key={form.formCode} delayDuration={0}> + <TooltipTrigger asChild> + <Button + variant="ghost" + className={cn( + "w-full justify-start font-normal", + isFormActive && "bg-accent text-accent-foreground" + )} + onClick={() => handleFormClick(form)} + disabled={isDisabled} + > + <FormInput className="mr-2 h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent side="right"> + {form.formName} + </TooltipContent> + </Tooltip> + ) : ( + <Button + key={form.formCode} + variant="ghost" + className={cn( + "w-full justify-start font-normal", + isFormActive && "bg-accent text-accent-foreground" + )} + onClick={() => handleFormClick(form)} + disabled={isDisabled} + > + <FormInput className="mr-2 h-4 w-4" /> + {form.formName} + </Button> + ) + } + + // ENG 모드 - 폼과 그 아래 패키지들 표시 + return ( + <div key={form.formCode}> + {isCollapsed ? ( + <Tooltip delayDuration={0}> + <TooltipTrigger asChild> + <Button + variant="ghost" + className="w-full justify-start font-normal" + // onClick={() => handleFormClick(form)} + > + <FormInput className="mr-2 h-4 w-4" /> + </Button> + </TooltipTrigger> + <TooltipContent side="right"> + {form.formName} + </TooltipContent> + </Tooltip> + ) : ( + <> + <Button + variant="ghost" + className="w-full justify-start font-normal" + // onClick={() => handleFormClick(form)} + > + {isExpanded ? ( + <ChevronDown className="mr-2 h-4 w-4" /> + ) : ( + <ChevronRight className="mr-2 h-4 w-4" /> + )} + <FormInput className="mr-2 h-4 w-4" /> + {form.formName} + </Button> + + {/* 확장된 경우 패키지 목록 표시 */} + {isExpanded && ( + <div className="ml-4 space-y-1"> + {packages.length === 0 ? ( + <p className="text-xs text-muted-foreground px-4 py-1"> + No packages available + </p> + ) : ( + packages.map((pkg) => { + const isPackageActive = + pkg.itemId === currentItemId && + form.formCode === currentFormCode - return isCollapsed ? ( - <Tooltip key={form.formCode} delayDuration={0}> - <TooltipTrigger asChild> - <Button - variant="ghost" - className={cn( - "w-full justify-start font-normal", - isActive && "bg-accent text-accent-foreground" + return ( + <Button + key={`${form.formCode}-${pkg.itemId}`} + variant="ghost" + size="sm" + className={cn( + "w-full justify-start font-normal text-sm", + isPackageActive && "bg-accent text-accent-foreground" + )} + onClick={() => handlePackageUnderFormClick(form, pkg)} + > + <Package2 className="mr-2 h-3 w-3" /> + {pkg.itemName} + </Button> + ) + }) + )} + </div> )} - onClick={() => handleFormClick(form)} - disabled={isDisabled} - > - <FormInput className="mr-2 h-4 w-4" /> - </Button> - </TooltipTrigger> - <TooltipContent side="right"> - {form.formName} - </TooltipContent> - </Tooltip> - ) : ( - <Button - key={form.formCode} - variant="ghost" - className={cn( - "w-full justify-start font-normal", - isActive && "bg-accent text-accent-foreground" + </> )} - onClick={() => handleFormClick(form)} - disabled={isDisabled} - > - <FormInput className="mr-2 h-4 w-4" /> - {form.formName} - </Button> + </div> ) }) )} diff --git a/lib/tags/table/tags-table-toolbar-actions.tsx b/lib/tags/table/tags-table-toolbar-actions.tsx index f77ebafa..cc2d82b4 100644 --- a/lib/tags/table/tags-table-toolbar-actions.tsx +++ b/lib/tags/table/tags-table-toolbar-actions.tsx @@ -56,7 +56,7 @@ interface TagsTableToolbarActionsProps { /** 현재 태그 목록(상태) */ tableData: Tag[] /** 태그 목록을 갱신하는 setState */ - selectedMode:string + selectedMode: string } /** @@ -134,7 +134,7 @@ export function TagsTableToolbarActions({ }, [tagNumberingRules]) const [projectId, setProjectId] = React.useState<number | null>(null); - + // Add useEffect to fetch projectId when selectedPackageId changes React.useEffect(() => { const fetchProjectId = async () => { @@ -158,22 +158,22 @@ export function TagsTableToolbarActions({ if (tagOptionsCache[attributesId]) { return tagOptionsCache[attributesId]; } - + try { // Only pass projectId if it's not null let options: TagOption[]; if (projectId !== null) { options = await fetchTagSubfieldOptions(attributesId, projectId); } else { - options = [] + options = [] } - + // Update cache setTagOptionsCache(prev => ({ ...prev, [attributesId]: options })); - + return options; } catch (error) { console.error(`Error fetching options for ${attributesId}:`, error); @@ -267,12 +267,24 @@ export function TagsTableToolbarActions({ // 정규식 검증 if (field.expression) { - const regex = new RegExp(`^${field.expression}$`) - if (!regex.test(part)) { - return `Part '${part}' for field '${field.label}' does not match the pattern '${field.expression}'.` + try { + // 중복된 ^, $ 제거 후 다시 추가 + let cleanPattern = field.expression; + + // 시작과 끝의 ^, $ 제거 + cleanPattern = cleanPattern.replace(/^\^/, '').replace(/\$$/, ''); + + // 정규식 생성 (항상 전체 매칭) + const regex = new RegExp(`^${cleanPattern}$`); + + if (!regex.test(part)) { + return `Part '${part}' for field '${field.label}' does not match the pattern '${field.expression}'.`; + } + } catch (error) { + console.error(`Invalid regex pattern: ${field.expression}`, error); + return `Invalid pattern for field '${field.label}': ${field.expression}`; } } - // 선택 옵션 검증 if (field.type === "select" && field.options && field.options.length > 0) { const validValues = field.options.map(opt => opt.value) @@ -563,7 +575,7 @@ export function TagsTableToolbarActions({ setIsExporting(true) // 유효성 검사가 포함된 새로운 엑셀 내보내기 함수 호출 - await exportTagsToExcel(table,selectedPackageId, { + await exportTagsToExcel(table, selectedPackageId, { filename: `Tags_${selectedPackageId}`, excludeColumns: ["select", "actions", "createdAt", "updatedAt"], }) @@ -580,11 +592,11 @@ export function TagsTableToolbarActions({ const startGetTags = async () => { try { setIsLoading(true) - + // API 엔드포인트 호출 - 작업 시작만 요청 const response = await fetch('/api/cron/tags/start', { method: 'POST', - body: JSON.stringify({ + body: JSON.stringify({ packageId: selectedPackageId, mode: selectedMode // 모드 정보 추가 }) @@ -593,14 +605,14 @@ export function TagsTableToolbarActions({ const errorData = await response.json() throw new Error(errorData.error || 'Failed to start tag import') } - + const data = await response.json() - + // 작업 ID 저장 if (data.syncId) { setSyncId(data.syncId) toast.info('Tag import started. This may take a while...') - + // 상태 확인을 위한 폴링 시작 startPolling(data.syncId) } else { @@ -616,24 +628,24 @@ export function TagsTableToolbarActions({ setIsLoading(false) } } - + const startPolling = (id: string) => { // 이전 폴링이 있다면 제거 if (pollingRef.current) { clearInterval(pollingRef.current) } - + // 5초마다 상태 확인 pollingRef.current = setInterval(async () => { try { const response = await fetch(`/api/cron/tags/status?id=${id}`) - + if (!response.ok) { throw new Error('Failed to get tag import status') } - + const data = await response.json() - + if (data.status === 'completed') { // 폴링 중지 if (pollingRef.current) { @@ -642,16 +654,16 @@ export function TagsTableToolbarActions({ } router.refresh() - + // 상태 초기화 setIsLoading(false) setSyncId(null) - + // 성공 메시지 표시 toast.success( `Tags imported successfully! ${data.result?.processedCount || 0} items processed.` ) - + // 테이블 데이터 업데이트 table.resetRowSelection() } else if (data.status === 'failed') { @@ -660,7 +672,7 @@ export function TagsTableToolbarActions({ clearInterval(pollingRef.current) pollingRef.current = null } - + setIsLoading(false) setSyncId(null) toast.error(data.error || 'Import failed') @@ -687,7 +699,7 @@ export function TagsTableToolbarActions({ .getFilteredSelectedRowModel() .rows.map((row) => row.original)} onSuccess={() => table.toggleAllRowsSelected(false)} - selectedPackageId={selectedPackageId} + selectedPackageId={selectedPackageId} /> ) : null} <Button |
