summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/code-groups/page.tsx (renamed from app/[lng]/evcp/(evcp)/docu-list-rule/code-groups/page.tsx)35
-rw-r--r--app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/combo-box-settings/page.tsx (renamed from app/[lng]/evcp/(evcp)/docu-list-rule/combo-box-settings/page.tsx)33
-rw-r--r--app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/document-class/page.tsx (renamed from app/[lng]/evcp/(evcp)/docu-list-rule/document-class/page.tsx)31
-rw-r--r--app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/layout.tsx59
-rw-r--r--app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/number-type-configs/page.tsx (renamed from app/[lng]/evcp/(evcp)/docu-list-rule/number-type-configs/page.tsx)40
-rw-r--r--app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/number-types/page.tsx (renamed from app/[lng]/evcp/(evcp)/docu-list-rule/number-types/page.tsx)31
-rw-r--r--app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/page.tsx10
-rw-r--r--app/[lng]/evcp/(evcp)/docu-list-rule/layout.tsx70
-rw-r--r--app/[lng]/evcp/(evcp)/docu-list-rule/page.tsx22
-rw-r--r--components/docu-list-rule/docu-list-rule-client.tsx141
-rw-r--r--components/docu-list-rule/dynamic-title-client.tsx52
-rw-r--r--db/schema/docu-list-rule.ts12
-rw-r--r--lib/docu-list-rule/code-groups/service.ts8
-rw-r--r--lib/docu-list-rule/code-groups/table/code-groups-add-dialog.tsx66
-rw-r--r--lib/docu-list-rule/code-groups/table/code-groups-table-columns.tsx13
-rw-r--r--lib/docu-list-rule/code-groups/table/code-groups-table.tsx7
-rw-r--r--lib/docu-list-rule/code-groups/validation.ts1
-rw-r--r--lib/docu-list-rule/combo-box-settings/service.ts119
-rw-r--r--lib/docu-list-rule/combo-box-settings/table/combo-box-options-detail-sheet.tsx80
-rw-r--r--lib/docu-list-rule/combo-box-settings/table/combo-box-options-table-columns.tsx13
-rw-r--r--lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table-columns.tsx14
-rw-r--r--lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table.tsx7
-rw-r--r--lib/docu-list-rule/combo-box-settings/validation.ts1
-rw-r--r--lib/docu-list-rule/document-class/service.ts78
-rw-r--r--lib/docu-list-rule/document-class/table/document-class-add-dialog.tsx73
-rw-r--r--lib/docu-list-rule/document-class/table/document-class-options-detail-sheet.tsx69
-rw-r--r--lib/docu-list-rule/document-class/table/document-class-options-table-columns.tsx13
-rw-r--r--lib/docu-list-rule/document-class/table/document-class-table-columns.tsx14
-rw-r--r--lib/docu-list-rule/document-class/table/document-class-table.tsx7
-rw-r--r--lib/docu-list-rule/document-class/validation.ts1
-rw-r--r--lib/docu-list-rule/number-type-configs/service.ts9
-rw-r--r--lib/docu-list-rule/number-type-configs/table/number-type-configs-table-columns.tsx14
-rw-r--r--lib/docu-list-rule/number-type-configs/table/number-type-configs-table.tsx47
-rw-r--r--lib/docu-list-rule/number-type-configs/table/number-type-selector.tsx122
-rw-r--r--lib/docu-list-rule/number-type-configs/validation.ts1
-rw-r--r--lib/docu-list-rule/number-types/service.ts16
-rw-r--r--lib/docu-list-rule/number-types/table/number-type-add-dialog.tsx73
-rw-r--r--lib/docu-list-rule/number-types/table/number-types-table-columns.tsx13
-rw-r--r--lib/docu-list-rule/number-types/table/number-types-table.tsx7
-rw-r--r--lib/docu-list-rule/number-types/validation.ts1
40 files changed, 794 insertions, 629 deletions
diff --git a/app/[lng]/evcp/(evcp)/docu-list-rule/code-groups/page.tsx b/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/code-groups/page.tsx
index 5aebf15d..c75bf6b9 100644
--- a/app/[lng]/evcp/(evcp)/docu-list-rule/code-groups/page.tsx
+++ b/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/code-groups/page.tsx
@@ -1,20 +1,27 @@
import * as React from "react";
import { type SearchParams } from "@/types/table";
-import { Shell } from "@/components/shell";
-import { Skeleton } from "@/components/ui/skeleton";
import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton";
import { getCodeGroups } from "@/lib/docu-list-rule/code-groups/service";
import { CodeGroupsTable } from "@/lib/docu-list-rule/code-groups/table/code-groups-table";
import { searchParamsCodeGroupsCache } from "@/lib/docu-list-rule/code-groups/validation";
-import { InformationButton } from "@/components/information/information-button";
+
interface IndexPageProps {
searchParams: Promise<SearchParams>;
+ params: Promise<{ lng: string; projectId: string }>;
}
export default async function IndexPage(props: IndexPageProps) {
const searchParams = await props.searchParams;
- const search = searchParamsCodeGroupsCache.parse(searchParams);
+ const { projectId } = await props.params;
+
+ // 프로젝트 ID를 필터에 추가
+ const searchParamsWithProject = {
+ ...searchParams,
+ projectId: projectId
+ };
+
+ const search = searchParamsCodeGroupsCache.parse(searchParamsWithProject);
const promises = Promise.all([
getCodeGroups({
@@ -23,19 +30,9 @@ export default async function IndexPage(props: IndexPageProps) {
]);
return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <div className="flex items-center gap-2">
- <h2 className="text-2xl font-bold tracking-tight">Code Group 정의</h2>
- <InformationButton pagePath="evcp/docu-list-rule/code-groups" />
- </div>
- {/* <p className="text-muted-foreground">
- 문서 번호에 사용될 수 있는 다양한 코드 그룹의 정의를 관리하는 페이지입니다.
- </p> */}
- </div>
- </div>
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}></React.Suspense>
+ <>
+
+
<React.Suspense
fallback={
<DataTableSkeleton
@@ -49,6 +46,6 @@ export default async function IndexPage(props: IndexPageProps) {
>
<CodeGroupsTable promises={promises} />
</React.Suspense>
- </Shell>
+ </>
);
-} \ No newline at end of file
+} \ No newline at end of file
diff --git a/app/[lng]/evcp/(evcp)/docu-list-rule/combo-box-settings/page.tsx b/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/combo-box-settings/page.tsx
index 194449a7..ffe30710 100644
--- a/app/[lng]/evcp/(evcp)/docu-list-rule/combo-box-settings/page.tsx
+++ b/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/combo-box-settings/page.tsx
@@ -1,39 +1,36 @@
import * as React from "react";
import { type SearchParams } from "@/types/table";
-import { Shell } from "@/components/shell";
import { Skeleton } from "@/components/ui/skeleton";
import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton";
import { getComboBoxCodeGroups } from "@/lib/docu-list-rule/combo-box-settings/service";
import { ComboBoxSettingsTable } from "@/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table";
import { searchParamsComboBoxSettingsCache } from "@/lib/docu-list-rule/combo-box-settings/validation";
-import { InformationButton } from "@/components/information/information-button";
+
interface IndexPageProps {
searchParams: Promise<SearchParams>;
+ params: Promise<{ lng: string; projectId: string }>;
}
export default async function IndexPage(props: IndexPageProps) {
const searchParams = await props.searchParams;
- const search = searchParamsComboBoxSettingsCache.parse(searchParams);
+ const { projectId } = await props.params;
+
+ // 프로젝트 ID를 필터에 추가
+ const searchParamsWithProject = {
+ ...searchParams,
+ projectId: projectId
+ };
+
+ const search = searchParamsComboBoxSettingsCache.parse(searchParamsWithProject);
const promises = Promise.all([
getComboBoxCodeGroups(search),
]);
return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <div className="flex items-center gap-2">
- <h2 className="text-2xl font-bold tracking-tight">Combo Box 설정</h2>
- <InformationButton pagePath="evcp/docu-list-rule/combo-box-settings" />
- </div>
- {/* <p className="text-muted-foreground">
- Combo Box 옵션을 관리하는 페이지입니다.
- 각 Code Group별로 Combo Box에 표시될 옵션들을 설정할 수 있습니다.
- </p> */}
- </div>
- </div>
+ <>
+
<React.Suspense fallback={<Skeleton className="h-7 w-52" />}></React.Suspense>
<React.Suspense
fallback={
@@ -48,6 +45,6 @@ export default async function IndexPage(props: IndexPageProps) {
>
<ComboBoxSettingsTable promises={promises} />
</React.Suspense>
- </Shell>
+ </>
);
-} \ No newline at end of file
+}
diff --git a/app/[lng]/evcp/(evcp)/docu-list-rule/document-class/page.tsx b/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/document-class/page.tsx
index 5c2c600e..8a0f1bd7 100644
--- a/app/[lng]/evcp/(evcp)/docu-list-rule/document-class/page.tsx
+++ b/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/document-class/page.tsx
@@ -1,38 +1,35 @@
import * as React from "react";
-import { Shell } from "@/components/shell";
import { Skeleton } from "@/components/ui/skeleton";
import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton";
import { getDocumentClassCodeGroups } from "@/lib/docu-list-rule/document-class/service";
import { DocumentClassTable } from "@/lib/docu-list-rule/document-class/table/document-class-table";
-import { InformationButton } from "@/components/information/information-button";
+
import { searchParamsDocumentClassCache } from "@/lib/docu-list-rule/document-class/validation";
interface IndexPageProps {
searchParams: Promise<any>;
+ params: Promise<{ lng: string; projectId: string }>;
}
export default async function IndexPage(props: IndexPageProps) {
const searchParams = await props.searchParams;
+ const { projectId } = await props.params;
+
+ // 프로젝트 ID를 필터에 추가
+ const searchParamsWithProject = {
+ ...searchParams,
+ projectId: projectId
+ };
const promises = Promise.all([
getDocumentClassCodeGroups(
- searchParamsDocumentClassCache.parse(searchParams)
+ searchParamsDocumentClassCache.parse(searchParamsWithProject)
),
]);
return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <div className="flex items-center gap-2">
- <h2 className="text-2xl font-bold tracking-tight">Document Class 관리</h2>
- <InformationButton pagePath="evcp/docu-list-rule/document-class" />
- </div>
- {/* <p className="text-muted-foreground">
- Document Class를 관리합니다.
- </p> */}
- </div>
- </div>
+ <>
+
<React.Suspense fallback={<Skeleton className="h-7 w-52" />}></React.Suspense>
<React.Suspense
fallback={
@@ -47,6 +44,6 @@ export default async function IndexPage(props: IndexPageProps) {
>
<DocumentClassTable promises={promises} />
</React.Suspense>
- </Shell>
+ </>
);
-} \ No newline at end of file
+}
diff --git a/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/layout.tsx b/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/layout.tsx
new file mode 100644
index 00000000..197c35b2
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/layout.tsx
@@ -0,0 +1,59 @@
+import { Metadata } from "next"
+import { Separator } from "@/components/ui/separator"
+import { SidebarNav } from "@/components/layout/sidebar-nav"
+import { DynamicTitleClient } from "@/components/docu-list-rule/dynamic-title-client"
+
+export const metadata: Metadata = {
+ title: "Document Numbering Rule",
+}
+
+export default async function ProjectDocuListRuleLayout({
+ children,
+ params,
+}: {
+ children: React.ReactNode
+ params: Promise<{ lng: string; projectId: string }>
+}) {
+ const { lng, projectId } = await params
+
+ const sidebarNavItems = [
+ {
+ title: "Document Class 관리",
+ href: `/${lng}/evcp/docu-list-rule/${projectId}/document-class`,
+ },
+ {
+ title: "Code Group 정의",
+ href: `/${lng}/evcp/docu-list-rule/${projectId}/code-groups`,
+ },
+ {
+ title: "Combo Box 설정",
+ href: `/${lng}/evcp/docu-list-rule/${projectId}/combo-box-settings`,
+ },
+ {
+ title: "Number Type 관리",
+ href: `/${lng}/evcp/docu-list-rule/${projectId}/number-types`,
+ },
+ {
+ title: "Number Type별 설정",
+ href: `/${lng}/evcp/docu-list-rule/${projectId}/number-type-configs`,
+ },
+ ]
+
+ return (
+ <>
+ <div className="hidden space-y-6 p-5 pb-16 md:block">
+ <DynamicTitleClient />
+
+ <Separator className="my-6" />
+ <div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
+ <aside className="-mx-4 lg:w-1/5">
+ <SidebarNav items={sidebarNavItems} />
+ </aside>
+ <div className="flex-1 ">{children}</div>
+ </div>
+ </div>
+
+
+ </>
+ )
+}
diff --git a/app/[lng]/evcp/(evcp)/docu-list-rule/number-type-configs/page.tsx b/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/number-type-configs/page.tsx
index c3556fb7..78714dae 100644
--- a/app/[lng]/evcp/(evcp)/docu-list-rule/number-type-configs/page.tsx
+++ b/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/number-type-configs/page.tsx
@@ -1,11 +1,10 @@
import * as React from "react";
-import { Shell } from "@/components/shell";
import { Skeleton } from "@/components/ui/skeleton";
import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton";
import { NumberTypeConfigsTable } from "@/lib/docu-list-rule/number-type-configs/table/number-type-configs-table";
import { getNumberTypes } from "@/lib/docu-list-rule/number-types/service";
import { getNumberTypeConfigs } from "@/lib/docu-list-rule/number-type-configs/service";
-import { InformationButton } from "@/components/information/information-button";
+
import { searchParamsNumberTypeConfigsCache } from "@/lib/docu-list-rule/number-type-configs/validation";
// Number Type with project info type
@@ -23,11 +22,20 @@ type NumberTypeWithProject = {
interface IndexPageProps {
searchParams: Promise<any>;
+ params: Promise<{ lng: string; projectId: string }>;
}
export default async function IndexPage(props: IndexPageProps) {
const searchParams = await props.searchParams;
- const parsedSearchParams = await searchParamsNumberTypeConfigsCache.parse(searchParams);
+ const { projectId } = await props.params;
+
+ // 프로젝트 ID를 필터에 추가
+ const searchParamsWithProject = {
+ ...searchParams,
+ projectId: projectId
+ };
+
+ const parsedSearchParams = await searchParamsNumberTypeConfigsCache.parse(searchParamsWithProject);
const promises = Promise.all([
getNumberTypes({
@@ -40,25 +48,19 @@ export default async function IndexPage(props: IndexPageProps) {
flags: ["advancedTable"],
numberTypeId: "",
description: "",
- isActive: ""
+ isActive: "",
+ projectId: projectId // 프로젝트 ID 추가
}) as Promise<{ data: NumberTypeWithProject[]; pageCount: number }>,
// Number Type Configs도 서버 사이드에서 가져오기
- parsedSearchParams.numberTypeId > 0 ? getNumberTypeConfigs(parsedSearchParams) : Promise.resolve({ success: true, data: [], pageCount: 0 }),
+ parsedSearchParams.numberTypeId > 0 ? getNumberTypeConfigs({
+ ...parsedSearchParams,
+ projectId: projectId
+ }) : Promise.resolve({ success: true, data: [], pageCount: 0 }),
]);
return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <div className="flex items-center gap-2">
- <h2 className="text-2xl font-bold tracking-tight">Number Type별 설정</h2>
- <InformationButton pagePath="evcp/docu-list-rule/number-type-configs" />
- </div>
- {/* <p className="text-muted-foreground">
- 각 문서 번호 유형별로 어떤 코드 그룹들을 어떤 순서로 사용할지 설정하는 페이지입니다.
- </p> */}
- </div>
- </div>
+ <>
+
<React.Suspense fallback={<Skeleton className="h-7 w-52" />}></React.Suspense>
<React.Suspense
fallback={
@@ -73,6 +75,6 @@ export default async function IndexPage(props: IndexPageProps) {
>
<NumberTypeConfigsTable promises={promises} searchParams={parsedSearchParams} />
</React.Suspense>
- </Shell>
+ </>
);
-} \ No newline at end of file
+}
diff --git a/app/[lng]/evcp/(evcp)/docu-list-rule/number-types/page.tsx b/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/number-types/page.tsx
index 58af176f..e18d536d 100644
--- a/app/[lng]/evcp/(evcp)/docu-list-rule/number-types/page.tsx
+++ b/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/number-types/page.tsx
@@ -1,38 +1,35 @@
import * as React from "react";
-import { Shell } from "@/components/shell";
import { Skeleton } from "@/components/ui/skeleton";
import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton";
import { NumberTypesTable } from "@/lib/docu-list-rule/number-types/table/number-types-table";
import { getNumberTypesWithConfigs } from "@/lib/docu-list-rule/number-types/service";
-import { InformationButton } from "@/components/information/information-button";
+
import { searchParamsNumberTypesCache } from "@/lib/docu-list-rule/number-types/validation";
interface IndexPageProps {
searchParams: Promise<any>;
+ params: Promise<{ lng: string; projectId: string }>;
}
export default async function IndexPage(props: IndexPageProps) {
const searchParams = await props.searchParams;
+ const { projectId } = await props.params;
+
+ // 프로젝트 ID를 필터에 추가
+ const searchParamsWithProject = {
+ ...searchParams,
+ projectId: projectId
+ };
const promises = Promise.all([
getNumberTypesWithConfigs(
- searchParamsNumberTypesCache.parse(searchParams)
+ searchParamsNumberTypesCache.parse(searchParamsWithProject)
),
]);
return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <div className="flex items-center gap-2">
- <h2 className="text-2xl font-bold tracking-tight">Number Type 관리</h2>
- <InformationButton pagePath="evcp/docu-list-rule/number-types" />
- </div>
- {/* <p className="text-muted-foreground">
- 문서 번호 유형을 추가, 수정, 삭제할 수 있는 페이지입니다.
- </p> */}
- </div>
- </div>
+ <>
+
<React.Suspense fallback={<Skeleton className="h-7 w-52" />}></React.Suspense>
<React.Suspense
fallback={
@@ -47,6 +44,6 @@ export default async function IndexPage(props: IndexPageProps) {
>
<NumberTypesTable promises={promises} />
</React.Suspense>
- </Shell>
+ </>
);
-} \ No newline at end of file
+}
diff --git a/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/page.tsx b/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/page.tsx
new file mode 100644
index 00000000..59bec55a
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/docu-list-rule/[projectId]/page.tsx
@@ -0,0 +1,10 @@
+import { redirect } from "next/navigation"
+
+export default async function ProjectDocuListRulePage({
+ params,
+}: {
+ params: Promise<{ lng: string; projectId: string }>
+}) {
+ const { lng, projectId } = await params
+ redirect(`/${lng}/evcp/docu-list-rule/${projectId}/document-class`)
+}
diff --git a/app/[lng]/evcp/(evcp)/docu-list-rule/layout.tsx b/app/[lng]/evcp/(evcp)/docu-list-rule/layout.tsx
index 03473293..05231bf1 100644
--- a/app/[lng]/evcp/(evcp)/docu-list-rule/layout.tsx
+++ b/app/[lng]/evcp/(evcp)/docu-list-rule/layout.tsx
@@ -1,69 +1,17 @@
-import { Metadata } from "next"
+import { Shell } from "@/components/shell"
+import DocuListRuleClient from "@/components/docu-list-rule/docu-list-rule-client"
-import { Separator } from "@/components/ui/separator"
-import { SidebarNav } from "@/components/layout/sidebar-nav"
-
-export const metadata: Metadata = {
- title: "Document Numbering Rule",
-}
-
-
-
-export default async function DocumentNumberingLayout({
+// Layout 컴포넌트는 서버 컴포넌트입니다
+export default async function VendorDocuments({
children,
- params,
}: {
children: React.ReactNode
- params: { lng: string }
}) {
- const resolvedParams = await params
- const lng = resolvedParams.lng
-
- const sidebarNavItems = [
- {
- title: "Document Class 관리",
- href: `/${lng}/evcp/docu-list-rule/document-class`,
- },
- {
- title: "Code Group 정의",
- href: `/${lng}/evcp/docu-list-rule/code-groups`,
- },
- {
- title: "Combo Box 설정",
- href: `/${lng}/evcp/docu-list-rule/combo-box-settings`,
- },
- {
- title: "Number Type 관리",
- href: `/${lng}/evcp/docu-list-rule/number-types`,
- },
- {
- title: "Number Type별 설정",
- href: `/${lng}/evcp/docu-list-rule/number-type-configs`,
- },
- ]
-
return (
- <>
- <div className="container py-6">
- <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
- <div className="hidden space-y-6 p-10 pb-16 md:block">
- <div className="space-y-0.5">
- <h2 className="text-2xl font-bold tracking-tight">Document Numbering Rule (해양)</h2>
- <p className="text-muted-foreground">
- 벤더 제출 문서 리스트 작성 시에 사용되는 넘버링
- </p>
- </div>
-
- <Separator className="my-6" />
- <div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
- <aside className="-mx-4 lg:w-1/5">
- <SidebarNav items={sidebarNavItems} />
- </aside>
- <div className="flex-1 ">{children}</div>
- </div>
- </div>
- </section>
- </div>
- </>
+ <Shell className="gap-2">
+ <DocuListRuleClient>
+ {children}
+ </DocuListRuleClient>
+ </Shell>
)
} \ No newline at end of file
diff --git a/app/[lng]/evcp/(evcp)/docu-list-rule/page.tsx b/app/[lng]/evcp/(evcp)/docu-list-rule/page.tsx
index 8735e3d6..fb36b3b8 100644
--- a/app/[lng]/evcp/(evcp)/docu-list-rule/page.tsx
+++ b/app/[lng]/evcp/(evcp)/docu-list-rule/page.tsx
@@ -1,11 +1,15 @@
-import { redirect } from "next/navigation"
+export default async function IndexPage() {
+ return (
+ <div className="space-y-6">
-
-export default async function DocumentNumberingPage({
- params,
-}: {
- params: Promise<{ lng: string }>
-}) {
- const { lng } = await params
- redirect(`/${lng}/evcp/docu-list-rule/document-class`)
+ <div className="grid gap-4">
+ <div className="rounded-lg border p-4">
+ <h4 className="text-sm font-medium">시작하는 방법</h4>
+ <p className="text-sm text-muted-foreground mt-1">
+ 오른쪽 상단에서 프로젝트/계약을 선택하세요.
+ </p>
+ </div>
+ </div>
+ </div>
+ )
} \ No newline at end of file
diff --git a/components/docu-list-rule/docu-list-rule-client.tsx b/components/docu-list-rule/docu-list-rule-client.tsx
new file mode 100644
index 00000000..7e6c2bb1
--- /dev/null
+++ b/components/docu-list-rule/docu-list-rule-client.tsx
@@ -0,0 +1,141 @@
+"use client"
+import * as React from "react"
+import { useRouter, useParams } from "next/navigation"
+
+import { getProjectLists } from "@/lib/projects/service"
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select"
+
+interface DocuListRuleClientProps {
+ children: React.ReactNode
+}
+
+export default function DocuListRuleClient({
+ children,
+}: DocuListRuleClientProps) {
+ const router = useRouter()
+ const params = useParams()
+ const lng = (params?.lng as string) || "ko"
+
+ // Get the projectId from route parameters
+ const projectIdFromUrl = React.useMemo(() => {
+ if (params?.projectId) {
+ const projectId = Array.isArray(params.projectId)
+ ? params.projectId[0]
+ : params.projectId
+ return Number(projectId)
+ }
+ return null
+ }, [params])
+
+ // Use the URL projectId as the selected project
+ const [selectedProjectId, setSelectedProjectId] = React.useState<number | null>(
+ projectIdFromUrl
+ )
+
+ // 프로젝트 목록 상태
+ const [projects, setProjects] = React.useState<Array<{ id: number; code: string; name: string; type: string }>>([])
+ const [isLoading, setIsLoading] = React.useState(true)
+
+ // Update selectedProjectId when URL changes
+ React.useEffect(() => {
+ if (projectIdFromUrl) {
+ setSelectedProjectId(projectIdFromUrl)
+ }
+ }, [projectIdFromUrl])
+
+ // 프로젝트 목록 로드
+ React.useEffect(() => {
+ const loadProjects = async () => {
+ try {
+ setIsLoading(true)
+ console.log("Loading projects...")
+ const result = await getProjectLists({
+ page: 1,
+ perPage: 1000,
+ search: "",
+ sort: [],
+ filters: [],
+ joinOperator: "and",
+ flags: [],
+ code: "",
+ name: "",
+ type: ""
+ })
+ console.log("Projects result:", result)
+ if (result.data) {
+ // plant 타입의 프로젝트만 필터링
+ const plantProjects = result.data.filter(project => project.type === 'plant')
+ console.log("Plant projects:", plantProjects)
+ setProjects(plantProjects)
+ }
+ } catch (error) {
+ console.error("Failed to load projects:", error)
+ } finally {
+ setIsLoading(false)
+ }
+ }
+ loadProjects()
+ }, [])
+
+ // Handle project selection
+ function handleSelectProject(projectId: number) {
+ console.log("Selecting project:", projectId)
+ setSelectedProjectId(projectId)
+
+ // Navigate to the project's document-class page
+ router.push(`/${lng}/evcp/docu-list-rule/${projectId}`)
+ }
+
+ return (
+ <>
+ {/* 상단 영역: 제목 왼쪽 / ProjectSwitcher 오른쪽 */}
+ <div className="flex items-center justify-between">
+ {/* 왼쪽: 타이틀 & 설명 */}
+ <div>
+ <div className="flex items-center gap-2">
+ <h2 className="text-2xl font-bold tracking-tight">Document Numbering Rule (해양)</h2>
+ </div>
+ <p className="text-muted-foreground">
+ 벤더 제출 문서 리스트 작성 시에 사용되는 넘버링
+ </p>
+ </div>
+
+ {/* 오른쪽: ProjectSwitcher */}
+ <div className="flex items-center space-x-2">
+ <Select
+ value={selectedProjectId ? String(selectedProjectId) : ""}
+ onValueChange={(value) => {
+ const projectId = Number(value)
+ if (projectId) {
+ handleSelectProject(projectId)
+ }
+ }}
+ disabled={isLoading}
+ >
+ <SelectTrigger className="max-w-[300px] whitespace-nowrap overflow-hidden text-ellipsis">
+ <SelectValue placeholder={isLoading ? "프로젝트 로딩 중..." : "프로젝트를 선택하세요"} />
+ </SelectTrigger>
+ <SelectContent>
+ {projects.map((project) => (
+ <SelectItem key={project.id} value={String(project.id)}>
+ {project.code} - {project.name}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+ </div>
+
+ {/* 문서 목록/테이블 영역 */}
+ <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow p-5">
+ {children}
+ </section>
+ </>
+ )
+}
diff --git a/components/docu-list-rule/dynamic-title-client.tsx b/components/docu-list-rule/dynamic-title-client.tsx
new file mode 100644
index 00000000..46517cbc
--- /dev/null
+++ b/components/docu-list-rule/dynamic-title-client.tsx
@@ -0,0 +1,52 @@
+"use client"
+
+import { usePathname } from "next/navigation"
+import { useEffect, useState } from "react"
+
+export function DynamicTitleClient() {
+ const pathname = usePathname()
+ const [currentTitle, setCurrentTitle] = useState<string>("")
+ const [isLoading, setIsLoading] = useState(true)
+
+ useEffect(() => {
+ // 로딩 상태를 true로 설정
+ setIsLoading(true)
+
+ // 약간의 지연 후 제목 설정 (로딩 효과 방지)
+ const timer = setTimeout(() => {
+ let title = ""
+
+ if (pathname?.includes("/document-class")) {
+ title = "Document Class 관리"
+ } else if (pathname?.includes("/code-groups")) {
+ title = "Code Group 정의"
+ } else if (pathname?.includes("/combo-box-settings")) {
+ title = "Combo Box 설정"
+ } else if (pathname?.includes("/number-types")) {
+ title = "Number Type 관리"
+ } else if (pathname?.includes("/number-type-configs")) {
+ title = "Number Type별 설정"
+ } else {
+ title = "Document Numbering Rule (해양)"
+ }
+
+ setCurrentTitle(title)
+ setIsLoading(false)
+ }, 100) // 100ms 지연
+
+ return () => clearTimeout(timer)
+ }, [pathname])
+
+ // 로딩 중에는 아무것도 표시하지 않음
+ if (isLoading) {
+ return <div className="space-y-0.5">
+ <h2 className="text-2xl font-bold tracking-tight">&nbsp;</h2>
+ </div>
+ }
+
+ return (
+ <div className="space-y-0.5">
+ <h2 className="text-2xl font-bold tracking-tight">{currentTitle}</h2>
+ </div>
+ )
+}
diff --git a/db/schema/docu-list-rule.ts b/db/schema/docu-list-rule.ts
index 27cace80..612c8045 100644
--- a/db/schema/docu-list-rule.ts
+++ b/db/schema/docu-list-rule.ts
@@ -56,6 +56,7 @@ export const documentClassOptions = pgTable("document_class_options_new", {
documentClassId: integer("document_class_id").notNull().references(() => documentClasses.id),
description: varchar("description", { length: 100 }).notNull(), // 하위 옵션 설명 (예: "General", "Technical")
optionCode: varchar("option_code", { length: 50 }), // 하위 옵션 코드 (선택사항)
+ sdq: integer("sdq").notNull(), // 순서 번호 (1, 2, 3, 4, 5, 6)
isActive: boolean("is_active").default(true),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
@@ -66,6 +67,11 @@ export const documentClassOptions = pgTable("document_class_options_new", {
table.documentClassId,
table.optionCode
),
+ // 같은 documentClassId 내에서 sdq는 유니크해야 함
+ uniqueDocumentClassSdq: unique("unique_document_class_sdq").on(
+ table.documentClassId,
+ table.sdq
+ ),
}
})
@@ -76,6 +82,7 @@ export const comboBoxSettings = pgTable("combo_box_settings", {
code: varchar("code", { length: 50 }).notNull(), // CODE (예: 100, 201, 202)
description: varchar("description", { length: 200 }).notNull(), // Description (예: General, Feed Gas Reveive)
remark: text("remark"), // Remark (비고)
+ sdq: integer("sdq").notNull(), // 순서 번호 (1, 2, 3, 4, 5, 6)
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
}, (table) => {
@@ -85,6 +92,11 @@ export const comboBoxSettings = pgTable("combo_box_settings", {
table.codeGroupId,
table.code
),
+ // 같은 codeGroupId 내에서 sdq는 유니크해야 함
+ uniqueCodeGroupSdq: unique("unique_code_group_sdq").on(
+ table.codeGroupId,
+ table.sdq
+ ),
}
})
diff --git a/lib/docu-list-rule/code-groups/service.ts b/lib/docu-list-rule/code-groups/service.ts
index c854f6c9..d9b3b859 100644
--- a/lib/docu-list-rule/code-groups/service.ts
+++ b/lib/docu-list-rule/code-groups/service.ts
@@ -15,6 +15,7 @@ export async function getCodeGroups(input: {
filters: Array<{ id: string; value: string | string[] }>
joinOperator: "and" | "or"
sort: Array<{ id: string; desc: boolean }>
+ projectId?: string
}) {
unstable_noStore()
@@ -25,10 +26,15 @@ export async function getCodeGroups(input: {
// 검색 조건 (plant 타입 프로젝트만)
let whereConditions = sql`${projects.type} = 'plant'`
+ // 프로젝트 ID 필터링
+ if (input.projectId) {
+ whereConditions = sql`${whereConditions} AND ${codeGroups.projectId} = ${parseInt(input.projectId)}`
+ }
+
// 검색어 필터링
if (search) {
const searchTerm = `%${search}%`
- whereConditions = sql`${projects.type} = 'plant' AND (
+ whereConditions = sql`${whereConditions} AND (
${codeGroups.groupId} ILIKE ${searchTerm} OR
${codeGroups.description} ILIKE ${searchTerm} OR
${codeGroups.codeFormat} ILIKE ${searchTerm} OR
diff --git a/lib/docu-list-rule/code-groups/table/code-groups-add-dialog.tsx b/lib/docu-list-rule/code-groups/table/code-groups-add-dialog.tsx
index 33dfdd03..f5354161 100644
--- a/lib/docu-list-rule/code-groups/table/code-groups-add-dialog.tsx
+++ b/lib/docu-list-rule/code-groups/table/code-groups-add-dialog.tsx
@@ -34,11 +34,10 @@ import {
SelectValue,
} from "@/components/ui/select"
import { createCodeGroup } from "@/lib/docu-list-rule/code-groups/service"
-import { getProjectLists } from "@/lib/projects/service"
+import { useParams } from "next/navigation"
import { z } from "zod"
const createCodeGroupSchema = z.object({
- projectId: z.string().min(1, "프로젝트는 필수입니다."),
description: z.string().min(1, "Description은 필수입니다."),
codeFormat: z.string().optional().refine((val) => {
if (!val) return true; // 빈 값은 허용
@@ -54,51 +53,21 @@ interface CodeGroupsAddDialogProps {
}
export function CodeGroupsAddDialog({ onSuccess }: CodeGroupsAddDialogProps) {
+ const params = useParams()
+ const projectId = Number(params?.projectId)
const [open, setOpen] = React.useState(false)
const [isLoading, setIsLoading] = React.useState(false)
- const [projects, setProjects] = React.useState<Array<{ id: number; code: string; name: string; type: string }>>([])
const form = useForm<CreateCodeGroupFormValues>({
resolver: zodResolver(createCodeGroupSchema),
defaultValues: {
- projectId: "",
description: "",
codeFormat: "",
controlType: "",
},
})
- // 프로젝트 목록 로드
- React.useEffect(() => {
- if (open) {
- const loadProjects = async () => {
- try {
- const result = await getProjectLists({
- page: 1,
- perPage: 1000,
- search: "",
- sort: [],
- filters: [],
- joinOperator: "and",
- flags: [],
- code: "",
- name: "",
- type: ""
- })
- if (result.data) {
- // plant 타입의 프로젝트만 필터링
- const plantProjects = result.data.filter(project => project.type === 'plant')
- setProjects(plantProjects)
- }
- } catch (error) {
- console.error("Failed to load projects:", error)
- toast.error("프로젝트 목록을 불러오는데 실패했습니다.")
-
- }
- }
- loadProjects()
- }
- }, [open])
+
// Code Format을 기반으로 정규식 자동 생성 함수
const generateExpression = (codeFormat: string): string => {
@@ -157,7 +126,7 @@ export function CodeGroupsAddDialog({ onSuccess }: CodeGroupsAddDialogProps) {
const expressions = generateExpression(data.codeFormat || "")
const result = await createCodeGroup({
- projectId: parseInt(data.projectId),
+ projectId: projectId,
description: data.description,
codeFormat: data.codeFormat,
expressions: expressions,
@@ -201,31 +170,6 @@ export function CodeGroupsAddDialog({ onSuccess }: CodeGroupsAddDialogProps) {
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
- name="projectId"
- render={({ field }) => (
- <FormItem>
- <FormLabel>프로젝트 *</FormLabel>
- <Select onValueChange={field.onChange} defaultValue={field.value}>
- <FormControl>
- <SelectTrigger>
- <SelectValue placeholder="프로젝트를 선택하세요" />
- </SelectTrigger>
- </FormControl>
- <SelectContent>
- {projects.map((project) => (
- <SelectItem key={project.id} value={project.id.toString()}>
- {project.code} - {project.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- <FormMessage />
- </FormItem>
- )}
- />
-
- <FormField
- control={form.control}
name="description"
render={({ field }) => (
<FormItem>
diff --git a/lib/docu-list-rule/code-groups/table/code-groups-table-columns.tsx b/lib/docu-list-rule/code-groups/table/code-groups-table-columns.tsx
index 01047c50..c15dd676 100644
--- a/lib/docu-list-rule/code-groups/table/code-groups-table-columns.tsx
+++ b/lib/docu-list-rule/code-groups/table/code-groups-table-columns.tsx
@@ -103,19 +103,6 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<typeof
// ----------------------------------------------------------------
const dataColumns: ColumnDef<typeof codeGroups.$inferSelect>[] = [
{
- accessorKey: "projectCode",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="프로젝트 코드" />
- ),
- meta: {
- excelHeader: "프로젝트 코드",
- type: "text",
- },
- cell: ({ row }) => row.getValue("projectCode") ?? "",
- minSize: 120
- },
- {
accessorKey: "groupId",
enableResizing: true,
header: ({ column }) => (
diff --git a/lib/docu-list-rule/code-groups/table/code-groups-table.tsx b/lib/docu-list-rule/code-groups/table/code-groups-table.tsx
index 0029ed91..fdddb2d6 100644
--- a/lib/docu-list-rule/code-groups/table/code-groups-table.tsx
+++ b/lib/docu-list-rule/code-groups/table/code-groups-table.tsx
@@ -75,12 +75,7 @@ export function CodeGroupsTable({ promises }: CodeGroupsTableProps) {
clearOnDefault: false,
})
- // 컴포넌트 마운트 후 그룹핑 설정
- React.useEffect(() => {
- if (data && table.getState().grouping.length === 0) {
- table.setGrouping(["projectCode"])
- }
- }, [table, data])
+
// 정렬 시 펼쳐진 상태 유지
React.useEffect(() => {
diff --git a/lib/docu-list-rule/code-groups/validation.ts b/lib/docu-list-rule/code-groups/validation.ts
index 9745841c..46ba02e8 100644
--- a/lib/docu-list-rule/code-groups/validation.ts
+++ b/lib/docu-list-rule/code-groups/validation.ts
@@ -24,6 +24,7 @@ export const searchParamsCodeGroupsCache = createSearchParamsCache({
description: parseAsString.withDefault(""),
controlType: parseAsString.withDefault(""),
isActive: parseAsString.withDefault(""),
+ projectId: parseAsString.withDefault(""),
// advanced filter
filters: getFiltersStateParser().withDefault([]),
diff --git a/lib/docu-list-rule/combo-box-settings/service.ts b/lib/docu-list-rule/combo-box-settings/service.ts
index c733f978..d2f7d0f7 100644
--- a/lib/docu-list-rule/combo-box-settings/service.ts
+++ b/lib/docu-list-rule/combo-box-settings/service.ts
@@ -19,6 +19,7 @@ export async function getComboBoxCodeGroups(input: {
groupId?: string
description?: string
isActive?: string
+ projectId?: string
}) {
unstable_noStore()
@@ -29,10 +30,15 @@ export async function getComboBoxCodeGroups(input: {
// Control Type이 combobox이고 plant 타입 프로젝트인 조건
let whereConditions = sql`${codeGroups.controlType} = 'combobox' AND ${projects.type} = 'plant'`
+ // 프로젝트 ID 필터링
+ if (input.projectId) {
+ whereConditions = sql`${whereConditions} AND ${codeGroups.projectId} = ${parseInt(input.projectId)}`
+ }
+
// 검색 조건
if (search) {
const searchTerm = `%${search}%`
- whereConditions = sql`${codeGroups.controlType} = 'combobox' AND ${projects.type} = 'plant' AND (
+ whereConditions = sql`${whereConditions} AND (
${codeGroups.groupId} ILIKE ${searchTerm} OR
${codeGroups.description} ILIKE ${searchTerm} OR
${codeGroups.codeFormat} ILIKE ${searchTerm} OR
@@ -168,7 +174,7 @@ export async function getComboBoxOptions(codeGroupId: number, input?: {
}
// 정렬 (안전한 필드 체크 적용)
- let orderBy = sql`${comboBoxSettings.code} ASC`
+ let orderBy = sql`${comboBoxSettings.sdq} ASC`
if (sort && sort.length > 0) {
const sortField = sort[0]
// 안전성 체크: 필드가 실제 테이블에 존재하는지 확인
@@ -193,6 +199,7 @@ export async function getComboBoxOptions(codeGroupId: number, input?: {
code: comboBoxSettings.code,
description: comboBoxSettings.description,
remark: comboBoxSettings.remark,
+ sdq: comboBoxSettings.sdq,
createdAt: comboBoxSettings.createdAt,
updatedAt: comboBoxSettings.updatedAt,
projectCode: projects.code,
@@ -274,6 +281,14 @@ export async function createComboBoxOption(input: {
}
}
+ // 다음 순서 번호 계산
+ const maxSdqResult = await db
+ .select({ maxSdq: sql<number>`COALESCE(MAX(sdq), 0)` })
+ .from(comboBoxSettings)
+ .where(eq(comboBoxSettings.codeGroupId, input.codeGroupId))
+
+ const nextSdq = (maxSdqResult[0]?.maxSdq || 0) + 1
+
const [newOption] = await db
.insert(comboBoxSettings)
.values({
@@ -281,6 +296,7 @@ export async function createComboBoxOption(input: {
code: input.code,
description: input.description || "-",
remark: input.remark,
+ sdq: nextSdq,
})
.returning({ id: comboBoxSettings.id })
@@ -308,6 +324,7 @@ export async function updateComboBoxOption(input: {
code: string
description: string
remark?: string
+ sdq?: number
}) {
try {
// 현재 수정 중인 항목의 codeGroupId 가져오기
@@ -346,6 +363,7 @@ export async function updateComboBoxOption(input: {
code: input.code,
description: input.description,
remark: input.remark,
+ ...(input.sdq !== undefined && { sdq: input.sdq }),
updatedAt: new Date(),
})
.where(eq(comboBoxSettings.id, input.id))
@@ -421,4 +439,99 @@ export async function clearComboBoxOptions(codeGroupId: number) {
}
}
- \ No newline at end of file
+// Combo Box 옵션 순서 업데이트 (드래그 앤 드롭)
+export async function updateComboBoxOptionOrder(codeGroupId: number, reorderedOptions: { id: number; sdq: number }[]) {
+ try {
+ console.log("Updating combo box option order:", { codeGroupId, reorderedOptions })
+
+ // 유니크 제약조건 때문에 임시로 큰 값으로 업데이트 후 실제 값으로 업데이트
+ await db.transaction(async (tx) => {
+ // 1단계: 모든 옵션을 임시 큰 값으로 업데이트
+ for (const option of reorderedOptions) {
+ console.log("Step 1 - Setting temporary value for option:", option.id)
+ await tx
+ .update(comboBoxSettings)
+ .set({
+ sdq: option.sdq + 1000, // 임시로 큰 값 설정
+ updatedAt: new Date(),
+ })
+ .where(eq(comboBoxSettings.id, option.id))
+ }
+
+ // 2단계: 실제 값으로 업데이트
+ for (const option of reorderedOptions) {
+ console.log("Step 2 - Setting final value for option:", option.id, "sdq:", option.sdq)
+ const result = await tx
+ .update(comboBoxSettings)
+ .set({
+ sdq: option.sdq,
+ updatedAt: new Date(),
+ })
+ .where(eq(comboBoxSettings.id, option.id))
+ .returning({ id: comboBoxSettings.id, sdq: comboBoxSettings.sdq })
+
+ console.log("Update result:", result)
+ }
+ })
+
+ revalidatePath("/evcp/docu-list-rule/combo-box-settings")
+
+ return {
+ success: true,
+ message: "Combo Box options reordered successfully"
+ }
+ } catch (error) {
+ console.error("Error updating combo box option order:", error)
+ return {
+ success: false,
+ error: "Failed to update combo box option order"
+ }
+ }
+}
+
+// 기존 데이터에 기본 순서값 설정 (마이그레이션 후 한 번만 실행)
+export async function initializeComboBoxOptionOrder() {
+ try {
+ // sdq가 null인 모든 옵션들을 찾아서 순서대로 업데이트
+ const codeGroupsWithOptions = await db
+ .select({
+ codeGroupId: comboBoxSettings.codeGroupId,
+ id: comboBoxSettings.id,
+ })
+ .from(comboBoxSettings)
+ .orderBy(comboBoxSettings.codeGroupId, comboBoxSettings.createdAt)
+
+ // codeGroupId별로 그룹화하여 순서 설정
+ const groupedOptions = codeGroupsWithOptions.reduce((acc, option) => {
+ if (!acc[option.codeGroupId]) {
+ acc[option.codeGroupId] = []
+ }
+ acc[option.codeGroupId].push(option.id)
+ return acc
+ }, {} as Record<number, number[]>)
+
+ // 각 그룹별로 순서 업데이트
+ for (const [codeGroupId, optionIds] of Object.entries(groupedOptions)) {
+ for (let i = 0; i < optionIds.length; i++) {
+ await db
+ .update(comboBoxSettings)
+ .set({
+ sdq: i + 1,
+ updatedAt: new Date(),
+ })
+ .where(eq(comboBoxSettings.id, optionIds[i]))
+ }
+ }
+
+ return {
+ success: true,
+ message: "Combo Box option order initialized successfully"
+ }
+ } catch (error) {
+ console.error("Error initializing combo box option order:", error)
+ return {
+ success: false,
+ error: "Failed to initialize combo box option order"
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-detail-sheet.tsx b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-detail-sheet.tsx
index 286acfbf..7e81fdab 100644
--- a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-detail-sheet.tsx
+++ b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-detail-sheet.tsx
@@ -4,12 +4,16 @@ import * as React from "react"
import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel } from "@tanstack/react-table"
import { DataTableDetail } from "@/components/data-table/data-table-detail"
import { DataTableAdvancedToolbarDetail } from "@/components/data-table/data-table-advanced-toolbar-detail"
+import { DragDropTable } from "@/lib/docu-list-rule/number-type-configs/table/drag-drop-table"
import type { DataTableAdvancedFilterField, DataTableRowAction } from "@/types/table"
+import { DragEndEvent } from '@dnd-kit/core'
+import { arrayMove } from '@dnd-kit/sortable'
+import { toast } from "sonner"
import {
Sheet,
SheetContent,
} from "@/components/ui/sheet"
-import { getComboBoxOptions } from "@/lib/docu-list-rule/combo-box-settings/service"
+import { getComboBoxOptions, updateComboBoxOption } from "@/lib/docu-list-rule/combo-box-settings/service"
import { getColumns } from "@/lib/docu-list-rule/combo-box-settings/table/combo-box-options-table-columns"
import { ComboBoxOptionsEditSheet } from "@/lib/docu-list-rule/combo-box-settings/table/combo-box-options-edit-sheet"
import { DeleteComboBoxOptionsDialog } from "@/lib/docu-list-rule/combo-box-settings/table/delete-combo-box-options-dialog"
@@ -21,6 +25,7 @@ type ComboBoxOption = {
code: string
description: string
remark: string | null
+ sdq: number
isActive: boolean
createdAt: Date
updatedAt: Date
@@ -57,7 +62,7 @@ export function ComboBoxOptionsDetailSheet({
page: 1,
perPage: 10,
search: "",
- sort: [{ id: "createdAt", desc: true }],
+ sort: [{ id: "sdq", desc: false }],
filters: [],
joinOperator: "and",
})
@@ -90,6 +95,7 @@ export function ComboBoxOptionsDetailSheet({
const result = await getComboBoxOptions(codeGroup.id, {
page: 1,
perPage: 10,
+ sort: [{ id: "sdq", desc: false }],
})
if (result.success && result.data) {
const optionsWithIsActive = result.data.map(option => ({
@@ -123,7 +129,7 @@ export function ComboBoxOptionsDetailSheet({
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
initialState: {
- sorting: [{ id: "code", desc: false }],
+ sorting: [{ id: "sdq", desc: false }],
pagination: {
pageSize: 10,
},
@@ -131,6 +137,66 @@ export function ComboBoxOptionsDetailSheet({
getRowId: (originalRow) => String((originalRow as any).id),
})
+ // 드래그 종료 핸들러
+ const handleDragEnd = React.useCallback(async (event: DragEndEvent) => {
+ const { active, over } = event
+ console.log("Drag end event:", { active, over })
+
+ if (active.id !== over?.id) {
+ const oldIndex = rawData.data.findIndex((item) => String(item.id) === active.id)
+ const newIndex = rawData.data.findIndex((item) => String(item.id) === over?.id)
+ console.log("Indices:", { oldIndex, newIndex })
+
+ if (oldIndex !== -1 && newIndex !== -1) {
+ const reorderedData = arrayMove(rawData.data, oldIndex, newIndex)
+
+ // 새로운 순서로 sdq 값 업데이트
+ const updatedOptions = reorderedData.map((item, index) => ({
+ ...item,
+ sdq: index + 1
+ }))
+
+ // 로컬 상태 먼저 업데이트
+ setRawData(prev => ({ ...prev, data: updatedOptions }))
+
+ // 서버에 순서 업데이트 (Number Type Config와 같은 방식)
+ try {
+ // 모든 항목을 임시 값으로 먼저 업데이트
+ for (let i = 0; i < updatedOptions.length; i++) {
+ const option = updatedOptions[i]
+ await updateComboBoxOption({
+ id: option.id,
+ codeGroupId: option.codeGroupId,
+ code: option.code,
+ description: option.description,
+ remark: option.remark,
+ sdq: -(i + 1), // 임시 음수 값
+ })
+ }
+
+ // 최종 순서로 업데이트
+ for (const option of updatedOptions) {
+ await updateComboBoxOption({
+ id: option.id,
+ codeGroupId: option.codeGroupId,
+ code: option.code,
+ description: option.description,
+ remark: option.remark,
+ sdq: option.sdq,
+ })
+ }
+
+ toast.success("순서가 성공적으로 변경되었습니다.")
+ } catch (error) {
+ console.error("Error updating order:", error)
+ toast.error("순서 변경 중 오류가 발생했습니다.")
+ // 에러 시 원래 데이터로 복원
+ await refreshData()
+ }
+ }
+ }
+ }, [rawData.data, codeGroup, refreshData])
+
if (!codeGroup) return null
return (
@@ -151,12 +217,16 @@ export function ComboBoxOptionsDetailSheet({
onSuccess={refreshData}
/>
- <DataTableDetail table={table as any}>
+ <DragDropTable
+ table={table as any}
+ data={rawData.data}
+ onDragEnd={handleDragEnd}
+ >
<DataTableAdvancedToolbarDetail
table={table as any}
filterFields={advancedFilterFields}
/>
- </DataTableDetail>
+ </DragDropTable>
<DeleteComboBoxOptionsDialog
open={rowAction?.type === "delete"}
diff --git a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-table-columns.tsx b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-table-columns.tsx
index 17754331..cf770c33 100644
--- a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-table-columns.tsx
+++ b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-table-columns.tsx
@@ -25,6 +25,7 @@ interface ComboBoxOption {
code: string
description: string
remark: string | null
+ sdq: number
isActive?: boolean
createdAt: Date
updatedAt: Date
@@ -114,17 +115,17 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<ComboBo
// ----------------------------------------------------------------
const dataColumns: ColumnDef<ComboBoxOption>[] = [
{
- accessorKey: "projectCode",
+ accessorKey: "sdq",
enableResizing: true,
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="프로젝트 코드" />
+ <DataTableColumnHeaderSimple column={column} title="순서" />
),
meta: {
- excelHeader: "프로젝트 코드",
- type: "text",
+ excelHeader: "순서",
+ type: "number",
},
- cell: ({ row }) => row.getValue("projectCode") ?? "",
- minSize: 120
+ cell: ({ row }) => row.getValue("sdq") ?? "",
+ minSize: 50
},
{
accessorKey: "code",
diff --git a/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table-columns.tsx b/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table-columns.tsx
index d41fe5ec..0775c1d2 100644
--- a/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table-columns.tsx
+++ b/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table-columns.tsx
@@ -91,19 +91,7 @@ export function getColumns({ onDetail }: GetColumnsProps): ColumnDef<typeof code
// 3) 데이터 컬럼들
// ----------------------------------------------------------------
const dataColumns: ColumnDef<typeof codeGroups.$inferSelect>[] = [
- {
- accessorKey: "projectCode",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="프로젝트 코드" />
- ),
- meta: {
- excelHeader: "프로젝트 코드",
- type: "text",
- },
- cell: ({ row }) => row.getValue("projectCode") ?? "",
- minSize: 120
- },
+
{
accessorKey: "groupId",
enableResizing: true,
diff --git a/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table.tsx b/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table.tsx
index 8e469149..fef4cf8e 100644
--- a/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table.tsx
+++ b/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table.tsx
@@ -66,12 +66,7 @@ export function ComboBoxSettingsTable({ promises }: ComboBoxSettingsTableProps)
clearOnDefault: true,
})
- // 컴포넌트 마운트 후 그룹핑 설정
- React.useEffect(() => {
- if (rawData[0]?.data && table.getState().grouping.length === 0) {
- table.setGrouping(["projectCode"])
- }
- }, [table, rawData])
+
// 정렬 시 펼쳐진 상태 유지
React.useEffect(() => {
diff --git a/lib/docu-list-rule/combo-box-settings/validation.ts b/lib/docu-list-rule/combo-box-settings/validation.ts
index ca8e9192..51671fb2 100644
--- a/lib/docu-list-rule/combo-box-settings/validation.ts
+++ b/lib/docu-list-rule/combo-box-settings/validation.ts
@@ -24,6 +24,7 @@ export const searchParamsComboBoxSettingsCache = createSearchParamsCache({
description: parseAsString.withDefault(""),
controlType: parseAsString.withDefault(""),
isActive: parseAsString.withDefault(""),
+ projectId: parseAsString.withDefault(""),
// advanced filter
filters: getFiltersStateParser().withDefault([]),
diff --git a/lib/docu-list-rule/document-class/service.ts b/lib/docu-list-rule/document-class/service.ts
index 2ec31ae6..378c3215 100644
--- a/lib/docu-list-rule/document-class/service.ts
+++ b/lib/docu-list-rule/document-class/service.ts
@@ -18,6 +18,7 @@ export async function getDocumentClassCodeGroups(input: {
classId?: string
description?: string
isActive?: string
+ projectId?: string
}) {
try {
const { page, perPage, sort, search, filters, joinOperator } = input
@@ -26,10 +27,15 @@ export async function getDocumentClassCodeGroups(input: {
// 기본 조건 (plant 타입 프로젝트만)
let whereConditions = sql`${documentClasses.isActive} = true AND ${projects.type} = 'plant'`
+ // 프로젝트 ID 필터링
+ if (input.projectId) {
+ whereConditions = sql`${whereConditions} AND ${documentClasses.projectId} = ${parseInt(input.projectId)}`
+ }
+
// 검색 조건
if (search) {
const searchTerm = `%${search}%`
- whereConditions = sql`${documentClasses.isActive} = true AND ${projects.type} = 'plant' AND (
+ whereConditions = sql`${whereConditions} AND (
${documentClasses.code} ILIKE ${searchTerm} OR
${documentClasses.value} ILIKE ${searchTerm} OR
${documentClasses.description} ILIKE ${searchTerm} OR
@@ -363,7 +369,7 @@ export async function getDocumentClassSubOptions(documentClassId: number, input?
}
// 정렬 (안전한 필드 체크 적용)
- let orderBy = sql`${documentClassOptions.optionCode} ASC`
+ let orderBy = sql`${documentClassOptions.sdq} ASC`
if (sort && sort.length > 0) {
const sortField = sort[0]
// 안전성 체크: 필드가 실제 테이블에 존재하는지 확인
@@ -380,6 +386,7 @@ export async function getDocumentClassSubOptions(documentClassId: number, input?
documentClassId: documentClassOptions.documentClassId,
description: documentClassOptions.description,
optionCode: documentClassOptions.optionCode,
+ sdq: documentClassOptions.sdq,
isActive: documentClassOptions.isActive,
createdAt: documentClassOptions.createdAt,
updatedAt: documentClassOptions.updatedAt,
@@ -448,12 +455,21 @@ export async function createDocumentClassOptionItem(input: {
}
}
+ // 해당 Document Class의 최대 sdq 값 찾기
+ const maxSdqResult = await db
+ .select({ maxSdq: sql<number>`COALESCE(MAX(${documentClassOptions.sdq}), 0)` })
+ .from(documentClassOptions)
+ .where(eq(documentClassOptions.documentClassId, input.documentClassId))
+
+ const nextSdq = (maxSdqResult[0]?.maxSdq || 0) + 1
+
const [newOption] = await db
.insert(documentClassOptions)
.values({
documentClassId: input.documentClassId,
description: userOptionCode, // 코드값을 description에도 자동 설정
optionCode: userOptionCode,
+ sdq: nextSdq,
isActive: true,
})
.returning({ id: documentClassOptions.id })
@@ -477,11 +493,10 @@ export async function createDocumentClassOptionItem(input: {
// Document Class 옵션 수정
export async function updateDocumentClassOption(input: {
id: number
- optionCode: string
+ optionCode?: string
+ sdq?: number
}) {
try {
- const userOptionCode = input.optionCode.toUpperCase().trim()
-
// 기존 옵션 조회하여 documentClassId 가져오기
const currentOption = await db
.select({ documentClassId: documentClassOptions.documentClassId })
@@ -496,32 +511,47 @@ export async function updateDocumentClassOption(input: {
}
}
- // 같은 Document Class 내에서 코드 중복 체크 (자신 제외)
- const existingOption = await db
- .select({ id: documentClassOptions.id })
- .from(documentClassOptions)
- .where(
- and(
- eq(documentClassOptions.documentClassId, currentOption[0].documentClassId),
- eq(documentClassOptions.optionCode, userOptionCode)
+ // optionCode가 제공된 경우에만 중복 체크 및 업데이트
+ if (input.optionCode) {
+ const userOptionCode = input.optionCode.toUpperCase().trim()
+
+ // 같은 Document Class 내에서 코드 중복 체크 (자신 제외)
+ const existingOption = await db
+ .select({ id: documentClassOptions.id })
+ .from(documentClassOptions)
+ .where(
+ and(
+ eq(documentClassOptions.documentClassId, currentOption[0].documentClassId),
+ eq(documentClassOptions.optionCode, userOptionCode)
+ )
)
- )
- .limit(1)
+ .limit(1)
- if (existingOption.length > 0 && existingOption[0].id !== input.id) {
- return {
- success: false,
- error: "이미 존재하는 코드입니다."
+ if (existingOption.length > 0 && existingOption[0].id !== input.id) {
+ return {
+ success: false,
+ error: "이미 존재하는 코드입니다."
+ }
}
}
+ // 업데이트할 데이터 준비
+ const updateData: any = {
+ updatedAt: new Date(),
+ }
+
+ if (input.optionCode) {
+ updateData.description = input.optionCode.toUpperCase().trim()
+ updateData.optionCode = input.optionCode.toUpperCase().trim()
+ }
+
+ if (input.sdq !== undefined) {
+ updateData.sdq = input.sdq
+ }
+
const [updatedOption] = await db
.update(documentClassOptions)
- .set({
- description: userOptionCode, // 코드값을 description에도 자동 설정
- optionCode: userOptionCode,
- updatedAt: new Date(),
- })
+ .set(updateData)
.where(eq(documentClassOptions.id, input.id))
.returning({ id: documentClassOptions.id })
diff --git a/lib/docu-list-rule/document-class/table/document-class-add-dialog.tsx b/lib/docu-list-rule/document-class/table/document-class-add-dialog.tsx
index a51b0598..e2cfc39e 100644
--- a/lib/docu-list-rule/document-class/table/document-class-add-dialog.tsx
+++ b/lib/docu-list-rule/document-class/table/document-class-add-dialog.tsx
@@ -26,19 +26,12 @@ import {
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select"
+
import { createDocumentClassCodeGroup } from "@/lib/docu-list-rule/document-class/service"
-import { getProjectLists } from "@/lib/projects/service"
+import { useParams } from "next/navigation"
const createDocumentClassSchema = z.object({
- projectId: z.string().min(1, "프로젝트는 필수입니다."),
value: z.string().min(1, "Value는 필수입니다."),
description: z.string().optional(),
})
@@ -52,56 +45,27 @@ interface DocumentClassAddDialogProps {
export function DocumentClassAddDialog({
onSuccess,
}: DocumentClassAddDialogProps) {
+ const params = useParams()
+ const projectId = Number(params?.projectId)
const [open, setOpen] = React.useState(false)
const [isPending, startTransition] = React.useTransition()
- const [projects, setProjects] = React.useState<Array<{ id: number; code: string; name: string; type: string }>>([])
const form = useForm<CreateDocumentClassSchema>({
resolver: zodResolver(createDocumentClassSchema),
defaultValues: {
- projectId: "",
value: "",
description: "",
},
mode: "onChange"
})
- // 프로젝트 목록 로드
- React.useEffect(() => {
- if (open) {
- const loadProjects = async () => {
- try {
- const result = await getProjectLists({
- page: 1,
- perPage: 1000,
- search: "",
- sort: [],
- filters: [],
- joinOperator: "and",
- flags: [],
- code: "",
- name: "",
- type: ""
- })
- if (result.data) {
- // plant 타입의 프로젝트만 필터링
- const plantProjects = result.data.filter(project => project.type === 'plant')
- setProjects(plantProjects)
- }
- } catch (error) {
- console.error("Failed to load projects:", error)
- toast.error("프로젝트 목록을 불러오는데 실패했습니다.")
- }
- }
- loadProjects()
- }
- }, [open])
+
async function onSubmit(input: CreateDocumentClassSchema) {
startTransition(async () => {
try {
const result = await createDocumentClassCodeGroup({
- projectId: parseInt(input.projectId),
+ projectId: projectId,
value: input.value,
description: input.description,
})
@@ -144,30 +108,7 @@ export function DocumentClassAddDialog({
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
- <FormField
- control={form.control}
- name="projectId"
- render={({ field }) => (
- <FormItem>
- <FormLabel>프로젝트 *</FormLabel>
- <Select onValueChange={field.onChange} defaultValue={field.value}>
- <FormControl>
- <SelectTrigger>
- <SelectValue placeholder="프로젝트를 선택하세요" />
- </SelectTrigger>
- </FormControl>
- <SelectContent>
- {projects.map((project) => (
- <SelectItem key={project.id} value={project.id.toString()}>
- {project.code} - {project.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- <FormMessage />
- </FormItem>
- )}
- />
+
<FormField
control={form.control}
diff --git a/lib/docu-list-rule/document-class/table/document-class-options-detail-sheet.tsx b/lib/docu-list-rule/document-class/table/document-class-options-detail-sheet.tsx
index 50e79d89..07384dd6 100644
--- a/lib/docu-list-rule/document-class/table/document-class-options-detail-sheet.tsx
+++ b/lib/docu-list-rule/document-class/table/document-class-options-detail-sheet.tsx
@@ -4,17 +4,21 @@ import * as React from "react"
import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel } from "@tanstack/react-table"
import { DataTableDetail } from "@/components/data-table/data-table-detail"
import { DataTableAdvancedToolbarDetail } from "@/components/data-table/data-table-advanced-toolbar-detail"
+import { DragDropTable } from "@/lib/docu-list-rule/number-type-configs/table/drag-drop-table"
import type { DataTableAdvancedFilterField, DataTableRowAction } from "@/types/table"
import {
Sheet,
SheetContent,
} from "@/components/ui/sheet"
-import { getDocumentClassSubOptions } from "@/lib/docu-list-rule/document-class/service"
+import { getDocumentClassSubOptions, updateDocumentClassOption } from "@/lib/docu-list-rule/document-class/service"
import { getColumns } from "@/lib/docu-list-rule/document-class/table/document-class-options-table-columns"
import { DocumentClassOptionEditSheet } from "@/lib/docu-list-rule/document-class/table/document-class-option-edit-sheet"
import { DeleteDocumentClassOptionDialog } from "@/lib/docu-list-rule/document-class/table/delete-document-class-option-dialog"
import { DocumentClassOptionsTableToolbarActions } from "@/lib/docu-list-rule/document-class/table/document-class-options-table-toolbar"
import { documentClasses, documentClassOptions } from "@/db/schema/docu-list-rule"
+import { DragEndEvent } from '@dnd-kit/core'
+import { arrayMove } from '@dnd-kit/sortable'
+import { toast } from "sonner"
type DocumentClassOption = typeof documentClassOptions.$inferSelect
@@ -65,6 +69,7 @@ export function DocumentClassOptionsDetailSheet({
const result = await getDocumentClassSubOptions(documentClass.id, {
page: 1,
perPage: 10,
+ sort: [{ id: "sdq", desc: false }],
})
if (result.success && result.data) {
setRawData({
@@ -94,7 +99,7 @@ export function DocumentClassOptionsDetailSheet({
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
initialState: {
- sorting: [{ id: "optionCode", desc: false }],
+ sorting: [{ id: "sdq", desc: false }],
pagination: {
pageSize: 10,
},
@@ -102,6 +107,58 @@ export function DocumentClassOptionsDetailSheet({
getRowId: (originalRow) => String(originalRow.id),
})
+ // 드래그 종료 핸들러
+ const handleDragEnd = React.useCallback(async (event: DragEndEvent) => {
+ const { active, over } = event
+ console.log("Drag end event:", { active, over })
+
+ if (active.id !== over?.id) {
+ const oldIndex = rawData.data.findIndex((item) => String(item.id) === active.id)
+ const newIndex = rawData.data.findIndex((item) => String(item.id) === over?.id)
+ console.log("Indices:", { oldIndex, newIndex })
+
+ if (oldIndex !== -1 && newIndex !== -1) {
+ const reorderedData = arrayMove(rawData.data, oldIndex, newIndex)
+
+ // 새로운 순서로 sdq 값 업데이트
+ const updatedOptions = reorderedData.map((item, index) => ({
+ ...item,
+ sdq: index + 1
+ }))
+
+ // 로컬 상태 먼저 업데이트
+ setRawData(prev => ({ ...prev, data: updatedOptions }))
+
+ // 서버에 순서 업데이트 (Combo Box Settings와 같은 방식)
+ try {
+ // 모든 항목을 임시 값으로 먼저 업데이트
+ for (let i = 0; i < updatedOptions.length; i++) {
+ const option = updatedOptions[i]
+ await updateDocumentClassOption({
+ id: option.id,
+ sdq: -(i + 1), // 임시 음수 값
+ })
+ }
+
+ // 최종 순서로 업데이트
+ for (const option of updatedOptions) {
+ await updateDocumentClassOption({
+ id: option.id,
+ sdq: option.sdq,
+ })
+ }
+
+ toast.success("순서가 성공적으로 변경되었습니다.")
+ } catch (error) {
+ console.error("Error updating order:", error)
+ toast.error("순서 변경 중 오류가 발생했습니다.")
+ // 에러 시 원래 데이터로 복원
+ await refreshData()
+ }
+ }
+ }
+ }, [rawData.data, refreshData])
+
if (!documentClass) return null
return (
@@ -122,12 +179,16 @@ export function DocumentClassOptionsDetailSheet({
onSuccess={refreshData}
/>
- <DataTableDetail table={table}>
+ <DragDropTable
+ table={table}
+ data={rawData.data}
+ onDragEnd={handleDragEnd}
+ >
<DataTableAdvancedToolbarDetail
table={table}
filterFields={advancedFilterFields}
/>
- </DataTableDetail>
+ </DragDropTable>
<DeleteDocumentClassOptionDialog
open={rowAction?.type === "delete"}
diff --git a/lib/docu-list-rule/document-class/table/document-class-options-table-columns.tsx b/lib/docu-list-rule/document-class/table/document-class-options-table-columns.tsx
index c3bf440d..c8ee4676 100644
--- a/lib/docu-list-rule/document-class/table/document-class-options-table-columns.tsx
+++ b/lib/docu-list-rule/document-class/table/document-class-options-table-columns.tsx
@@ -101,6 +101,19 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<typeof
// ----------------------------------------------------------------
const dataColumns: ColumnDef<typeof documentClassOptions.$inferSelect>[] = [
{
+ accessorKey: "sdq",
+ enableResizing: true,
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="순서" />
+ ),
+ meta: {
+ excelHeader: "순서",
+ type: "number",
+ },
+ cell: ({ row }) => row.getValue("sdq") ?? "",
+ minSize: 50
+ },
+ {
accessorKey: "optionCode",
enableResizing: true,
header: ({ column }) => (
diff --git a/lib/docu-list-rule/document-class/table/document-class-table-columns.tsx b/lib/docu-list-rule/document-class/table/document-class-table-columns.tsx
index ad8494c7..8c391def 100644
--- a/lib/docu-list-rule/document-class/table/document-class-table-columns.tsx
+++ b/lib/docu-list-rule/document-class/table/document-class-table-columns.tsx
@@ -107,20 +107,6 @@ export function getColumns({ setRowAction, onDetail }: GetColumnsProps): ColumnD
// ----------------------------------------------------------------
const dataColumns: ColumnDef<typeof documentClasses.$inferSelect>[] = [
{
- accessorKey: "projectCode",
- enableResizing: true,
- enableColumnFilter: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="프로젝트 코드" />
- ),
- meta: {
- excelHeader: "프로젝트 코드",
- type: "text",
- },
- cell: ({ row }) => row.getValue("projectCode") ?? "",
- minSize: 120
- },
- {
accessorKey: "code",
enableResizing: true,
header: ({ column }) => (
diff --git a/lib/docu-list-rule/document-class/table/document-class-table.tsx b/lib/docu-list-rule/document-class/table/document-class-table.tsx
index 03855fe1..11ec3a3c 100644
--- a/lib/docu-list-rule/document-class/table/document-class-table.tsx
+++ b/lib/docu-list-rule/document-class/table/document-class-table.tsx
@@ -59,12 +59,7 @@ export function DocumentClassTable({ promises }: DocumentClassTableProps) {
})
- // 컴포넌트 마운트 후 그룹핑 설정
- React.useEffect(() => {
- if (rawData[0]?.data && table.getState().grouping.length === 0) {
- table.setGrouping(["projectCode"])
- }
- }, [table, rawData])
+
// 정렬 시 펼쳐진 상태 유지
React.useEffect(() => {
diff --git a/lib/docu-list-rule/document-class/validation.ts b/lib/docu-list-rule/document-class/validation.ts
index 78f87484..b69b49ea 100644
--- a/lib/docu-list-rule/document-class/validation.ts
+++ b/lib/docu-list-rule/document-class/validation.ts
@@ -9,4 +9,5 @@ export const searchParamsDocumentClassCache = createSearchParamsCache({
filters: getFiltersStateParser(),
search: parseAsString.withDefault(""),
joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
+ projectId: parseAsString.withDefault(""),
}); \ No newline at end of file
diff --git a/lib/docu-list-rule/number-type-configs/service.ts b/lib/docu-list-rule/number-type-configs/service.ts
index 01e045ed..f4cadc70 100644
--- a/lib/docu-list-rule/number-type-configs/service.ts
+++ b/lib/docu-list-rule/number-type-configs/service.ts
@@ -3,7 +3,7 @@
import { revalidatePath } from "next/cache"
import db from "@/db/db"
import { unstable_noStore } from "next/cache"
-import { documentNumberTypeConfigs, codeGroups } from "@/db/schema/docu-list-rule"
+import { documentNumberTypeConfigs, codeGroups, documentNumberTypes } from "@/db/schema/docu-list-rule"
import { projects } from "@/db/schema/projects"
import { asc, eq, sql, and } from "drizzle-orm"
import { GetNumberTypeConfigsSchema } from "./validation"
@@ -27,6 +27,11 @@ export async function getNumberTypeConfigs(input: GetNumberTypeConfigsSchema) {
// 기본 조건: 특정 Number Type
let whereConditions = eq(documentNumberTypeConfigs.documentNumberTypeId, numberTypeId)
+
+ // 프로젝트 ID 필터링 (Number Type이 해당 프로젝트에 속하는지 확인)
+ if (input.projectId) {
+ whereConditions = sql`${whereConditions} AND ${documentNumberTypes.projectId} = ${parseInt(input.projectId)}`
+ }
// 검색 조건 추가
if (search && search.trim() !== "") {
@@ -128,6 +133,7 @@ export async function getNumberTypeConfigs(input: GetNumberTypeConfigsSchema) {
.from(documentNumberTypeConfigs)
.leftJoin(codeGroups, eq(documentNumberTypeConfigs.codeGroupId, codeGroups.id))
.leftJoin(projects, eq(codeGroups.projectId, projects.id))
+ .leftJoin(documentNumberTypes, eq(documentNumberTypeConfigs.documentNumberTypeId, documentNumberTypes.id))
.where(whereConditions)
.orderBy(orderBy)
.limit(perPage)
@@ -139,6 +145,7 @@ export async function getNumberTypeConfigs(input: GetNumberTypeConfigsSchema) {
.from(documentNumberTypeConfigs)
.leftJoin(codeGroups, eq(documentNumberTypeConfigs.codeGroupId, codeGroups.id))
.leftJoin(projects, eq(codeGroups.projectId, projects.id))
+ .leftJoin(documentNumberTypes, eq(documentNumberTypeConfigs.documentNumberTypeId, documentNumberTypes.id))
.where(whereConditions)
const totalCount = totalCountResult[0]?.count || 0
diff --git a/lib/docu-list-rule/number-type-configs/table/number-type-configs-table-columns.tsx b/lib/docu-list-rule/number-type-configs/table/number-type-configs-table-columns.tsx
index 24255acf..260c50cd 100644
--- a/lib/docu-list-rule/number-type-configs/table/number-type-configs-table-columns.tsx
+++ b/lib/docu-list-rule/number-type-configs/table/number-type-configs-table-columns.tsx
@@ -49,19 +49,7 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<NumberT
enableHiding: false,
size: 35,
},
- {
- accessorKey: "projectCode",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="프로젝트 코드" />
- ),
- meta: {
- excelHeader: "프로젝트 코드",
- type: "text",
- },
- cell: ({ row }) => row.getValue("projectCode") ?? "",
- minSize: 120
- },
+
{
accessorKey: "sdq",
enableResizing: true,
diff --git a/lib/docu-list-rule/number-type-configs/table/number-type-configs-table.tsx b/lib/docu-list-rule/number-type-configs/table/number-type-configs-table.tsx
index 1bddfa52..df01fd81 100644
--- a/lib/docu-list-rule/number-type-configs/table/number-type-configs-table.tsx
+++ b/lib/docu-list-rule/number-type-configs/table/number-type-configs-table.tsx
@@ -7,8 +7,9 @@ import type { DataTableAdvancedFilterField, DataTableRowAction } from "@/types/t
import { arrayMove } from '@dnd-kit/sortable'
import { DragEndEvent } from '@dnd-kit/core'
import { toast } from "sonner"
+import { useParams } from "next/navigation"
-import { getNumberTypeConfigs, updateNumberTypeConfig, updateNumberTypeConfigOrder } from "@/lib/docu-list-rule/number-type-configs/service"
+import { getNumberTypeConfigs, updateNumberTypeConfig } from "@/lib/docu-list-rule/number-type-configs/service"
import { getColumns } from "@/lib/docu-list-rule/number-type-configs/table/number-type-configs-table-columns"
import { DeleteNumberTypeConfigsDialog } from "@/lib/docu-list-rule/number-type-configs/table/delete-number-type-configs-dialog"
import { NumberTypeConfigsEditDialog } from "@/lib/docu-list-rule/number-type-configs/table/number-type-configs-edit-dialog"
@@ -32,7 +33,8 @@ interface NumberTypeConfigsTableProps {
export function NumberTypeConfigsTable({ promises, searchParams }: NumberTypeConfigsTableProps) {
const rawData = React.use(promises!)
- const [selectedProjectId, setSelectedProjectId] = React.useState<number | null>(null)
+ const params = useParams()
+ const projectId = Number(params?.projectId)
const [selectedNumberType, setSelectedNumberType] = React.useState<number | null>(null)
const [configsData, setConfigsData] = React.useState<{ data: NumberTypeConfig[]; pageCount: number }>({ data: [], pageCount: 0 })
const [rowAction, setRowAction] = React.useState<DataTableRowAction<NumberTypeConfig> | null>(null)
@@ -64,18 +66,12 @@ export function NumberTypeConfigsTable({ promises, searchParams }: NumberTypeCon
const result = rawData[0]
if (result.data && result.data.length > 0 && isInitialLoad.current) {
- // 초기 로드 시 첫 번째 프로젝트의 첫 번째 Number Type을 선택
- const firstProjectId = result.data[0].projectId
- setSelectedProjectId(firstProjectId)
+ // 초기 로드 시 첫 번째 Number Type을 선택
+ const firstNumberType = result.data[0]
+ setSelectedNumberType(firstNumberType.id)
- const firstProjectNumberTypes = result.data.filter(nt => nt.projectId === firstProjectId)
- if (firstProjectNumberTypes.length > 0) {
- const firstNumberType = firstProjectNumberTypes[0]
- setSelectedNumberType(firstNumberType.id)
-
- // 첫 번째 타입의 configs 데이터도 바로 로드
- await fetchConfigs(firstNumberType.id)
- }
+ // 첫 번째 타입의 configs 데이터도 바로 로드
+ await fetchConfigs(firstNumberType.id)
// 초기 로드 완료 표시
isInitialLoad.current = false
@@ -89,25 +85,7 @@ export function NumberTypeConfigsTable({ promises, searchParams }: NumberTypeCon
loadData()
}, [rawData, fetchConfigs])
- // 프로젝트 변경 핸들러
- const handleProjectChange = React.useCallback((projectId: number | null) => {
- setSelectedProjectId(projectId)
- setConfigsData({ data: [], pageCount: 0 }) // Configs 데이터 초기화
-
- if (projectId && rawData[0]?.data) {
- // 선택된 프로젝트의 첫 번째 Number Type을 자동 선택
- const selectedProjectNumberTypes = rawData[0].data.filter(nt => nt.projectId === projectId)
- if (selectedProjectNumberTypes.length > 0) {
- const firstNumberType = selectedProjectNumberTypes[0]
- setSelectedNumberType(firstNumberType.id)
- fetchConfigs(firstNumberType.id)
- } else {
- setSelectedNumberType(null)
- }
- } else {
- setSelectedNumberType(null)
- }
- }, [rawData, fetchConfigs])
+
const handleNumberTypeChange = React.useCallback((numberTypeId: number) => {
@@ -253,8 +231,6 @@ export function NumberTypeConfigsTable({ promises, searchParams }: NumberTypeCon
numberTypes={rawData[0]?.data || []}
selectedNumberType={selectedNumberType}
onNumberTypeChange={handleNumberTypeChange}
- selectedProjectId={selectedProjectId}
- onProjectChange={handleProjectChange}
isLoading={!rawData[0]?.data}
/>
@@ -273,7 +249,7 @@ export function NumberTypeConfigsTable({ promises, searchParams }: NumberTypeCon
table={table}
onSuccess={refreshData}
selectedNumberType={selectedNumberType}
- selectedProjectId={selectedProjectId}
+ selectedProjectId={projectId}
configsData={configsData.data}
/>
</DataTableAdvancedToolbar>
@@ -297,7 +273,6 @@ export function NumberTypeConfigsTable({ promises, searchParams }: NumberTypeCon
onOpenChange={() => setRowAction(null)}
data={rowAction?.row.original ?? null}
existingConfigs={configsData.data}
- selectedProjectId={selectedProjectId}
onSuccess={() => {
setRowAction(null)
refreshData()
diff --git a/lib/docu-list-rule/number-type-configs/table/number-type-selector.tsx b/lib/docu-list-rule/number-type-configs/table/number-type-selector.tsx
index d01e25d5..dd8aa023 100644
--- a/lib/docu-list-rule/number-type-configs/table/number-type-selector.tsx
+++ b/lib/docu-list-rule/number-type-configs/table/number-type-selector.tsx
@@ -2,13 +2,7 @@
import * as React from "react"
import { documentNumberTypes } from "@/db/schema/docu-list-rule"
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select"
+
// Number Type with project info type
type NumberTypeWithProject = typeof documentNumberTypes.$inferSelect & {
@@ -20,8 +14,6 @@ interface NumberTypeSelectorProps {
numberTypes: NumberTypeWithProject[]
selectedNumberType: number | null
onNumberTypeChange: (numberTypeId: number) => void
- selectedProjectId: number | null
- onProjectChange: (projectId: number | null) => void
isLoading?: boolean
}
@@ -29,60 +21,14 @@ export function NumberTypeSelector({
numberTypes,
selectedNumberType,
onNumberTypeChange,
- selectedProjectId,
- onProjectChange,
isLoading = false
}: NumberTypeSelectorProps) {
- // 프로젝트별로 Number Types 그룹화
- const projectGroups = React.useMemo(() => {
- const groups: { [key: string]: { id: number; code: string; name: string; numberTypes: NumberTypeWithProject[] } } = {}
-
- numberTypes.forEach(numberType => {
- const projectKey = `${numberType.projectId}`
- if (!groups[projectKey]) {
- groups[projectKey] = {
- id: numberType.projectId,
- code: numberType.projectCode || '',
- name: numberType.projectName || '',
- numberTypes: []
- }
- }
- groups[projectKey].numberTypes.push(numberType)
- })
-
- return Object.values(groups).sort((a, b) => a.code.localeCompare(b.code))
- }, [numberTypes])
-
- // 선택된 프로젝트의 Number Types만 필터링
- const filteredNumberTypes = React.useMemo(() => {
- if (!selectedProjectId) return []
- return numberTypes.filter(nt => nt.projectId === selectedProjectId)
- }, [numberTypes, selectedProjectId])
-
- // 프로젝트 변경 시 Number Type 선택 초기화
- const handleNumberTypeReset = React.useCallback(() => {
- if (selectedProjectId && selectedNumberType) {
- const isNumberTypeInProject = filteredNumberTypes.some(nt => nt.id === selectedNumberType)
- if (!isNumberTypeInProject) {
- onNumberTypeChange(0) // 선택 해제
- }
- }
- }, [selectedProjectId, selectedNumberType, filteredNumberTypes, onNumberTypeChange])
- React.useEffect(() => {
- handleNumberTypeReset()
- }, [handleNumberTypeReset])
if (isLoading) {
return (
<div className="mb-6 space-y-4">
<div>
- <label className="text-sm font-medium mb-2 block">프로젝트 선택</label>
- <div className="px-4 py-2 text-sm text-muted-foreground">
- 프로젝트를 불러오는 중...
- </div>
- </div>
- <div>
<label className="text-sm font-medium mb-2 block">Number Type 선택</label>
<div className="px-4 py-2 text-sm text-muted-foreground">
Number Type을 불러오는 중...
@@ -96,12 +42,6 @@ export function NumberTypeSelector({
return (
<div className="mb-6 space-y-4">
<div>
- <label className="text-sm font-medium mb-2 block">프로젝트 선택</label>
- <div className="px-4 py-2 text-sm text-muted-foreground">
- 사용 가능한 프로젝트가 없습니다.
- </div>
- </div>
- <div>
<label className="text-sm font-medium mb-2 block">Number Type 선택</label>
<div className="px-4 py-2 text-sm text-muted-foreground">
사용 가능한 Number Type이 없습니다.
@@ -113,54 +53,24 @@ export function NumberTypeSelector({
return (
<div className="mb-6 space-y-4">
- {/* 프로젝트 선택 */}
- <div>
- <label className="text-sm font-medium mb-2 block">프로젝트 선택</label>
- <Select
- value={selectedProjectId?.toString() || ""}
- onValueChange={(value) => onProjectChange(value ? parseInt(value) : null)}
- >
- <SelectTrigger className="w-[300px]">
- <SelectValue placeholder="프로젝트를 선택하세요" />
- </SelectTrigger>
- <SelectContent>
- {projectGroups.map((project) => (
- <SelectItem key={project.id} value={project.id.toString()}>
- {project.code} - {project.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
-
{/* Number Type 선택 */}
<div>
<label className="text-sm font-medium mb-2 block">Number Type 선택</label>
- {!selectedProjectId ? (
- <div className="px-4 py-2 text-sm text-muted-foreground">
- 프로젝트를 먼저 선택해주세요.
- </div>
- ) : filteredNumberTypes.length === 0 ? (
- <div className="px-4 py-2 text-sm text-muted-foreground">
- 선택한 프로젝트에 Number Type이 없습니다.
- </div>
- ) : (
- <div className="flex gap-2 flex-wrap">
- {filteredNumberTypes.map((numberType) => (
- <button
- key={numberType.id}
- onClick={() => onNumberTypeChange(numberType.id)}
- className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
- selectedNumberType === numberType.id
- ? "bg-primary text-primary-foreground"
- : "bg-muted text-muted-foreground hover:bg-muted/80"
- }`}
- >
- {numberType.name}
- </button>
- ))}
- </div>
- )}
+ <div className="flex gap-2 flex-wrap">
+ {numberTypes.map((numberType) => (
+ <button
+ key={numberType.id}
+ onClick={() => onNumberTypeChange(numberType.id)}
+ className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
+ selectedNumberType === numberType.id
+ ? "bg-primary text-primary-foreground"
+ : "bg-muted text-muted-foreground hover:bg-muted/80"
+ }`}
+ >
+ {numberType.name}
+ </button>
+ ))}
+ </div>
</div>
</div>
)
diff --git a/lib/docu-list-rule/number-type-configs/validation.ts b/lib/docu-list-rule/number-type-configs/validation.ts
index deb8193a..5b7977b4 100644
--- a/lib/docu-list-rule/number-type-configs/validation.ts
+++ b/lib/docu-list-rule/number-type-configs/validation.ts
@@ -28,6 +28,7 @@ export const searchParamsNumberTypeConfigsCache = createSearchParamsCache({
description: parseAsString.withDefault(""),
remark: parseAsString.withDefault(""),
isActive: parseAsString.withDefault(""),
+ projectId: parseAsString.withDefault(""),
// 고급 필터
filters: getFiltersStateParser().withDefault([]),
diff --git a/lib/docu-list-rule/number-types/service.ts b/lib/docu-list-rule/number-types/service.ts
index 18c5e6a3..3fb51abd 100644
--- a/lib/docu-list-rule/number-types/service.ts
+++ b/lib/docu-list-rule/number-types/service.ts
@@ -19,6 +19,7 @@ export async function getNumberTypes(input: {
numberTypeId?: string
description?: string
isActive?: string
+ projectId?: string
}) {
unstable_noStore()
@@ -29,10 +30,15 @@ export async function getNumberTypes(input: {
// 기본 조건 (plant 타입 프로젝트만)
let whereConditions = sql`${projects.type} = 'plant'`
+ // 프로젝트 ID 필터링
+ if (input.projectId) {
+ whereConditions = sql`${whereConditions} AND ${documentNumberTypes.projectId} = ${parseInt(input.projectId)}`
+ }
+
// 검색 조건
if (search) {
const searchTerm = `%${search}%`
- whereConditions = sql`${projects.type} = 'plant' AND (
+ whereConditions = sql`${whereConditions} AND (
${documentNumberTypes.name} ILIKE ${searchTerm} OR
${documentNumberTypes.description} ILIKE ${searchTerm} OR
${projects.code} ILIKE ${searchTerm}
@@ -373,6 +379,7 @@ export async function getNumberTypesWithConfigs(input: {
numberTypeId?: string
description?: string
isActive?: string
+ projectId?: string
}) {
unstable_noStore()
@@ -383,10 +390,15 @@ export async function getNumberTypesWithConfigs(input: {
// 기본 조건 (plant 타입 프로젝트만)
let whereConditions = sql`${projects.type} = 'plant'`
+ // 프로젝트 ID 필터링
+ if (input.projectId) {
+ whereConditions = sql`${whereConditions} AND ${documentNumberTypes.projectId} = ${parseInt(input.projectId)}`
+ }
+
// 검색 조건
if (search) {
const searchTerm = `%${search}%`
- whereConditions = sql`${projects.type} = 'plant' AND (
+ whereConditions = sql`${whereConditions} AND (
${documentNumberTypes.name} ILIKE ${searchTerm} OR
${documentNumberTypes.description} ILIKE ${searchTerm} OR
${projects.code} ILIKE ${searchTerm}
diff --git a/lib/docu-list-rule/number-types/table/number-type-add-dialog.tsx b/lib/docu-list-rule/number-types/table/number-type-add-dialog.tsx
index 903cd5c6..545e3965 100644
--- a/lib/docu-list-rule/number-types/table/number-type-add-dialog.tsx
+++ b/lib/docu-list-rule/number-types/table/number-type-add-dialog.tsx
@@ -14,70 +14,30 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select"
import { toast } from "sonner"
import { createNumberType } from "@/lib/docu-list-rule/number-types/service"
-import { getProjectLists } from "@/lib/projects/service"
+import { useParams } from "next/navigation"
interface NumberTypeAddDialogProps {
onSuccess: () => void
}
export function NumberTypeAddDialog({ onSuccess }: NumberTypeAddDialogProps) {
+ const params = useParams()
+ const projectId = Number(params?.projectId)
const [open, setOpen] = React.useState(false)
const [loading, setLoading] = React.useState(false)
- const [projects, setProjects] = React.useState<Array<{ id: number; code: string; name: string; type: string }>>([])
+
const [formData, setFormData] = React.useState({
- projectId: "",
name: "",
description: "",
})
- // 프로젝트 목록 로드
- React.useEffect(() => {
- if (open) {
- const loadProjects = async () => {
- try {
- const result = await getProjectLists({
- name: "",
- search: "",
- code: "",
- type: "",
- sort: [],
- filters: [],
- joinOperator: "and",
- flags: [],
- page: 1,
- perPage: 1000
- })
- if (result.data) {
- // plant 타입의 프로젝트만 필터링
- const plantProjects = result.data.filter(project => project.type === 'plant')
- setProjects(plantProjects)
- }
- } catch (error) {
- console.error("Failed to load projects:", error)
- toast.error("프로젝트 목록을 불러오는데 실패했습니다.")
- }
- }
- loadProjects()
- }
- }, [open])
+
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
- if (!formData.projectId) {
- toast.error("프로젝트는 필수 선택 항목입니다.")
- return
- }
-
if (!formData.name.trim()) {
toast.error("Name은 필수 입력 항목입니다.")
return
@@ -86,14 +46,14 @@ export function NumberTypeAddDialog({ onSuccess }: NumberTypeAddDialogProps) {
setLoading(true)
try {
const result = await createNumberType({
- projectId: parseInt(formData.projectId),
+ projectId: projectId,
name: formData.name.trim(),
description: formData.description.trim() || undefined,
})
if (result.success) {
toast.success("Number Type이 생성되었습니다.")
- setFormData({ projectId: "", name: "", description: "" })
+ setFormData({ name: "", description: "" })
setOpen(false)
onSuccess()
} else {
@@ -108,7 +68,7 @@ export function NumberTypeAddDialog({ onSuccess }: NumberTypeAddDialogProps) {
}
const handleCancel = () => {
- setFormData({ projectId: "", name: "", description: "" })
+ setFormData({ name: "", description: "" })
setOpen(false)
}
@@ -130,21 +90,6 @@ export function NumberTypeAddDialog({ onSuccess }: NumberTypeAddDialogProps) {
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
- <Label htmlFor="projectId">프로젝트 *</Label>
- <Select onValueChange={(value) => setFormData(prev => ({ ...prev, projectId: value }))} value={formData.projectId}>
- <SelectTrigger>
- <SelectValue placeholder="프로젝트를 선택하세요" />
- </SelectTrigger>
- <SelectContent>
- {projects.map((project) => (
- <SelectItem key={project.id} value={project.id.toString()}>
- {project.code} - {project.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
- <div className="space-y-2">
<Label htmlFor="name">Name *</Label>
<Input
id="name"
@@ -175,7 +120,7 @@ export function NumberTypeAddDialog({ onSuccess }: NumberTypeAddDialogProps) {
</Button>
<Button
type="submit"
- disabled={loading || !formData.projectId || !formData.name.trim()}
+ disabled={loading || !formData.name.trim()}
>
{loading ? "Creating..." : "Create"}
</Button>
diff --git a/lib/docu-list-rule/number-types/table/number-types-table-columns.tsx b/lib/docu-list-rule/number-types/table/number-types-table-columns.tsx
index c529bcfb..59403f3d 100644
--- a/lib/docu-list-rule/number-types/table/number-types-table-columns.tsx
+++ b/lib/docu-list-rule/number-types/table/number-types-table-columns.tsx
@@ -109,19 +109,6 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<NumberT
// ----------------------------------------------------------------
const dataColumns: ColumnDef<NumberTypeWithConfigs>[] = [
{
- accessorKey: "projectCode",
- enableResizing: true,
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="프로젝트 코드" />
- ),
- meta: {
- excelHeader: "프로젝트 코드",
- type: "text",
- },
- cell: ({ row }) => row.getValue("projectCode") ?? "",
- minSize: 120
- },
- {
accessorKey: "name",
enableResizing: true,
header: ({ column }) => (
diff --git a/lib/docu-list-rule/number-types/table/number-types-table.tsx b/lib/docu-list-rule/number-types/table/number-types-table.tsx
index 22e63363..a57d0634 100644
--- a/lib/docu-list-rule/number-types/table/number-types-table.tsx
+++ b/lib/docu-list-rule/number-types/table/number-types-table.tsx
@@ -63,12 +63,7 @@ export function NumberTypesTable({ promises }: NumberTypesTableProps) {
clearOnDefault: true,
})
- // 컴포넌트 마운트 후 그룹핑 설정
- React.useEffect(() => {
- if (rawData[0]?.data && table.getState().grouping.length === 0) {
- table.setGrouping(["projectCode"])
- }
- }, [table, rawData])
+
// 정렬 시 펼쳐진 상태 유지
React.useEffect(() => {
diff --git a/lib/docu-list-rule/number-types/validation.ts b/lib/docu-list-rule/number-types/validation.ts
index 6382ee5a..9d281b1b 100644
--- a/lib/docu-list-rule/number-types/validation.ts
+++ b/lib/docu-list-rule/number-types/validation.ts
@@ -9,4 +9,5 @@ export const searchParamsNumberTypesCache = createSearchParamsCache({
filters: getFiltersStateParser(),
search: parseAsString.withDefault(""),
joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
+ projectId: parseAsString.withDefault(""),
}); \ No newline at end of file