summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-10-21 09:05:52 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-10-21 09:05:52 +0000
commitcf77558c0ccc5c0c1bc3cdd6edd9a0475e1970c8 (patch)
tree32eded30db38fe321c288d55c7f3ee136a0b4723
parent261c45877f58af5779ef796c0c9186ed021e028e (diff)
(대표님) 설계측 오류 및 스타일 등 수정
-rw-r--r--components/common/selectors/procurement-manager/README.md18
-rw-r--r--components/form-data/form-data-table.tsx2
-rw-r--r--components/form-data/sedp-compare-dialog.tsx2
-rw-r--r--components/vendor-data/sidebar copy.tsx256
-rw-r--r--components/vendor-data/sidebar.tsx211
-rw-r--r--lib/tags/table/tags-table-toolbar-actions.tsx64
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