summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-09-17 18:04:10 +0900
committerjoonhoekim <26rote@gmail.com>2025-09-17 18:04:10 +0900
commit7433eea5b4bbc0899e255b88e1a7e91f26e9d95b (patch)
treecad02c119fd41545e24a98734488962c78ed895d /lib
parent6c11fccc84f4c84fa72ee01f9caad9f76f35cea2 (diff)
(김준회) 자재그룹 선택기 오류수정, 공용사용을 위한 다이얼로그 컴포넌트 구현, data-table 오류 수정
Diffstat (limited to 'lib')
-rw-r--r--lib/material-groups/services.ts67
-rw-r--r--lib/material-groups/table/material-group-table-columns.tsx23
-rw-r--r--lib/material-groups/table/material-group-table.tsx16
-rw-r--r--lib/material-groups/validations.ts4
-rw-r--r--lib/material/material-group-service.ts26
-rw-r--r--lib/material/vendor-material/add-confirmed-material.tsx123
6 files changed, 111 insertions, 148 deletions
diff --git a/lib/material-groups/services.ts b/lib/material-groups/services.ts
index be683077..3ee9886d 100644
--- a/lib/material-groups/services.ts
+++ b/lib/material-groups/services.ts
@@ -3,10 +3,10 @@
import { and, asc, desc, ilike, or, sql, eq } from 'drizzle-orm';
import db from '@/db/db';
import { filterColumns } from "@/lib/filter-columns";
-import { materialSearchView } from "@/db/schema/items";
+import { MATERIAL_GROUP_MASTER } from "@/db/schema/MDG/mdg";
-// 자재그룹 뷰의 컬럼 타입 정의
-type MaterialGroupColumn = keyof typeof materialSearchView.$inferSelect;
+// 자재그룹 테이블의 컬럼 타입 정의
+type MaterialGroupColumn = keyof typeof MATERIAL_GROUP_MASTER.$inferSelect;
export interface GetMaterialGroupsInput {
page: number;
@@ -16,13 +16,16 @@ export interface GetMaterialGroupsInput {
id: MaterialGroupColumn;
desc: boolean;
}>;
- filters?: any[];
+ filters?: Array<{
+ id: string;
+ value: unknown;
+ }>;
joinOperator: 'and' | 'or';
}
/**
- * 자재그룹 목록을 조회합니다.
- * materialSearchView를 사용하여 MATKL(자재그룹코드)와 ATWTB(자재그룹 설명)의 고유한 조합을 조회
+ * 자재그룹 목록을 조회
+ * 자재그룹코드/자재그룹명/UOM 조회
*/
export async function getMaterialGroups(input: GetMaterialGroupsInput) {
const safePerPage = Math.min(input.perPage, 100);
@@ -32,8 +35,8 @@ export async function getMaterialGroups(input: GetMaterialGroupsInput) {
// 고급 필터링
const advancedWhere = filterColumns({
- table: materialSearchView,
- filters: (input.filters || []) as any,
+ table: MATERIAL_GROUP_MASTER,
+ filters: input.filters || [],
joinOperator: input.joinOperator,
});
@@ -42,8 +45,9 @@ export async function getMaterialGroups(input: GetMaterialGroupsInput) {
if (input.search) {
const s = `%${input.search}%`;
globalWhere = or(
- ilike(materialSearchView.materialGroupCode, s), // 자재그룹코드
- ilike(materialSearchView.materialGroupDesc, s), // 자재그룹명
+ ilike(MATERIAL_GROUP_MASTER.materialGroupCode, s), // 자재그룹코드
+ ilike(MATERIAL_GROUP_MASTER.materialGroupDescription, s), // 자재그룹명
+ ilike(MATERIAL_GROUP_MASTER.materialGroupUom, s), // UOM
);
}
@@ -52,19 +56,20 @@ export async function getMaterialGroups(input: GetMaterialGroupsInput) {
// 정렬 처리 - 타입 안전하게 처리
const orderBy = input.sort.length > 0
? input.sort.map((item) => {
- const column = materialSearchView[item.id];
+ const column = MATERIAL_GROUP_MASTER[item.id];
return item.desc ? desc(column) : asc(column);
})
- : [asc(materialSearchView.materialGroupCode)];
+ : [asc(MATERIAL_GROUP_MASTER.materialGroupCode)];
// 데이터 조회
const { data, total } = await db.transaction(async (tx) => {
const data = await tx
.select({
- materialGroupCode: materialSearchView.materialGroupCode,
- materialGroupDesc: materialSearchView.materialGroupDesc,
+ materialGroupCode: MATERIAL_GROUP_MASTER.materialGroupCode,
+ materialGroupDescription: MATERIAL_GROUP_MASTER.materialGroupDescription,
+ materialGroupUom: MATERIAL_GROUP_MASTER.materialGroupUom,
})
- .from(materialSearchView)
+ .from(MATERIAL_GROUP_MASTER)
.where(finalWhere)
.orderBy(...orderBy)
.offset(offset)
@@ -74,7 +79,7 @@ export async function getMaterialGroups(input: GetMaterialGroupsInput) {
.select({
count: sql<number>`count(*)`
})
- .from(materialSearchView)
+ .from(MATERIAL_GROUP_MASTER)
.where(finalWhere);
const total = Number(totalResult[0]?.count) || 0;
@@ -100,8 +105,8 @@ export async function getMaterialGroupsInfinite(input: GetMaterialGroupsInfinite
try {
// 고급 필터링
const advancedWhere = filterColumns({
- table: materialSearchView,
- filters: (input.filters || []) as any,
+ table: MATERIAL_GROUP_MASTER,
+ filters: input.filters || [],
joinOperator: input.joinOperator || "and",
});
@@ -110,8 +115,9 @@ export async function getMaterialGroupsInfinite(input: GetMaterialGroupsInfinite
if (input.search) {
const s = `%${input.search}%`;
globalWhere = or(
- ilike(materialSearchView.materialGroupCode, s),
- ilike(materialSearchView.materialGroupDesc, s),
+ ilike(MATERIAL_GROUP_MASTER.materialGroupCode, s),
+ ilike(MATERIAL_GROUP_MASTER.materialGroupDescription, s),
+ ilike(MATERIAL_GROUP_MASTER.materialGroupUom, s),
);
}
@@ -120,18 +126,19 @@ export async function getMaterialGroupsInfinite(input: GetMaterialGroupsInfinite
// 정렬 처리 - 타입 안전하게 처리
const orderBy = input.sort.length > 0
? input.sort.map((item) => {
- const column = materialSearchView[item.id];
+ const column = MATERIAL_GROUP_MASTER[item.id];
return item.desc ? desc(column) : asc(column);
})
- : [asc(materialSearchView.materialGroupCode)];
+ : [asc(MATERIAL_GROUP_MASTER.materialGroupCode)];
// 전체 데이터 조회 (클라이언트에서 가상화 처리)
const data = await db
.select({
- materialGroupCode: materialSearchView.materialGroupCode,
- materialGroupDesc: materialSearchView.materialGroupDesc,
+ materialGroupCode: MATERIAL_GROUP_MASTER.materialGroupCode,
+ materialGroupDescription: MATERIAL_GROUP_MASTER.materialGroupDescription,
+ materialGroupUom: MATERIAL_GROUP_MASTER.materialGroupUom,
})
- .from(materialSearchView)
+ .from(MATERIAL_GROUP_MASTER)
.where(finalWhere)
.orderBy(...orderBy);
@@ -148,9 +155,13 @@ export async function getMaterialGroupsInfinite(input: GetMaterialGroupsInfinite
export async function getMaterialGroupDetail(materialGroupCode: string) {
try {
const materialGroup = await db
- .select()
- .from(materialSearchView)
- .where(eq(materialSearchView.materialGroupCode, materialGroupCode))
+ .select({
+ materialGroupCode: MATERIAL_GROUP_MASTER.materialGroupCode,
+ materialGroupDescription: MATERIAL_GROUP_MASTER.materialGroupDescription,
+ materialGroupUom: MATERIAL_GROUP_MASTER.materialGroupUom,
+ })
+ .from(MATERIAL_GROUP_MASTER)
+ .where(eq(MATERIAL_GROUP_MASTER.materialGroupCode, materialGroupCode))
.limit(1);
return materialGroup.length > 0 ? materialGroup[0] : null;
diff --git a/lib/material-groups/table/material-group-table-columns.tsx b/lib/material-groups/table/material-group-table-columns.tsx
index 08e730e3..f41ed887 100644
--- a/lib/material-groups/table/material-group-table-columns.tsx
+++ b/lib/material-groups/table/material-group-table-columns.tsx
@@ -8,7 +8,8 @@ import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-
// MaterialGroup 타입 정의 (서비스에서 반환되는 타입과 일치)
type MaterialGroup = {
materialGroupCode: string | null;
- materialGroupDesc: string | null;
+ materialGroupDescription: string | null;
+ materialGroupUom?: string | null;
}
/**
@@ -36,12 +37,12 @@ export function getColumns(): ColumnDef<MaterialGroup>[] {
enableHiding: false,
},
{
- accessorKey: "materialGroupDesc",
+ accessorKey: "materialGroupDescription",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="자재그룹명" />
),
cell: ({ row }) => {
- const value = row.getValue("materialGroupDesc") as string | null
+ const value = row.getValue("materialGroupDescription") as string | null
return (
<div className="max-w-[400px] truncate">
{value || "-"}
@@ -51,6 +52,22 @@ export function getColumns(): ColumnDef<MaterialGroup>[] {
enableSorting: true,
enableHiding: false,
},
+ {
+ accessorKey: "materialGroupUom",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="UOM" />
+ ),
+ cell: ({ row }) => {
+ const value = row.getValue("materialGroupUom") as string | null
+ return (
+ <div className="max-w-[80px] truncate">
+ {value || "-"}
+ </div>
+ )
+ },
+ enableSorting: true,
+ enableHiding: false,
+ },
]
// ----------------------------------------------------------------
diff --git a/lib/material-groups/table/material-group-table.tsx b/lib/material-groups/table/material-group-table.tsx
index 766b5054..a5617e9b 100644
--- a/lib/material-groups/table/material-group-table.tsx
+++ b/lib/material-groups/table/material-group-table.tsx
@@ -20,7 +20,8 @@ import { ViewModeToggle } from "@/components/data-table/view-mode-toggle"
// MaterialGroup 타입 정의 (서비스에서 반환되는 타입과 일치)
type MaterialGroup = {
materialGroupCode: string | null;
- materialGroupDesc: string | null;
+ materialGroupDescription: string | null;
+ materialGroupUom?: string | null;
}
interface MaterialGroupTableProps {
@@ -50,9 +51,13 @@ export function MaterialGroupTable({ promises }: MaterialGroupTableProps) {
label: "자재그룹코드",
},
{
- id: "materialGroupDesc",
+ id: "materialGroupDescription",
label: "자재그룹명",
},
+ {
+ id: "materialGroupUom",
+ label: "UOM",
+ },
]
const advancedFilterFields: DataTableAdvancedFilterField<MaterialGroup>[] = [
@@ -62,10 +67,15 @@ export function MaterialGroupTable({ promises }: MaterialGroupTableProps) {
type: "text",
},
{
- id: "materialGroupDesc",
+ id: "materialGroupDescription",
label: "자재그룹명",
type: "text",
},
+ {
+ id: "materialGroupUom",
+ label: "UOM",
+ type: "text",
+ },
]
// 확장된 useDataTable 훅 사용 (pageSize 기반 자동 전환)
diff --git a/lib/material-groups/validations.ts b/lib/material-groups/validations.ts
index c379c833..f2acfaee 100644
--- a/lib/material-groups/validations.ts
+++ b/lib/material-groups/validations.ts
@@ -8,7 +8,7 @@ import {
import * as z from "zod"
import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers"
-import { materialSearchView } from "@/db/schema/items"
+import { MATERIAL_GROUP_MASTER } from "@/db/schema/MDG/mdg"
// MaterialGroup 검색 파라미터 캐시 정의
export const searchParamsCache = createSearchParamsCache({
@@ -17,7 +17,7 @@ export const searchParamsCache = createSearchParamsCache({
perPage: parseAsInteger.withDefault(10),
// 확장된 타입으로 정렬 파서 사용
- sort: getSortingStateParser<typeof materialSearchView>().withDefault([
+ sort: getSortingStateParser<typeof MATERIAL_GROUP_MASTER>().withDefault([
{ id: "materialGroupCode", desc: false },
]),
diff --git a/lib/material/material-group-service.ts b/lib/material/material-group-service.ts
index ae0ca725..bf58d243 100644
--- a/lib/material/material-group-service.ts
+++ b/lib/material/material-group-service.ts
@@ -2,11 +2,12 @@
import { sql, SQL } from "drizzle-orm";
import db from "@/db/db";
-import { materialSearchView } from "@/db/schema/items";
+import { MATERIAL_GROUP_MASTER } from "@/db/schema/MDG/mdg";
export interface MaterialSearchItem {
materialGroupCode: string;
- materialGroupDesc: string;
+ materialGroupDescription: string;
+ materialGroupUom?: string;
displayText: string; // 애플리케이션 레벨에서 계산된 필드
}
@@ -39,25 +40,29 @@ export async function searchMaterialsForSelector(
if (query.trim()) {
const searchPattern = `%${query.trim()}%`;
searchWhere = sql`(
- ${materialSearchView.materialGroupCode} ILIKE ${searchPattern} OR
- ${materialSearchView.materialGroupDesc} ILIKE ${searchPattern}
+ ${MATERIAL_GROUP_MASTER.materialGroupCode} ILIKE ${searchPattern} OR
+ ${MATERIAL_GROUP_MASTER.materialGroupDescription} ILIKE ${searchPattern}
)`;
}
const { data, total } = await db.transaction(async (tx) => {
// 데이터 조회
const data = await tx
- .select()
- .from(materialSearchView)
+ .select({
+ materialGroupCode: MATERIAL_GROUP_MASTER.materialGroupCode,
+ materialGroupDescription: MATERIAL_GROUP_MASTER.materialGroupDescription,
+ materialGroupUom: MATERIAL_GROUP_MASTER.materialGroupUom,
+ })
+ .from(MATERIAL_GROUP_MASTER)
.where(searchWhere)
- .orderBy(materialSearchView.materialGroupCode, materialSearchView.materialGroupDesc)
+ .orderBy(MATERIAL_GROUP_MASTER.materialGroupCode, MATERIAL_GROUP_MASTER.materialGroupDescription)
.limit(perPage)
.offset(offset);
// 총 개수 조회
const countResult = await tx
.select({ count: sql<number>`count(*)` })
- .from(materialSearchView)
+ .from(MATERIAL_GROUP_MASTER)
.where(searchWhere);
const total = countResult[0]?.count || 0;
@@ -65,8 +70,9 @@ export async function searchMaterialsForSelector(
return {
data: data.map((row) => ({
materialGroupCode: row.materialGroupCode,
- materialGroupDesc: row.materialGroupDesc,
- displayText: `${row.materialGroupCode || ''} - ${row.materialGroupDesc || ''}`, // 애플리케이션 레벨에서 생성
+ materialGroupDescription: row.materialGroupDescription,
+ materialGroupUom: row.materialGroupUom,
+ displayText: `${row.materialGroupCode || ''} - ${row.materialGroupDescription || ''}`, // 애플리케이션 레벨에서 생성
})),
total,
};
diff --git a/lib/material/vendor-material/add-confirmed-material.tsx b/lib/material/vendor-material/add-confirmed-material.tsx
index 5e9415f4..78940c00 100644
--- a/lib/material/vendor-material/add-confirmed-material.tsx
+++ b/lib/material/vendor-material/add-confirmed-material.tsx
@@ -3,20 +3,8 @@
import * as React from "react";
import { useState } from "react";
import { useSession } from "next-auth/react";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import { Label } from "@/components/ui/label";
import { toast } from "sonner";
-import { Plus, Loader2 } from "lucide-react";
-import { MaterialGroupSelector } from "@/components/common/material/material-group-selector";
+import { MaterialGroupSelectorDialogMulti } from "@/components/common/material/material-group-selector-dialog-multi";
import { MaterialSearchItem } from "@/lib/material/material-group-service";
import { addConfirmedMaterial, VendorPossibleMaterial } from "../vendor-possible-material-service";
@@ -32,43 +20,22 @@ export function AddConfirmedMaterial({
onMaterialAdded,
}: AddConfirmedMaterialProps) {
const { data: session } = useSession();
- const [open, setOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [selectedMaterials, setSelectedMaterials] = useState<MaterialSearchItem[]>([]);
// 이미 등록된 자재그룹코드들의 Set
const existingMaterialCodes = new Set(
- existingConfirmedMaterials.map(material => material.itemCode).filter(Boolean)
+ existingConfirmedMaterials.map(material => material.itemCode).filter((code): code is string => Boolean(code))
);
- // 자재 선택 시 중복 체크
- const handleMaterialsChange = (materials: MaterialSearchItem[]) => {
- // 이미 등록된 자재가 있는지 확인
- const duplicatedMaterials = materials.filter(material =>
- existingMaterialCodes.has(material.materialGroupCode)
- );
-
- if (duplicatedMaterials.length > 0) {
- const duplicatedCodes = duplicatedMaterials.map(m => m.materialGroupCode).join(', ');
- toast.error(`이미 등록된 자재그룹코드입니다: ${duplicatedCodes}`);
-
- // 중복되지 않은 자재만 선택
- const validMaterials = materials.filter(material =>
- !existingMaterialCodes.has(material.materialGroupCode)
- );
- setSelectedMaterials(validMaterials);
- } else {
- setSelectedMaterials(materials);
- }
- };
-
- const handleSubmit = async () => {
+ // 자재 선택 완료 시 처리
+ const handleMaterialsSelect = async (materials: MaterialSearchItem[]) => {
if (!session?.user) {
toast.error("로그인이 필요합니다.");
return;
}
- if (selectedMaterials.length === 0) {
+ if (materials.length === 0) {
toast.error("추가할 자재를 선택해주세요.");
return;
}
@@ -77,10 +44,10 @@ export function AddConfirmedMaterial({
try {
// 선택된 자재들을 각각 추가
- for (const material of selectedMaterials) {
+ for (const material of materials) {
const materialData = {
itemCode: material.materialGroupCode,
- itemName: material.materialName,
+ itemName: material.materialGroupDescription,
};
await addConfirmedMaterial(
@@ -91,11 +58,10 @@ export function AddConfirmedMaterial({
);
}
- toast.success(`${selectedMaterials.length}개의 자재가 확정정보에 추가되었습니다.`);
+ toast.success(`${materials.length}개의 자재가 확정정보에 추가되었습니다.`);
// 폼 리셋
setSelectedMaterials([]);
- setOpen(false);
// 부모 컴포넌트에 추가 완료 알림
onMaterialAdded?.();
@@ -119,65 +85,18 @@ export function AddConfirmedMaterial({
};
return (
- <Dialog open={open} onOpenChange={setOpen}>
- <DialogTrigger asChild>
- <Button size="sm" className="gap-2">
- <Plus className="h-4 w-4" />
- 추가 등록
- </Button>
- </DialogTrigger>
- <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
- <DialogHeader>
- <DialogTitle>확정정보 자재 추가</DialogTitle>
- <DialogDescription>
- 구매담당자 권한으로 확정 공급품목을 추가합니다. 상세 정보는 I/F를 통해 업데이트됩니다.
- </DialogDescription>
- </DialogHeader>
-
- <div className="space-y-4">
- {/* 자재 선택 */}
- <div className="space-y-2">
- <Label>자재 선택 *</Label>
- <MaterialGroupSelector
- selectedMaterials={selectedMaterials}
- onMaterialsChange={handleMaterialsChange}
- singleSelect={false}
- placeholder="자재그룹코드 또는 자재명으로 검색..."
- noValuePlaceHolder="자재를 선택해주세요"
- maxSelections={10}
- closeOnSelect={false}
- excludeMaterialCodes={existingMaterialCodes}
- />
- {existingMaterialCodes.size > 0 && (
- <p className="text-xs text-muted-foreground">
- 💡 이미 등록된 자재그룹코드는 자동으로 제외됩니다.
- </p>
- )}
- <p className="text-sm text-muted-foreground">
- 최대 10개까지 선택 가능합니다. 등록자는 현재 로그인한 사용자로 자동 설정됩니다.
- </p>
- </div>
- </div>
-
- <DialogFooter>
- <Button
- type="button"
- variant="outline"
- onClick={() => setOpen(false)}
- disabled={isLoading}
- >
- 취소
- </Button>
- <Button
- type="button"
- onClick={handleSubmit}
- disabled={isLoading || selectedMaterials.length === 0}
- >
- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
- {isLoading ? "추가 중..." : "추가"}
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
+ <MaterialGroupSelectorDialogMulti
+ triggerLabel={isLoading ? "추가 중..." : "➕ 추가 등록"}
+ selectedMaterials={selectedMaterials}
+ onMaterialsSelect={handleMaterialsSelect}
+ placeholder="자재그룹코드 또는 자재명으로 검색..."
+ title="확정정보 자재 추가"
+ description="구매담당자 권한으로 확정 공급품목을 추가합니다. 상세 정보는 I/F를 통해 업데이트됩니다."
+ disabled={isLoading}
+ triggerVariant="default"
+ excludeMaterialCodes={existingMaterialCodes}
+ showInitialData={true}
+ showSelectedInTrigger={false}
+ />
);
}