diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/[lng]/evcp/(evcp)/material-groups/page.tsx | 76 | ||||
| -rw-r--r-- | app/api/table/material-groups/infinite/route.ts | 93 |
2 files changed, 169 insertions, 0 deletions
diff --git a/app/[lng]/evcp/(evcp)/material-groups/page.tsx b/app/[lng]/evcp/(evcp)/material-groups/page.tsx new file mode 100644 index 00000000..3acd11b9 --- /dev/null +++ b/app/[lng]/evcp/(evcp)/material-groups/page.tsx @@ -0,0 +1,76 @@ +/** + * 자재그룹 테이블 + * materialSearchView를 사용하여 MDG 자재마스터의 고유한 자재그룹 조회 + * 수정/추가 기능은 불필요 (읽기 전용) + */ + +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" +import { getMaterialGroups } from "@/lib/material-groups/services" +import { MaterialGroupTable } from "@/lib/material-groups/table/material-group-table" +import { InformationButton } from "@/components/information/information-button" +import { searchParamsCache } from "@/lib/material-groups/validations" + +interface MaterialGroupPageProps { + searchParams: Promise<SearchParams> +} + +export default async function MaterialGroupPage(props: MaterialGroupPageProps) { + const searchParams = await props.searchParams + + // searchParamsCache를 사용해서 파라미터 파싱 + const search = searchParamsCache.parse(searchParams) + + // pageSize 기반으로 모드 자동 결정 + const isInfiniteMode = search.perPage >= 1_000_000 + + // 페이지네이션 모드일 때만 서버에서 데이터 가져오기 + // 무한 스크롤 모드에서는 클라이언트에서 SWR로 데이터 로드 + const promises = isInfiniteMode + ? undefined + : Promise.all([ + getMaterialGroups(search), + ]) + + return ( + <Shell className="gap-2"> + <div className="flex items-center justify-between space-y-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"> + 자재그룹 + </h2> + <InformationButton pagePath="evcp/material-groups" /> + </div> + <p className="text-muted-foreground"> + MDG로부터 수신된 자재그룹 정보 + </p> + </div> + </div> + </div> + + <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> + {/* 추가 컴포넌트가 필요한 경우 여기에 */} + </React.Suspense> + + <React.Suspense + fallback={ + <DataTableSkeleton + columnCount={2} + searchableColumnCount={1} + filterableColumnCount={2} + cellWidths={["15rem", "25rem"]} + shrinkZero + /> + } + > + <MaterialGroupTable promises={promises} /> + </React.Suspense> + </Shell> + ) +} diff --git a/app/api/table/material-groups/infinite/route.ts b/app/api/table/material-groups/infinite/route.ts new file mode 100644 index 00000000..1201ab77 --- /dev/null +++ b/app/api/table/material-groups/infinite/route.ts @@ -0,0 +1,93 @@ +// app/api/table/material-groups/infinite/route.ts +import { NextRequest, NextResponse } from "next/server"; +import { getMaterialGroupsInfinite, type GetMaterialGroupsInfiniteInput } from "@/lib/material-groups/services"; + +// URL 파라미터를 GetMaterialGroupsInfiniteInput으로 변환하는 헬퍼 함수 +function parseUrlParamsToInfiniteInput(searchParams: URLSearchParams): GetMaterialGroupsInfiniteInput { + + const cursor = searchParams.get("cursor") || undefined; + const limit = parseInt(searchParams.get("limit") || "50"); + + // 고급 필터링 관련 + const search = searchParams.get("search") || ""; + const joinOperator = searchParams.get("joinOperator") || "and"; + + // 필터 파라미터 파싱 + let filters: any[] = []; + const filtersParam = searchParams.get("filters"); + if (filtersParam) { + try { + filters = JSON.parse(filtersParam); + } catch (e) { + console.warn("Invalid filters parameter:", e); + filters = []; + } + } + + // 정렬 파라미터 파싱 + let sort: Array<{ id: string; desc: boolean }> = [{ id: "materialGroupCode", desc: false }]; + const sortParam = searchParams.get("sort"); + if (sortParam) { + try { + sort = JSON.parse(sortParam); + } catch (e) { + console.warn("Invalid sort parameter:", e); + // 기본 정렬 + sort = [{ id: "materialGroupCode", desc: false }]; + } + } else { + // 정렬이 없으면 기본 정렬 + sort = [{ id: "materialGroupCode", desc: false }]; + } + + return { + // 무한 스크롤 관련 + limit, + + // 고급 필터링 + search, + filters, + joinOperator: joinOperator as "and" | "or", + + // 정렬 + sort, + }; +} + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + console.log("=== Material Groups Infinite API ==="); + console.log("Raw searchParams:", Object.fromEntries(searchParams.entries())); + + // URL 파라미터 파싱 + const input = parseUrlParamsToInfiniteInput(searchParams); + console.log("Parsed input:", input); + + // 데이터 조회 + const result = await getMaterialGroupsInfinite(input); + console.log("Query result count:", result.data.length); + + // 응답 구성 + const response = { + items: result.data, + hasNextPage: false, // 무한 스크롤에서는 모든 데이터를 한번에 로드 + nextCursor: null, + total: result.data.length, + }; + + return NextResponse.json(response); + } catch (error) { + console.error("Material Groups infinite API error:", error); + return NextResponse.json( + { + error: "Internal server error", + items: [], + hasNextPage: false, + nextCursor: null, + total: 0, + }, + { status: 500 } + ); + } +} |
