Merge branch 'gbpark-node'
Build & Deploy to K8s / build-and-deploy (push) Successful in 9m32s

This commit is contained in:
DDD1542
2026-05-15 16:55:31 +09:00
402 changed files with 34890 additions and 50360 deletions
-231
View File
@@ -1,231 +0,0 @@
/**
* 자동 입력 (Auto-Fill) API 클라이언트
*/
import { apiClient } from "./client";
// =====================================================
// 타입 정의
// =====================================================
export interface AutoFillMapping {
mappingId?: number;
sourceColumn: string;
targetField: string;
targetLabel?: string;
isEditable?: string;
isRequired?: string;
defaultValue?: string;
sortOrder?: number;
}
export interface AutoFillGroup {
groupId?: number;
groupCode: string;
groupName: string;
description?: string;
masterTable: string;
masterValueColumn: string;
masterLabelColumn?: string;
companyCode?: string;
isActive?: string;
createdDate?: string;
updatedDate?: string;
mappingCount?: number;
mappings?: AutoFillMapping[];
}
export interface AutoFillOption {
value: string;
label: string;
}
export interface AutoFillDataResponse {
data: Record<string, any>;
mappings: Array<{
targetField: string;
targetLabel: string;
value: any;
isEditable: boolean;
isRequired: boolean;
}>;
}
// =====================================================
// API 함수
// =====================================================
/**
* 자동 입력 그룹 목록 조회
*/
export async function getAutoFillGroups(isActive?: string): Promise<{
success: boolean;
data?: AutoFillGroup[];
error?: string;
}> {
try {
const params = new URLSearchParams();
if (isActive) params.append("isActive", isActive);
const response = await apiClient.get(`/cascading-auto-fill/groups?${params.toString()}`);
return response.data;
} catch (error: any) {
console.error("자동 입력 그룹 목록 조회 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 자동 입력 그룹 상세 조회 (매핑 포함)
*/
export async function getAutoFillGroupDetail(groupCode: string): Promise<{
success: boolean;
data?: AutoFillGroup;
error?: string;
}> {
try {
const response = await apiClient.get(`/cascading-auto-fill/groups/${groupCode}`);
return response.data;
} catch (error: any) {
console.error("자동 입력 그룹 상세 조회 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 자동 입력 그룹 생성
*/
export async function createAutoFillGroup(data: {
groupCode: string;
groupName: string;
description?: string;
masterTable: string;
masterValueColumn: string;
masterLabelColumn?: string;
mappings?: AutoFillMapping[];
}): Promise<{
success: boolean;
data?: AutoFillGroup;
message?: string;
error?: string;
}> {
try {
const response = await apiClient.post("/cascading-auto-fill/groups", data);
return response.data;
} catch (error: any) {
console.error("자동 입력 그룹 생성 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 자동 입력 그룹 수정
*/
export async function updateAutoFillGroup(
groupCode: string,
data: Partial<AutoFillGroup> & { mappings?: AutoFillMapping[] }
): Promise<{
success: boolean;
data?: AutoFillGroup;
message?: string;
error?: string;
}> {
try {
const response = await apiClient.put(`/cascading-auto-fill/groups/${groupCode}`, data);
return response.data;
} catch (error: any) {
console.error("자동 입력 그룹 수정 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 자동 입력 그룹 삭제
*/
export async function deleteAutoFillGroup(groupCode: string): Promise<{
success: boolean;
message?: string;
error?: string;
}> {
try {
const response = await apiClient.delete(`/cascading-auto-fill/groups/${groupCode}`);
return response.data;
} catch (error: any) {
console.error("자동 입력 그룹 삭제 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 마스터 옵션 목록 조회
*/
export async function getAutoFillMasterOptions(groupCode: string): Promise<{
success: boolean;
data?: AutoFillOption[];
error?: string;
}> {
try {
const response = await apiClient.get(`/cascading-auto-fill/options/${groupCode}`);
return response.data;
} catch (error: any) {
console.error("마스터 옵션 목록 조회 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 자동 입력 데이터 조회
* 마스터 값 선택 시 자동으로 입력할 데이터 조회
*/
export async function getAutoFillData(
groupCode: string,
masterValue: string
): Promise<{
success: boolean;
data?: Record<string, any>;
mappings?: AutoFillDataResponse["mappings"];
error?: string;
}> {
try {
const response = await apiClient.get(
`/cascading-auto-fill/data/${groupCode}?masterValue=${encodeURIComponent(masterValue)}`
);
return response.data;
} catch (error: any) {
console.error("자동 입력 데이터 조회 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
// 편의를 위한 네임스페이스 export
export const cascadingAutoFillApi = {
getGroups: getAutoFillGroups,
getGroupDetail: getAutoFillGroupDetail,
createGroup: createAutoFillGroup,
updateGroup: updateAutoFillGroup,
deleteGroup: deleteAutoFillGroup,
getMasterOptions: getAutoFillMasterOptions,
getData: getAutoFillData,
};
-206
View File
@@ -1,206 +0,0 @@
/**
* 조건부 연쇄 (Conditional Cascading) API 클라이언트
*/
import { apiClient } from "./client";
// =====================================================
// 타입 정의
// =====================================================
export interface CascadingCondition {
conditionId?: number;
relationType: string; // "RELATION" | "HIERARCHY"
relationCode: string;
conditionName: string;
conditionField: string;
conditionOperator: string; // "EQ" | "NEQ" | "CONTAINS" | "IN" | "GT" | "LT" 등
conditionValue: string;
filterColumn: string;
filterValues: string; // 콤마로 구분된 값들
priority?: number;
companyCode?: string;
isActive?: string;
createdDate?: string;
updatedDate?: string;
}
// 연산자 목록
export const CONDITION_OPERATORS = [
{ value: "EQ", label: "같음 (=)" },
{ value: "NEQ", label: "같지 않음 (!=)" },
{ value: "CONTAINS", label: "포함" },
{ value: "NOT_CONTAINS", label: "포함하지 않음" },
{ value: "STARTS_WITH", label: "시작" },
{ value: "ENDS_WITH", label: "끝" },
{ value: "IN", label: "목록에 포함" },
{ value: "NOT_IN", label: "목록에 미포함" },
{ value: "GT", label: "보다 큼 (>)" },
{ value: "GTE", label: "보다 크거나 같음 (>=)" },
{ value: "LT", label: "보다 작음 (<)" },
{ value: "LTE", label: "보다 작거나 같음 (<=)" },
{ value: "IS_NULL", label: "비어있음" },
{ value: "IS_NOT_NULL", label: "비어있지 않음" },
];
// =====================================================
// API 함수
// =====================================================
/**
* 조건부 연쇄 규칙 목록 조회
*/
export async function getConditions(params?: {
isActive?: string;
relationCode?: string;
relationType?: string;
}): Promise<{
success: boolean;
data?: CascadingCondition[];
error?: string;
}> {
try {
const searchParams = new URLSearchParams();
if (params?.isActive) searchParams.append("isActive", params.isActive);
if (params?.relationCode) searchParams.append("relationCode", params.relationCode);
if (params?.relationType) searchParams.append("relationType", params.relationType);
const response = await apiClient.get(`/cascading-conditions?${searchParams.toString()}`);
return response.data;
} catch (error: any) {
console.error("조건부 연쇄 규칙 목록 조회 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 조건부 연쇄 규칙 상세 조회
*/
export async function getConditionDetail(conditionId: number): Promise<{
success: boolean;
data?: CascadingCondition;
error?: string;
}> {
try {
const response = await apiClient.get(`/cascading-conditions/${conditionId}`);
return response.data;
} catch (error: any) {
console.error("조건부 연쇄 규칙 상세 조회 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 조건부 연쇄 규칙 생성
*/
export async function createCondition(data: Omit<CascadingCondition, "conditionId">): Promise<{
success: boolean;
data?: CascadingCondition;
message?: string;
error?: string;
}> {
try {
const response = await apiClient.post("/cascading-conditions", data);
return response.data;
} catch (error: any) {
console.error("조건부 연쇄 규칙 생성 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 조건부 연쇄 규칙 수정
*/
export async function updateCondition(
conditionId: number,
data: Partial<CascadingCondition>
): Promise<{
success: boolean;
data?: CascadingCondition;
message?: string;
error?: string;
}> {
try {
const response = await apiClient.put(`/cascading-conditions/${conditionId}`, data);
return response.data;
} catch (error: any) {
console.error("조건부 연쇄 규칙 수정 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 조건부 연쇄 규칙 삭제
*/
export async function deleteCondition(conditionId: number): Promise<{
success: boolean;
message?: string;
error?: string;
}> {
try {
const response = await apiClient.delete(`/cascading-conditions/${conditionId}`);
return response.data;
} catch (error: any) {
console.error("조건부 연쇄 규칙 삭제 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 조건에 따른 필터링된 옵션 조회
*/
export async function getFilteredOptions(
relationCode: string,
params: {
conditionFieldValue?: string;
parentValue?: string;
}
): Promise<{
success: boolean;
data?: Array<{ value: string; label: string }>;
appliedCondition?: { conditionId: number; conditionName: string } | null;
error?: string;
}> {
try {
const searchParams = new URLSearchParams();
if (params.conditionFieldValue) searchParams.append("conditionFieldValue", params.conditionFieldValue);
if (params.parentValue) searchParams.append("parentValue", params.parentValue);
const response = await apiClient.get(
`/cascading-conditions/filtered-options/${relationCode}?${searchParams.toString()}`
);
return response.data;
} catch (error: any) {
console.error("조건부 필터링 옵션 조회 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
// 편의를 위한 네임스페이스 export
export const cascadingConditionApi = {
getList: getConditions,
getDetail: getConditionDetail,
create: createCondition,
update: updateCondition,
delete: deleteCondition,
getFilteredOptions,
};
-317
View File
@@ -1,317 +0,0 @@
/**
* 다단계 계층 (Hierarchy) API 클라이언트
*/
import { apiClient } from "./client";
// =====================================================
// 타입 정의
// =====================================================
export interface HierarchyLevel {
levelId?: number;
groupCode: string;
companyCode?: string;
levelOrder: number;
levelName: string;
levelCode?: string;
tableName: string;
valueColumn: string;
labelColumn: string;
parentKeyColumn?: string;
filterColumn?: string;
filterValue?: string;
orderColumn?: string;
orderDirection?: string;
placeholder?: string;
isRequired?: string;
isSearchable?: string;
isActive?: string;
createdDate?: string;
updatedDate?: string;
}
export interface HierarchyGroup {
groupId?: number;
groupCode: string;
groupName: string;
description?: string;
hierarchyType: "MULTI_TABLE" | "SELF_REFERENCE" | "BOM" | "TREE";
maxLevels?: number;
isFixedLevels?: string;
// Self-reference 설정
selfRefTable?: string;
selfRefIdColumn?: string;
selfRefParentColumn?: string;
selfRefValueColumn?: string;
selfRefLabelColumn?: string;
selfRefLevelColumn?: string;
selfRefOrderColumn?: string;
// BOM 설정
bomTable?: string;
bomParentColumn?: string;
bomChildColumn?: string;
bomItemTable?: string;
bomItemIdColumn?: string;
bomItemLabelColumn?: string;
bomQtyColumn?: string;
bomLevelColumn?: string;
// 메시지
emptyMessage?: string;
noOptionsMessage?: string;
loadingMessage?: string;
// 메타
companyCode?: string;
isActive?: string;
createdBy?: string;
createdDate?: string;
updatedBy?: string;
updatedDate?: string;
// 조회 시 포함
levels?: HierarchyLevel[];
levelCount?: number;
}
// 계층 타입
export const HIERARCHY_TYPES = [
{ value: "MULTI_TABLE", label: "다중 테이블 (국가>도시>구)" },
{ value: "SELF_REFERENCE", label: "자기 참조 (조직도)" },
{ value: "BOM", label: "BOM (부품 구조)" },
{ value: "TREE", label: "트리 (카테고리)" },
];
// =====================================================
// API 함수
// =====================================================
/**
* 계층 그룹 목록 조회
*/
export async function getHierarchyGroups(params?: {
isActive?: string;
hierarchyType?: string;
}): Promise<{
success: boolean;
data?: HierarchyGroup[];
error?: string;
}> {
try {
const searchParams = new URLSearchParams();
if (params?.isActive) searchParams.append("isActive", params.isActive);
if (params?.hierarchyType) searchParams.append("hierarchyType", params.hierarchyType);
const response = await apiClient.get(`/cascading-hierarchy?${searchParams.toString()}`);
return response.data;
} catch (error: any) {
console.error("계층 그룹 목록 조회 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 계층 그룹 상세 조회 (레벨 포함)
*/
export async function getHierarchyGroupDetail(groupCode: string): Promise<{
success: boolean;
data?: HierarchyGroup;
error?: string;
}> {
try {
const response = await apiClient.get(`/cascading-hierarchy/${groupCode}`);
return response.data;
} catch (error: any) {
console.error("계층 그룹 상세 조회 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 계층 그룹 생성
*/
export async function createHierarchyGroup(
data: Omit<HierarchyGroup, "groupId"> & { levels?: Partial<HierarchyLevel>[] }
): Promise<{
success: boolean;
data?: HierarchyGroup;
message?: string;
error?: string;
}> {
try {
const response = await apiClient.post("/cascading-hierarchy", data);
return response.data;
} catch (error: any) {
console.error("계층 그룹 생성 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 계층 그룹 수정
*/
export async function updateHierarchyGroup(
groupCode: string,
data: Partial<HierarchyGroup>
): Promise<{
success: boolean;
data?: HierarchyGroup;
message?: string;
error?: string;
}> {
try {
const response = await apiClient.put(`/cascading-hierarchy/${groupCode}`, data);
return response.data;
} catch (error: any) {
console.error("계층 그룹 수정 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 계층 그룹 삭제
*/
export async function deleteHierarchyGroup(groupCode: string): Promise<{
success: boolean;
message?: string;
error?: string;
}> {
try {
const response = await apiClient.delete(`/cascading-hierarchy/${groupCode}`);
return response.data;
} catch (error: any) {
console.error("계층 그룹 삭제 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 레벨 추가
*/
export async function addLevel(
groupCode: string,
data: Partial<HierarchyLevel>
): Promise<{
success: boolean;
data?: HierarchyLevel;
message?: string;
error?: string;
}> {
try {
const response = await apiClient.post(`/cascading-hierarchy/${groupCode}/levels`, data);
return response.data;
} catch (error: any) {
console.error("레벨 추가 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 레벨 수정
*/
export async function updateLevel(
levelId: number,
data: Partial<HierarchyLevel>
): Promise<{
success: boolean;
data?: HierarchyLevel;
message?: string;
error?: string;
}> {
try {
const response = await apiClient.put(`/cascading-hierarchy/levels/${levelId}`, data);
return response.data;
} catch (error: any) {
console.error("레벨 수정 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 레벨 삭제
*/
export async function deleteLevel(levelId: number): Promise<{
success: boolean;
message?: string;
error?: string;
}> {
try {
const response = await apiClient.delete(`/cascading-hierarchy/levels/${levelId}`);
return response.data;
} catch (error: any) {
console.error("레벨 삭제 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 특정 레벨의 옵션 조회
*/
export async function getLevelOptions(
groupCode: string,
levelOrder: number,
parentValue?: string
): Promise<{
success: boolean;
data?: Array<{ value: string; label: string }>;
levelInfo?: {
levelId: number;
levelName: string;
placeholder: string;
isRequired: string;
isSearchable: string;
};
error?: string;
}> {
try {
const params = new URLSearchParams();
if (parentValue) params.append("parentValue", parentValue);
const response = await apiClient.get(
`/cascading-hierarchy/${groupCode}/options/${levelOrder}?${params.toString()}`
);
return response.data;
} catch (error: any) {
console.error("레벨 옵션 조회 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
// 편의를 위한 네임스페이스 export
export const hierarchyApi = {
getGroups: getHierarchyGroups,
getDetail: getHierarchyGroupDetail,
createGroup: createHierarchyGroup,
updateGroup: updateHierarchyGroup,
deleteGroup: deleteHierarchyGroup,
addLevel,
updateLevel,
deleteLevel,
getLevelOptions,
};
@@ -1,215 +0,0 @@
/**
* 상호 배제 (Mutual Exclusion) API 클라이언트
*/
import { apiClient } from "./client";
// =====================================================
// 타입 정의
// =====================================================
export interface MutualExclusion {
exclusionId?: number;
exclusionCode: string;
exclusionName: string;
fieldNames: string; // 콤마로 구분된 필드명 (예: "source_warehouse,target_warehouse")
sourceTable: string;
valueColumn: string;
labelColumn?: string;
exclusionType?: string; // "SAME_VALUE"
errorMessage?: string;
companyCode?: string;
isActive?: string;
createdDate?: string;
}
// 배제 타입 목록
export const EXCLUSION_TYPES = [
{ value: "SAME_VALUE", label: "동일 값 배제" },
{ value: "RELATED", label: "관련 값 배제 (예정)" },
];
// =====================================================
// API 함수
// =====================================================
/**
* 상호 배제 규칙 목록 조회
*/
export async function getExclusions(isActive?: string): Promise<{
success: boolean;
data?: MutualExclusion[];
error?: string;
}> {
try {
const params = new URLSearchParams();
if (isActive) params.append("isActive", isActive);
const response = await apiClient.get(`/cascading-exclusions?${params.toString()}`);
return response.data;
} catch (error: any) {
console.error("상호 배제 규칙 목록 조회 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 상호 배제 규칙 상세 조회
*/
export async function getExclusionDetail(exclusionId: number): Promise<{
success: boolean;
data?: MutualExclusion;
error?: string;
}> {
try {
const response = await apiClient.get(`/cascading-exclusions/${exclusionId}`);
return response.data;
} catch (error: any) {
console.error("상호 배제 규칙 상세 조회 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 상호 배제 규칙 생성
*/
export async function createExclusion(data: Omit<MutualExclusion, "exclusionId">): Promise<{
success: boolean;
data?: MutualExclusion;
message?: string;
error?: string;
}> {
try {
const response = await apiClient.post("/cascading-exclusions", data);
return response.data;
} catch (error: any) {
console.error("상호 배제 규칙 생성 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 상호 배제 규칙 수정
*/
export async function updateExclusion(
exclusionId: number,
data: Partial<MutualExclusion>
): Promise<{
success: boolean;
data?: MutualExclusion;
message?: string;
error?: string;
}> {
try {
const response = await apiClient.put(`/cascading-exclusions/${exclusionId}`, data);
return response.data;
} catch (error: any) {
console.error("상호 배제 규칙 수정 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 상호 배제 규칙 삭제
*/
export async function deleteExclusion(exclusionId: number): Promise<{
success: boolean;
message?: string;
error?: string;
}> {
try {
const response = await apiClient.delete(`/cascading-exclusions/${exclusionId}`);
return response.data;
} catch (error: any) {
console.error("상호 배제 규칙 삭제 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 상호 배제 검증
*/
export async function validateExclusion(
exclusionCode: string,
fieldValues: Record<string, string>
): Promise<{
success: boolean;
data?: {
isValid: boolean;
errorMessage: string | null;
conflictingFields: string[];
};
error?: string;
}> {
try {
const response = await apiClient.post(`/cascading-exclusions/validate/${exclusionCode}`, {
fieldValues,
});
return response.data;
} catch (error: any) {
console.error("상호 배제 검증 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 배제된 옵션 조회 (다른 필드에서 선택한 값 제외)
*/
export async function getExcludedOptions(
exclusionCode: string,
params: {
currentField?: string;
selectedValues?: string; // 콤마로 구분된 값들
}
): Promise<{
success: boolean;
data?: Array<{ value: string; label: string }>;
error?: string;
}> {
try {
const searchParams = new URLSearchParams();
if (params.currentField) searchParams.append("currentField", params.currentField);
if (params.selectedValues) searchParams.append("selectedValues", params.selectedValues);
const response = await apiClient.get(
`/cascading-exclusions/options/${exclusionCode}?${searchParams.toString()}`
);
return response.data;
} catch (error: any) {
console.error("배제된 옵션 조회 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
// 편의를 위한 네임스페이스 export
export const mutualExclusionApi = {
getList: getExclusions,
getDetail: getExclusionDetail,
create: createExclusion,
update: updateExclusion,
delete: deleteExclusion,
validate: validateExclusion,
getExcludedOptions,
};
-180
View File
@@ -1,180 +0,0 @@
import { apiClient } from "./client";
export interface CascadingRelation {
relation_id: number;
relation_code: string;
relation_name: string;
description?: string;
parent_table: string;
parent_value_column: string;
parent_label_column?: string;
child_table: string;
child_filter_column: string;
child_value_column: string;
child_label_column: string;
child_order_column?: string;
child_order_direction?: string;
empty_parent_message?: string;
no_options_message?: string;
loading_message?: string;
clear_on_parent_change?: string;
company_code: string;
is_active?: string;
created_by?: string;
created_date?: string;
updated_by?: string;
updated_date?: string;
}
export interface CascadingRelationCreateInput {
relationCode: string;
relationName: string;
description?: string;
parentTable: string;
parentValueColumn: string;
parentLabelColumn?: string;
childTable: string;
childFilterColumn: string;
childValueColumn: string;
childLabelColumn: string;
childOrderColumn?: string;
childOrderDirection?: string;
emptyParentMessage?: string;
noOptionsMessage?: string;
loadingMessage?: string;
clearOnParentChange?: boolean;
}
export interface CascadingRelationUpdateInput extends Partial<CascadingRelationCreateInput> {
isActive?: boolean;
}
export interface CascadingOption {
value: string;
label: string;
parent_value?: string; // 다중 부모 선택 시 어떤 부모에 속하는지 구분용
}
/**
* 연쇄 관계 목록 조회
*/
export const getCascadingRelations = async (isActive?: string) => {
try {
const params = new URLSearchParams();
if (isActive !== undefined) {
params.append("isActive", isActive);
}
const response = await apiClient.get(`/cascading-relations?${params.toString()}`);
return response.data;
} catch (error: any) {
console.error("연쇄 관계 목록 조회 실패:", error);
return { success: false, error: error.message };
}
};
/**
* 연쇄 관계 상세 조회 (ID)
*/
export const getCascadingRelationById = async (id: number) => {
try {
const response = await apiClient.get(`/cascading-relations/${id}`);
return response.data;
} catch (error: any) {
console.error("연쇄 관계 상세 조회 실패:", error);
return { success: false, error: error.message };
}
};
/**
* 연쇄 관계 코드로 조회
*/
export const getCascadingRelationByCode = async (code: string) => {
try {
const response = await apiClient.get(`/cascading-relations/code/${code}`);
return response.data;
} catch (error: any) {
console.error("연쇄 관계 코드 조회 실패:", error);
return { success: false, error: error.message };
}
};
/**
* 연쇄 관계로 자식 옵션 조회
* 단일 부모값 또는 다중 부모값 지원
*/
export const getCascadingOptions = async (
code: string,
parentValue: string | string[]
): Promise<{ success: boolean; data?: CascadingOption[]; error?: string }> => {
try {
let url: string;
if (Array.isArray(parentValue)) {
// 다중 부모값: parentValues 파라미터 사용
if (parentValue.length === 0) {
return { success: true, data: [] };
}
const parentValuesParam = parentValue.join(',');
url = `/cascading-relations/options/${code}?parentValues=${encodeURIComponent(parentValuesParam)}`;
} else {
// 단일 부모값: 기존 호환
url = `/cascading-relations/options/${code}?parentValue=${encodeURIComponent(parentValue)}`;
}
const response = await apiClient.get(url);
return response.data;
} catch (error: any) {
console.error("연쇄 옵션 조회 실패:", error);
return { success: false, error: error.message };
}
};
/**
* 연쇄 관계 생성
*/
export const createCascadingRelation = async (data: CascadingRelationCreateInput) => {
try {
const response = await apiClient.post("/cascading-relations", data);
return response.data;
} catch (error: any) {
console.error("연쇄 관계 생성 실패:", error);
return { success: false, error: error.message };
}
};
/**
* 연쇄 관계 수정
*/
export const updateCascadingRelation = async (id: number, data: CascadingRelationUpdateInput) => {
try {
const response = await apiClient.put(`/cascading-relations/${id}`, data);
return response.data;
} catch (error: any) {
console.error("연쇄 관계 수정 실패:", error);
return { success: false, error: error.message };
}
};
/**
* 연쇄 관계 삭제
*/
export const deleteCascadingRelation = async (id: number) => {
try {
const response = await apiClient.delete(`/cascading-relations/${id}`);
return response.data;
} catch (error: any) {
console.error("연쇄 관계 삭제 실패:", error);
return { success: false, error: error.message };
}
};
export const cascadingRelationApi = {
getList: getCascadingRelations,
getById: getCascadingRelationById,
getByCode: getCascadingRelationByCode,
getOptions: getCascadingOptions,
create: createCascadingRelation,
update: updateCascadingRelation,
delete: deleteCascadingRelation,
};
-226
View File
@@ -1,226 +0,0 @@
/**
* 카테고리 트리 API 클라이언트 (테스트용)
* - 트리 구조 CRUD 지원
*/
import { apiClient } from "./client";
// 카테고리 값 타입
export interface CategoryValue {
value_id: number;
table_name: string;
column_name: string;
value_code: string;
value_label: string;
value_order: number;
parent_value_id: number | null;
depth: number; // 1=대분류, 2=중분류, 3=소분류
path: string | null;
description: string | null;
color: string | null;
icon: string | null;
is_active: boolean;
is_default: boolean;
company_code: string;
created_at: string;
updated_at: string;
// 트리 구조용
children?: CategoryValue[];
}
// 카테고리 값 생성 입력
export interface CreateCategoryValueInput {
table_name: string;
column_name: string;
value_code: string;
value_label: string;
value_order?: number;
parent_value_id?: number | null;
description?: string;
color?: string;
icon?: string;
is_active?: boolean;
is_default?: boolean;
target_company_code?: string; // 저장할 회사 코드 (최고 관리자가 회사 선택 시)
}
// 카테고리 값 수정 입력
export interface UpdateCategoryValueInput {
value_code?: string;
value_label?: string;
value_order?: number;
parent_value_id?: number | null;
description?: string;
color?: string;
icon?: string;
is_active?: boolean;
is_default?: boolean;
}
// API 응답 타입
export interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
message?: string;
}
/**
* 카테고리 트리 조회
*/
export async function getCategoryTree(
tableName: string,
columnName: string
): Promise<ApiResponse<CategoryValue[]>> {
try {
const response = await apiClient.get(`/category-tree/test/${tableName}/${columnName}`);
return response.data;
} catch (error: unknown) {
const err = error as { response?: { data?: { error?: string } }; message?: string };
return {
success: false,
error: err.response?.data?.error || err.message || "카테고리 트리 조회 실패",
};
}
}
/**
* 카테고리 목록 조회 (플랫)
*/
export async function getCategoryList(
tableName: string,
columnName: string
): Promise<ApiResponse<CategoryValue[]>> {
try {
const response = await apiClient.get(`/category-tree/test/${tableName}/${columnName}/flat`);
return response.data;
} catch (error: unknown) {
const err = error as { response?: { data?: { error?: string } }; message?: string };
return {
success: false,
error: err.response?.data?.error || err.message || "카테고리 목록 조회 실패",
};
}
}
/**
* 카테고리 값 단일 조회
*/
export async function getCategoryValue(valueId: number): Promise<ApiResponse<CategoryValue>> {
try {
const response = await apiClient.get(`/category-tree/test/value/${valueId}`);
return response.data;
} catch (error: unknown) {
const err = error as { response?: { data?: { error?: string } }; message?: string };
return {
success: false,
error: err.response?.data?.error || err.message || "카테고리 값 조회 실패",
};
}
}
/**
* 카테고리 값 생성
*/
export async function createCategoryValue(
input: CreateCategoryValueInput
): Promise<ApiResponse<CategoryValue>> {
try {
const response = await apiClient.post("/category-tree/test/value", input);
return response.data;
} catch (error: unknown) {
const err = error as { response?: { data?: { error?: string } }; message?: string };
return {
success: false,
error: err.response?.data?.error || err.message || "카테고리 값 생성 실패",
};
}
}
/**
* 카테고리 값 수정
*/
export async function updateCategoryValue(
valueId: number,
input: UpdateCategoryValueInput
): Promise<ApiResponse<CategoryValue>> {
try {
const response = await apiClient.put(`/category-tree/test/value/${valueId}`, input);
return response.data;
} catch (error: unknown) {
const err = error as { response?: { data?: { error?: string } }; message?: string };
return {
success: false,
error: err.response?.data?.error || err.message || "카테고리 값 수정 실패",
};
}
}
/**
* 카테고리 값 삭제 가능 여부 사전 확인
*/
export async function checkCanDeleteCategoryValue(
valueId: number
): Promise<ApiResponse<{ canDelete: boolean; reason?: string }>> {
try {
const response = await apiClient.get(`/category-tree/test/value/${valueId}/can-delete`);
return response.data;
} catch (error: unknown) {
const err = error as { response?: { data?: { error?: string } }; message?: string };
return {
success: false,
error: err.response?.data?.error || err.message || "삭제 가능 여부 확인 실패",
};
}
}
/**
* 카테고리 값 삭제
*/
export async function deleteCategoryValue(valueId: number): Promise<ApiResponse<void>> {
try {
const response = await apiClient.delete(`/category-tree/test/value/${valueId}`);
return response.data;
} catch (error: unknown) {
const err = error as { response?: { data?: { error?: string } }; message?: string };
return {
success: false,
error: err.response?.data?.error || err.message || "카테고리 값 삭제 실패",
};
}
}
/**
* 테이블의 카테고리 컬럼 목록 조회
*/
export async function getCategoryColumns(
tableName: string
): Promise<ApiResponse<{ column_name: string; column_label: string }[]>> {
try {
const response = await apiClient.get(`/category-tree/test/columns/${tableName}`);
return response.data;
} catch (error: unknown) {
const err = error as { response?: { data?: { error?: string } }; message?: string };
return {
success: false,
error: err.response?.data?.error || err.message || "카테고리 컬럼 조회 실패",
};
}
}
/**
* 전체 카테고리 키 목록 조회 (모든 테이블.컬럼 조합)
*/
export async function getAllCategoryKeys(): Promise<ApiResponse<{ table_name: string; column_name: string; table_label?: string; column_label?: string }[]>> {
try {
const response = await apiClient.get("/category-tree/test/all-category-keys");
return response.data;
} catch (error: unknown) {
const err = error as { response?: { data?: { error?: string } }; message?: string };
return {
success: false,
error: err.response?.data?.error || err.message || "전체 카테고리 키 조회 실패",
};
}
}
-255
View File
@@ -1,255 +0,0 @@
import { apiClient } from "./client";
// ============================================
// 타입 정의
// ============================================
export interface CategoryValueCascadingGroup {
group_id: number;
relation_code: string;
relation_name: string;
description?: string;
parent_table_name: string;
parent_column_name: string;
parent_menu_objid?: number;
child_table_name: string;
child_column_name: string;
child_menu_objid?: number;
clear_on_parent_change?: string;
show_group_label?: string;
empty_parent_message?: string;
no_options_message?: string;
company_code: string;
is_active?: string;
created_by?: string;
created_date?: string;
updated_by?: string;
updated_date?: string;
// 상세 조회 시 포함
mappings?: CategoryValueCascadingMapping[];
mappingsByParent?: Record<string, { childValueCode: string; childValueLabel: string; displayOrder: number }[]>;
}
export interface CategoryValueCascadingMapping {
mapping_id?: number;
parent_value_code: string;
parent_value_label?: string;
child_value_code: string;
child_value_label?: string;
display_order?: number;
}
export interface CategoryValueCascadingGroupInput {
relationCode: string;
relationName: string;
description?: string;
parentTableName: string;
parentColumnName: string;
parentMenuObjid?: number;
childTableName: string;
childColumnName: string;
childMenuObjid?: number;
clearOnParentChange?: boolean;
showGroupLabel?: boolean;
emptyParentMessage?: string;
noOptionsMessage?: string;
}
export interface CategoryValueCascadingMappingInput {
parentValueCode: string;
parentValueLabel?: string;
childValueCode: string;
childValueLabel?: string;
displayOrder?: number;
}
export interface CategoryValueCascadingOption {
value: string;
label: string;
parent_value?: string;
parent_label?: string;
display_order?: number;
}
// ============================================
// API 함수
// ============================================
/**
* 카테고리 값 연쇄관계 그룹 목록 조회
*/
export const getCategoryValueCascadingGroups = async (isActive?: string) => {
try {
const params = new URLSearchParams();
if (isActive !== undefined) {
params.append("isActive", isActive);
}
const response = await apiClient.get(`/category-value-cascading/groups?${params.toString()}`);
return response.data;
} catch (error: any) {
console.error("카테고리 값 연쇄관계 그룹 목록 조회 실패:", error);
return { success: false, error: error.message };
}
};
/**
* 카테고리 값 연쇄관계 그룹 상세 조회
*/
export const getCategoryValueCascadingGroupById = async (groupId: number) => {
try {
const response = await apiClient.get(`/category-value-cascading/groups/${groupId}`);
return response.data;
} catch (error: any) {
console.error("카테고리 값 연쇄관계 그룹 상세 조회 실패:", error);
return { success: false, error: error.message };
}
};
/**
* 관계 코드로 조회
*/
export const getCategoryValueCascadingByCode = async (code: string) => {
try {
const response = await apiClient.get(`/category-value-cascading/code/${code}`);
return response.data;
} catch (error: any) {
console.error("카테고리 값 연쇄관계 코드 조회 실패:", error);
return { success: false, error: error.message };
}
};
/**
* 카테고리 값 연쇄관계 그룹 생성
*/
export const createCategoryValueCascadingGroup = async (data: CategoryValueCascadingGroupInput) => {
try {
const response = await apiClient.post("/category-value-cascading/groups", data);
return response.data;
} catch (error: any) {
console.error("카테고리 값 연쇄관계 그룹 생성 실패:", error);
return { success: false, error: error.message };
}
};
/**
* 카테고리 값 연쇄관계 그룹 수정
*/
export const updateCategoryValueCascadingGroup = async (
groupId: number,
data: Partial<CategoryValueCascadingGroupInput> & { isActive?: boolean }
) => {
try {
const response = await apiClient.put(`/category-value-cascading/groups/${groupId}`, data);
return response.data;
} catch (error: any) {
console.error("카테고리 값 연쇄관계 그룹 수정 실패:", error);
return { success: false, error: error.message };
}
};
/**
* 카테고리 값 연쇄관계 그룹 삭제
*/
export const deleteCategoryValueCascadingGroup = async (groupId: number) => {
try {
const response = await apiClient.delete(`/category-value-cascading/groups/${groupId}`);
return response.data;
} catch (error: any) {
console.error("카테고리 값 연쇄관계 그룹 삭제 실패:", error);
return { success: false, error: error.message };
}
};
/**
* 매핑 일괄 저장
*/
export const saveCategoryValueCascadingMappings = async (
groupId: number,
mappings: CategoryValueCascadingMappingInput[]
) => {
try {
const response = await apiClient.post(`/category-value-cascading/groups/${groupId}/mappings`, { mappings });
return response.data;
} catch (error: any) {
console.error("카테고리 값 연쇄관계 매핑 저장 실패:", error);
return { success: false, error: error.message };
}
};
/**
* 연쇄 옵션 조회 (실제 드롭다운에서 사용)
* 다중 부모값 지원
*/
export const getCategoryValueCascadingOptions = async (
code: string,
parentValue: string | string[]
): Promise<{ success: boolean; data?: CategoryValueCascadingOption[]; showGroupLabel?: boolean; error?: string }> => {
try {
let url: string;
if (Array.isArray(parentValue)) {
if (parentValue.length === 0) {
return { success: true, data: [] };
}
const parentValuesParam = parentValue.join(',');
url = `/category-value-cascading/options/${code}?parentValues=${encodeURIComponent(parentValuesParam)}`;
} else {
url = `/category-value-cascading/options/${code}?parentValue=${encodeURIComponent(parentValue)}`;
}
const response = await apiClient.get(url);
return response.data;
} catch (error: any) {
console.error("카테고리 값 연쇄 옵션 조회 실패:", error);
return { success: false, error: error.message };
}
};
/**
* 부모 카테고리 값 목록 조회
*/
export const getCategoryValueCascadingParentOptions = async (code: string) => {
try {
const response = await apiClient.get(`/category-value-cascading/parent-options/${code}`);
return response.data;
} catch (error: any) {
console.error("부모 카테고리 값 조회 실패:", error);
return { success: false, error: error.message };
}
};
/**
* 자식 카테고리 값 목록 조회 (매핑 설정 UI용)
*/
export const getCategoryValueCascadingChildOptions = async (code: string) => {
try {
const response = await apiClient.get(`/category-value-cascading/child-options/${code}`);
return response.data;
} catch (error: any) {
console.error("자식 카테고리 값 조회 실패:", error);
return { success: false, error: error.message };
}
};
// ============================================
// API 객체 export
// ============================================
export const categoryValueCascadingApi = {
// 그룹 CRUD
getGroups: getCategoryValueCascadingGroups,
getGroupById: getCategoryValueCascadingGroupById,
getByCode: getCategoryValueCascadingByCode,
createGroup: createCategoryValueCascadingGroup,
updateGroup: updateCategoryValueCascadingGroup,
deleteGroup: deleteCategoryValueCascadingGroup,
// 매핑
saveMappings: saveCategoryValueCascadingMappings,
// 옵션 조회
getOptions: getCategoryValueCascadingOptions,
getParentOptions: getCategoryValueCascadingParentOptions,
getChildOptions: getCategoryValueCascadingChildOptions,
};
+41 -119
View File
@@ -1,8 +1,13 @@
/**
* 코드 관리 API
* 코드 관리 API (호환 레이어)
*
* 2026-05-15: 옛 카테고리 API 가 폐기되고 마스터-디테일(code_info/code_detail) 로 재작성됨.
* 이 파일은 화면관리/플로우 등에서 호출하는 기존 헬퍼 시그니처를 유지하기 위한 얇은 어댑터.
*
* 새 코드는 `lib/api/commonCode.ts` 의 `getCodeDetailTree` 등을 직접 사용 권장.
*/
import { apiClient } from "./client";
import { getCodeDetailTree, getCodeInfoList } from "./commonCode";
export interface CodeItem {
code: string;
@@ -12,144 +17,70 @@ export interface CodeItem {
useYn: string;
}
export interface CodeCategory {
export interface CodeInfo {
categoryCode: string;
categoryName: string;
description?: string;
useYn: string;
}
/**
* 코드 카테고리 목록 조회
*/
export const getCodeCategories = async (): Promise<CodeCategory[]> => {
/** 그룹 목록 조회 — 옛 시그니처 유지 */
export const getCodeCategories = async (): Promise<CodeInfo[]> => {
try {
// 올바른 API 엔드포인트 사용 (apiClient 사용)
const response = await apiClient.get("/common-codes/categories");
const data = response.data;
// 응답 데이터 구조에 맞게 변환
const categories = data.data || [];
return categories.map((category: any) => ({
categoryCode: category.categoryCode,
categoryName: category.categoryName,
description: category.description,
useYn: category.isActive ? "Y" : "N",
const response = await getCodeInfoList({ is_active: true });
const rows = response.data || [];
return rows.map((row: any) => ({
categoryCode: row.code_info,
categoryName: row.code_name || row.code_info,
description: row.description,
useYn: row.is_active === "Y" ? "Y" : "N",
}));
} catch (error) {
console.error("코드 카테고리 조회 실패:", error);
// API 호출 실패 시 빈 배열 반환
console.warn("코드 카테고리 API 호출 실패 - 빈 배열 반환");
console.error("코드 그룹 조회 실패:", error);
return [];
}
};
/**
* 특정 카테고리의 코드 목록 조회
*/
/** 그룹의 디테일 목록 조회 — 옛 시그니처 유지 (활성만) */
export const getCodesByCategory = async (categoryCode: string): Promise<CodeItem[]> => {
if (!categoryCode || categoryCode === "none") return [];
try {
// API URL 디버깅
const apiUrl = `/common-codes/categories/${encodeURIComponent(categoryCode)}/codes`;
console.log("🔗 코드 API 호출 URL:", {
categoryCode,
apiUrl,
currentURL: typeof window !== "undefined" ? window.location.href : "서버사이드",
baseURL: typeof window !== "undefined" ? `${window.location.protocol}//${window.location.host}` : "서버사이드",
apiClientBaseURL: apiClient.defaults.baseURL,
});
console.log("📡 실제 요청 URL:", `${apiClient.defaults.baseURL}${apiUrl}`);
// 올바른 API 엔드포인트 사용 (apiClient 사용)
const response = await apiClient.get(apiUrl);
const data = response.data;
console.log("🔍 백엔드 응답 데이터:", {
fullResponse: data,
dataArray: data.data,
firstItem: data.data && data.data[0] ? data.data[0] : "없음",
dataType: typeof data.data,
isArray: Array.isArray(data.data),
});
// 응답 데이터 구조에 맞게 변환
const codes = data.data || [];
const mappedCodes = codes.map((code: any) => {
console.log("🔄 코드 매핑:", {
original: code,
mapped: {
code: code.codeValue || code.code || code.id || code.value,
name: code.codeName || code.name || code.label || code.description,
description: code.description || code.codeName || code.name,
orderNo: code.sortOrder || code.orderNo || code.order,
useYn: code.isActive ? "Y" : code.useYn || "Y",
},
});
return {
code: code.codeValue || code.code || code.id || code.value,
name: code.codeName || code.name || code.label || code.description,
description: code.description || code.codeName || code.name,
orderNo: code.sortOrder || code.orderNo || code.order,
useYn: code.isActive ? "Y" : code.useYn || "Y",
};
});
console.log("📋 최종 매핑된 코드들:", mappedCodes);
return mappedCodes;
const response = await getCodeDetailTree({ code_info: categoryCode, is_active: true });
const rows = response.data || [];
return rows.map((row: any) => ({
code: row.code_value,
name: row.code_name || row.code_value,
description: row.description || undefined,
orderNo: row.sort_order ?? undefined,
useYn: row.is_active === "Y" ? "Y" : "N",
}));
} catch (error: any) {
console.error("코드 목록 조회 실패:", error);
// 인증 오류인 경우 명시적으로 알림
if (error.response?.status === 401) {
console.warn("🔐 인증이 필요합니다. 로그인 후 다시 시도하세요.");
} else if (error.response?.status === 404) {
console.warn(`📭 코드 카테고리 '${categoryCode}'가 존재하지 않습니다.`);
} else {
console.warn(`❌ 코드 카테고리 '${categoryCode}'에 대한 API 호출 실패:`, error.message);
}
console.error(`코드 그룹 '${categoryCode}' 디테일 조회 실패:`, error?.message || error);
return [];
}
};
/**
* 컬럼의 웹타입과 코드 카테고리 정보 기반으로 코드 조회
* 컬럼 메타(webType / codeInfo) 기반으로 코드 조회
*/
export const getCodesForColumn = async (
columnName: string,
webType?: string,
codeCategory?: string,
codeInfo?: string,
): Promise<CodeItem[]> => {
// 코드 타입이 아니면 빈 배열 반환
if (webType !== "code" && !codeCategory) {
return [];
}
if (webType !== "code" && !codeInfo) return [];
if (codeInfo) return getCodesByCategory(codeInfo);
// 코드 카테고리가 있으면 해당 카테고리의 코드 조회
if (codeCategory) {
return await getCodesByCategory(codeCategory);
}
// 컬럼명에서 코드 카테고리 추론 (예: status_code -> STATUS)
const inferredCategory = inferCodeCategoryFromColumnName(columnName);
if (inferredCategory) {
return await getCodesByCategory(inferredCategory);
}
return [];
const inferred = inferCodeInfoFromColumnName(columnName);
return inferred ? getCodesByCategory(inferred) : [];
};
/**
* 컬럼명에서 코드 카테고리 추론
* 컬럼명에서 그룹 코드를 추론 (legacy fallback)
*/
const inferCodeCategoryFromColumnName = (columnName: string): string | null => {
const lowerName = columnName.toLowerCase();
// 일반적인 패턴들
const patterns = [
const inferCodeInfoFromColumnName = (columnName: string): string | null => {
const lower = columnName.toLowerCase();
const patterns: Array<{ pattern: RegExp; category: string }> = [
{ pattern: /status/i, category: "STATUS" },
{ pattern: /state/i, category: "STATE" },
{ pattern: /type/i, category: "TYPE" },
@@ -159,17 +90,8 @@ const inferCodeCategoryFromColumnName = (columnName: string): string | null => {
{ pattern: /priority/i, category: "PRIORITY" },
{ pattern: /role/i, category: "ROLE" },
];
for (const { pattern, category } of patterns) {
if (pattern.test(lowerName)) {
return category;
}
if (pattern.test(lower)) return category;
}
return null;
};
/**
* 실제 데이터베이스에 있는 코드 카테고리만 사용
* Mock 데이터는 더 이상 사용하지 않음
*/
+184 -225
View File
@@ -1,243 +1,202 @@
import { apiClient } from "./client";
import {
CodeCategory,
import type {
CodeInfo,
CodeOption,
CreateCategoryRequest,
UpdateCategoryRequest,
CreateCodeRequest,
UpdateCodeRequest,
GetCategoriesQuery,
GetCodesQuery,
CodeDetail,
ApiResponse,
GetCodeInfoListQuery,
GetCodeDetailListQuery,
DuplicateCheckResult,
} from "@/types/commonCode";
/**
* camelCase 키를 snake_case로 변환하는 헬퍼
* 공통코드 API 클라이언트 (마스터-디테일 구조)
*
* code_info — 그룹 마스터
* code_detail — 트리 노드 (depth 2 시작, 무한 depth)
*
* 백엔드 계약 (BE Agent 가 만드는 것):
* /api/common-codes/info/...
* /api/common-codes/detail/...
*
* 응답 포맷:
* 목록: { success, data: [...], total: N, message }
* 단건/CRUD: { success, data: {...}, message }
*/
function toSnakeCase(obj: Record<string, any>): Record<string, any> {
const result: Record<string, any> = {};
for (const key of Object.keys(obj)) {
const snakeKey = key.replace(/([A-Z])/g, "_$1").toLowerCase();
result[snakeKey] = obj[key];
/* ───────────────────────── code_info (그룹 마스터) ───────────────────────── */
/** 그룹 목록 (페이징/검색) */
export async function getCodeInfoList(
params?: GetCodeInfoListQuery,
): Promise<ApiResponse<CodeInfo[]>> {
const search = new URLSearchParams();
if (params?.search) search.append("search", params.search);
if (params?.is_active !== undefined) search.append("is_active", params.is_active.toString());
if (params?.page !== undefined) search.append("page", params.page.toString());
if (params?.size !== undefined) search.append("size", params.size.toString());
const qs = search.toString();
const url = `/common-codes/info${qs ? `?${qs}` : ""}`;
const response = await apiClient.get(url);
return response.data;
}
/** 그룹 단건 */
export async function getCodeInfoInfo(codeInfo: string): Promise<ApiResponse<CodeInfo>> {
const response = await apiClient.get(`/common-codes/info/${encodeURIComponent(codeInfo)}`);
return response.data;
}
/** 그룹 생성 */
export async function createCodeInfo(data: Record<string, any>): Promise<ApiResponse<CodeInfo>> {
const response = await apiClient.post("/common-codes/info", data);
return response.data;
}
/** 그룹 수정 */
export async function updateCodeInfo(
codeInfo: string,
data: Record<string, any>,
): Promise<ApiResponse<CodeInfo>> {
const response = await apiClient.put(
`/common-codes/info/${encodeURIComponent(codeInfo)}`,
data,
);
return response.data;
}
/** 그룹 삭제 (CASCADE) */
export async function deleteCodeInfo(codeInfo: string): Promise<ApiResponse> {
const response = await apiClient.delete(`/common-codes/info/${encodeURIComponent(codeInfo)}`);
return response.data;
}
/** 그룹 중복 체크 */
export async function checkCodeInfoDuplicate(
field: "code_info" | "code_name" | "code_name_eng",
value: string,
excludeCode?: string,
): Promise<ApiResponse<DuplicateCheckResult>> {
const search = new URLSearchParams();
search.append("field", field);
search.append("value", value);
if (excludeCode) search.append("excludeCode", excludeCode);
const response = await apiClient.get(`/common-codes/info/check-duplicate?${search}`);
return response.data;
}
/* ───────────────────────── code_detail (디테일 트리) ───────────────────────── */
/** 그룹의 전체 디테일 트리 (depth+sort_order 평탄화 리스트) */
export async function getCodeDetailTree(
params: GetCodeDetailListQuery,
): Promise<ApiResponse<CodeDetail[]>> {
const search = new URLSearchParams();
search.append("code_info", params.code_info);
if (params.search) search.append("search", params.search);
if (params.is_active !== undefined) search.append("is_active", params.is_active.toString());
const response = await apiClient.get(`/common-codes/detail?${search}`);
return response.data;
}
/** 디테일 단건 */
export async function getCodeDetailInfo(
codeDetailId: number | string,
): Promise<ApiResponse<CodeDetail>> {
const response = await apiClient.get(`/common-codes/detail/${codeDetailId}`);
return response.data;
}
/** 디테일 생성 */
export async function createCodeDetail(
data: Record<string, any>,
): Promise<ApiResponse<CodeDetail>> {
const response = await apiClient.post("/common-codes/detail", data);
return response.data;
}
/** 디테일 수정 */
export async function updateCodeDetail(
codeDetailId: number | string,
data: Record<string, any>,
): Promise<ApiResponse<CodeDetail>> {
const response = await apiClient.put(`/common-codes/detail/${codeDetailId}`, data);
return response.data;
}
/** 디테일 삭제 (CASCADE) */
export async function deleteCodeDetail(codeDetailId: number | string): Promise<ApiResponse> {
const response = await apiClient.delete(`/common-codes/detail/${codeDetailId}`);
return response.data;
}
/** 디테일 중복 체크 (그룹 + 필드 단위) */
export async function checkCodeDetailDuplicate(
codeInfo: string,
field: "code_value" | "code_name" | "code_name_eng",
value: string,
excludeId?: number | string,
): Promise<ApiResponse<DuplicateCheckResult>> {
const search = new URLSearchParams();
search.append("code_info", codeInfo);
search.append("field", field);
search.append("value", value);
if (excludeId !== undefined && excludeId !== null && excludeId !== "") {
search.append("excludeId", String(excludeId));
}
return result;
const response = await apiClient.get(`/common-codes/detail/check-duplicate?${search}`);
return response.data;
}
/* ───────────────────────── compat: 화면관리/캐시에서 사용 ───────────────────────── */
/**
* 그룹의 활성 디테일 옵션 (label/value)
* 기존 `commonCodeApi.options.getOptions(categoryCode)` 의 대체.
*/
export async function getCodeOptions(
codeInfo: string,
): Promise<ApiResponse<Array<{ value: string; label: string; depth?: number; parent_detail_id?: number | null }>>> {
const tree = await getCodeDetailTree({ code_info: codeInfo, is_active: true });
const options = (tree.data || []).map((row) => ({
value: row.code_value,
label: row.code_name || row.code_value,
depth: row.depth,
parent_detail_id: row.parent_detail_id ?? null,
}));
return {
success: tree.success,
data: options,
message: tree.message,
total: options.length,
};
}
/**
* 공통코드 관리 API 클라이언트
* 호환 객체. 기존 코드가 `commonCodeApi.codes.getList(category, ...)` 등으로 부르고 있어
* 깨끗한 1안 마이그레이션 동안 임시로 노출. 새 코드는 위 함수들을 직접 import.
*
* NOTE: 사용처가 점진적으로 제거되면 이 객체도 삭제.
*/
export const commonCodeApi = {
// 카테고리 관련 API
categories: {
/**
* 카테고리 목록 조회
*/
async getList(params?: GetCategoriesQuery): Promise<ApiResponse<CodeCategory[]>> {
const searchParams = new URLSearchParams();
if (params?.search) searchParams.append("search", params.search);
if (params?.is_active !== undefined) searchParams.append("isActive", params.is_active.toString());
if (params?.page) searchParams.append("page", params.page.toString());
if (params?.size) searchParams.append("size", params.size.toString());
const queryString = searchParams.toString();
const url = `/common-codes/categories${queryString ? `?${queryString}` : ""}`;
const response = await apiClient.get(url);
return response.data;
},
/**
* 카테고리 생성
* Zod 스키마가 camelCase를 사용하므로 백엔드가 기대하는 snake_case로 변환
*/
async create(data: CreateCategoryRequest): Promise<ApiResponse<CodeCategory>> {
const response = await apiClient.post("/common-codes/categories", toSnakeCase(data));
return response.data;
},
/**
* 카테고리 수정
* Zod 스키마가 camelCase를 사용하므로 백엔드가 기대하는 snake_case로 변환
*/
async update(categoryCode: string, data: UpdateCategoryRequest): Promise<ApiResponse<CodeCategory>> {
const response = await apiClient.put(`/common-codes/categories/${categoryCode}`, toSnakeCase(data));
return response.data;
},
/**
* 카테고리 삭제
*/
async delete(categoryCode: string): Promise<ApiResponse> {
const response = await apiClient.delete(`/common-codes/categories/${categoryCode}`);
return response.data;
},
info: {
getList: getCodeInfoList,
get: getCodeInfoInfo,
create: createCodeInfo,
update: updateCodeInfo,
delete: deleteCodeInfo,
checkDuplicate: checkCodeInfoDuplicate,
},
// 코드 관련 API
codes: {
/**
* 카테고리별 코드 목록 조회
*/
async getList(categoryCode: string, params?: GetCodesQuery & { menuObjid?: number }): Promise<ApiResponse<CodeInfo[]>> {
const searchParams = new URLSearchParams();
if (params?.search) searchParams.append("search", params.search);
if (params?.is_active !== undefined) searchParams.append("isActive", params.is_active.toString());
if (params?.page !== undefined) searchParams.append("page", params.page.toString());
if (params?.size !== undefined) searchParams.append("size", params.size.toString());
if (params?.menuObjid !== undefined) searchParams.append("menuObjid", params.menuObjid.toString());
const queryString = searchParams.toString();
const url = `/common-codes/categories/${categoryCode}/codes${queryString ? `?${queryString}` : ""}`;
const response = await apiClient.get(url);
return response.data;
},
/**
* 코드 생성
* Zod 스키마가 camelCase를 사용하므로 백엔드가 기대하는 snake_case로 변환
*/
async create(categoryCode: string, data: CreateCodeRequest): Promise<ApiResponse<CodeInfo>> {
const response = await apiClient.post(`/common-codes/categories/${categoryCode}/codes`, toSnakeCase(data));
return response.data;
},
/**
* 코드 수정
* Zod 스키마가 camelCase를 사용하므로 백엔드가 기대하는 snake_case로 변환
*/
async update(categoryCode: string, codeValue: string, data: UpdateCodeRequest): Promise<ApiResponse<CodeInfo>> {
const response = await apiClient.put(`/common-codes/categories/${categoryCode}/codes/${codeValue}`, toSnakeCase(data));
return response.data;
},
/**
* 코드 삭제
*/
async delete(categoryCode: string, codeValue: string): Promise<ApiResponse> {
const response = await apiClient.delete(`/common-codes/categories/${categoryCode}/codes/${codeValue}`);
return response.data;
},
/**
* 코드 순서 변경
*/
async reorder(categoryCode: string, codes: Array<{ codeValue: string; sortOrder: number }>): Promise<ApiResponse> {
const data = { codes }; // 백엔드가 기대하는 형식으로 래핑
const response = await apiClient.put(`/common-codes/categories/${categoryCode}/codes/reorder`, data);
return response.data;
},
detail: {
getTree: getCodeDetailTree,
get: getCodeDetailInfo,
create: createCodeDetail,
update: updateCodeDetail,
delete: deleteCodeDetail,
checkDuplicate: checkCodeDetailDuplicate,
},
// 중복 검사 API
validation: {
/**
* 카테고리 중복 검사
*/
async checkCategoryDuplicate(
field: "categoryCode" | "categoryName" | "categoryNameEng",
value: string,
excludeCode?: string,
): Promise<ApiResponse<{ isDuplicate: boolean; message: string; field: string; value: string }>> {
const params = new URLSearchParams();
params.append("field", field);
params.append("value", value);
if (excludeCode) params.append("excludeCode", excludeCode);
const response = await apiClient.get(`/common-codes/categories/check-duplicate?${params}`);
return response.data;
},
/**
* 코드 중복 검사
*/
async checkCodeDuplicate(
categoryCode: string,
field: "codeValue" | "codeName" | "codeNameEng",
value: string,
excludeCode?: string,
): Promise<
ApiResponse<{ isDuplicate: boolean; message: string; categoryCode: string; field: string; value: string }>
> {
const params = new URLSearchParams();
params.append("field", field);
params.append("value", value);
if (excludeCode) params.append("excludeCode", excludeCode);
const response = await apiClient.get(`/common-codes/categories/${categoryCode}/codes/check-duplicate?${params}`);
return response.data;
},
},
// 옵션 조회 API (화면관리용)
options: {
/**
* 카테고리별 옵션 조회
*/
async getOptions(categoryCode: string): Promise<ApiResponse<CodeOption[]>> {
const response = await apiClient.get(`/common-codes/categories/${categoryCode}/options`);
return response.data;
},
},
// 계층구조 코드 API
hierarchy: {
/**
* 계층구조 코드 조회
* @param categoryCode 카테고리 코드
* @param parentCodeValue 부모 코드값 (빈 문자열이면 최상위 코드)
* @param depth 특정 깊이만 조회 (선택)
* @param menuObjid 메뉴 OBJID (선택)
*/
async getHierarchicalCodes(
categoryCode: string,
parentCodeValue?: string | null,
depth?: number,
menuObjid?: number
): Promise<ApiResponse<CodeInfo[]>> {
const searchParams = new URLSearchParams();
if (parentCodeValue !== undefined && parentCodeValue !== null) {
searchParams.append("parentCodeValue", parentCodeValue);
}
if (depth !== undefined) searchParams.append("depth", depth.toString());
if (menuObjid !== undefined) searchParams.append("menuObjid", menuObjid.toString());
const queryString = searchParams.toString();
const url = `/common-codes/categories/${categoryCode}/hierarchy${queryString ? `?${queryString}` : ""}`;
const response = await apiClient.get(url);
return response.data;
},
/**
* 코드 트리 조회 (전체 계층구조)
*/
async getCodeTree(
categoryCode: string,
menuObjid?: number
): Promise<ApiResponse<{ flat: CodeInfo[]; tree: CodeInfo[] }>> {
const searchParams = new URLSearchParams();
if (menuObjid !== undefined) searchParams.append("menuObjid", menuObjid.toString());
const queryString = searchParams.toString();
const url = `/common-codes/categories/${categoryCode}/tree${queryString ? `?${queryString}` : ""}`;
const response = await apiClient.get(url);
return response.data;
},
/**
* 자식 코드 존재 여부 확인
*/
async hasChildren(categoryCode: string, codeValue: string): Promise<ApiResponse<{ hasChildren: boolean }>> {
const response = await apiClient.get(
`/common-codes/categories/${categoryCode}/codes/${codeValue}/has-children`
);
return response.data;
},
getOptions: getCodeOptions,
},
};
+1 -1
View File
@@ -73,7 +73,7 @@ export interface ColumnInfo {
numericPrecision?: number;
numericScale?: number;
detailSettings?: string;
codeCategory?: string;
codeInfo?: string;
referenceTable?: string;
referenceColumn?: string;
isVisible?: string;
+3 -3
View File
@@ -16,7 +16,7 @@ export interface EntityReferenceData {
export interface CodeReferenceData {
options: EntityReferenceOption[];
codeCategory: string;
codeInfo: string;
}
export interface ApiResponse<T> {
@@ -63,7 +63,7 @@ export class EntityReferenceAPI {
* 공통 코드 데이터 조회
*/
static async getCodeReferenceData(
codeCategory: string,
codeInfo: string,
options: {
limit?: number;
search?: string;
@@ -75,7 +75,7 @@ export class EntityReferenceAPI {
if (options.search) params.append("search", options.search);
const queryString = params.toString();
const url = `/entity-reference/code/${codeCategory}${queryString ? `?${queryString}` : ""}`;
const url = `/entity-reference/code/${codeInfo}${queryString ? `?${queryString}` : ""}`;
const response = await apiClient.get<ApiResponse<CodeReferenceData>>(url);
+1 -1
View File
@@ -200,7 +200,7 @@ export const menuApi = {
addPrefix?: string;
},
additionalCopyOptions?: {
copyCodeCategory?: boolean;
copyCodeInfo?: boolean;
copyNumberingRules?: boolean;
copyCategoryMapping?: boolean;
copyTableTypeColumns?: boolean;
+4 -4
View File
@@ -70,7 +70,7 @@ const mapDataTypeToWebType = (dataType: string | undefined | null): string => {
* 컬럼명으로부터 코드 카테고리를 추론
* 실제 존재하는 카테고리만 반환하도록 개선
*/
const inferCodeCategory = (columnName: string): string => {
const inferCodeInfo = (columnName: string): string => {
const lowerName = columnName.toLowerCase();
// 실제 데이터베이스에 존재하는 것으로 확인된 카테고리만 반환
@@ -204,7 +204,7 @@ export const getColumnsFromConnection = async (connectionId: number, tableName:
...col,
columnName: columnName,
web_type: isCodeColumn ? "code" : col.web_type || mapDataTypeToWebType(col.data_type),
codeCategory: isCodeColumn ? inferCodeCategory(columnName) : col.code_category,
codeInfo: isCodeColumn ? inferCodeInfo(columnName) : col.code_info,
};
})
: columns;
@@ -253,7 +253,7 @@ export const getColumnsFromConnection = async (connectionId: number, tableName:
columnDefault: col.column_default,
description: col.column_comment || col.description,
// 코드 타입인 경우 카테고리 추론
codeCategory: isCodeColumn ? inferCodeCategory(columnName) : undefined,
codeInfo: isCodeColumn ? inferCodeInfo(columnName) : undefined,
};
})
: rawResult;
@@ -320,7 +320,7 @@ const getMockColumnsForTable = (tableName: string): ColumnInfo[] => {
isNullable: true,
isPrimaryKey: false,
columnComment: "상태 코드",
codeCategory: "STATUS",
codeInfo: "STATUS",
},
{
columnName: "created_date",
+18
View File
@@ -172,6 +172,24 @@ export async function resetSequence(ruleId: string): Promise<ApiResponse<void>>
}
}
/**
* 현재 시퀀스 임의 값으로 수정 (admin)
* backend `PUT /api/numbering-rules/:ruleId/sequence` body={sequence}
*/
export async function updateRuleSequence(
ruleId: string,
newSequence: number,
): Promise<ApiResponse<void>> {
try {
const response = await apiClient.put(`/numbering-rules/${ruleId}/sequence`, {
sequence: newSequence,
});
return response.data;
} catch (error: any) {
return { success: false, error: error.message || "시퀀스 수정 실패" };
}
}
// ====== 테스트용 API (numbering_rules 테이블 사용) ======
/**
+160
View File
@@ -0,0 +1,160 @@
import { apiClient } from "./client";
import type { OptionFilter } from "@/lib/registry/components/input/use-option-loader";
export type StatsAggregation =
| "count"
| "sum"
| "avg"
| "min"
| "max"
| "distinctCount";
/**
* AggregateRequest — 백엔드 `/api/table-management/tables/{tableName}/aggregate` body.
*
* filters 는 `OptionFilter[]` 와 동일한 모양으로 보낸다. 단, `value_type` / `field_ref` /
* `user_field` 는 호출자가 미리 실제 값으로 치환해서 `value` 만 남긴 상태여야 한다 — 백엔드는
* 그 외 필드를 무시한다.
*/
export interface AggregateRequest {
aggregation: StatsAggregation;
columnName?: string;
filters?: Array<Pick<OptionFilter, "column" | "operator" | "value">>;
}
export interface AggregateResponse {
value: number;
}
/**
* aggregateTableStat — 단일 stat 카드 값을 백엔드에서 계산해 가져온다.
*
* 디자인 모드 / 빈 tableName 가드는 호출자가 책임진다.
* 실패 시 axios error 가 그대로 throw 됨 — 상위 hook 에서 카드 단위 fallback.
*/
export async function aggregateTableStat(
tableName: string,
request: AggregateRequest,
): Promise<AggregateResponse> {
const res = await apiClient.post(
`/table-management/tables/${encodeURIComponent(tableName)}/aggregate`,
request,
);
const body = res.data;
// ApiResponse<{ value: number }> wrapper. value 는 number 또는 numeric string 가능.
const payload = (body?.data ?? body) as { value?: unknown };
const raw = payload?.value;
const value =
typeof raw === "number"
? raw
: typeof raw === "string" && raw.trim() !== ""
? Number(raw)
: 0;
return { value: Number.isFinite(value) ? value : 0 };
}
/**
* AggregateGroupRequest — Phase G.3 canonical chart 컴포넌트가 사용하는
* `/aggregate-group` body.
*
* 백엔드가 `GROUP BY <groupBy>` 후 각 그룹마다 단일 집계 값을 계산해 row 배열로 반환.
* `valueColumn` 는 `aggregation === "count"` 일 때 생략 가능. distinctCount / sum /
* avg / min / max 는 valueColumn 필수 (백엔드 측 가드).
*/
export interface AggregateGroupRequest {
aggregation: StatsAggregation;
groupBy: string;
valueColumn?: string;
filters?: Array<Pick<OptionFilter, "column" | "operator" | "value">>;
limit?: number;
orderDir?: "asc" | "desc";
}
export interface AggregateGroupRow {
/** GROUP BY 컬럼의 raw 값. null 가능. */
group: string | number | null;
/** 집계 결과 (숫자). */
value: number;
}
export interface AggregateGroupResponse {
rows: AggregateGroupRow[];
}
/**
* aggregateTableGroup — 그룹별 집계.
*
* 호출자는 디자인 모드 / 빈 tableName / 빈 groupBy 가드를 책임진다. 실패는 axios
* error 로 throw 된다.
*/
export async function aggregateTableGroup(
tableName: string,
request: AggregateGroupRequest,
): Promise<AggregateGroupResponse> {
const res = await apiClient.post(
`/table-management/tables/${encodeURIComponent(tableName)}/aggregate-group`,
request,
);
const body = res.data;
const payload = (body?.data ?? body) as { rows?: unknown };
const rawRows = Array.isArray(payload?.rows) ? (payload!.rows as any[]) : [];
const rows: AggregateGroupRow[] = rawRows.map((r) => {
const v = r?.value;
const num =
typeof v === "number"
? v
: typeof v === "string" && v.trim() !== ""
? Number(v)
: 0;
return {
group: r?.group ?? null,
value: Number.isFinite(num) ? num : 0,
};
});
return { rows };
}
/**
* SelectRowsRequest — Phase G.3.1 canonical cardList / groupedTable 가 사용하는
* `/select-rows` body.
*
* 단순 SELECT — 필터 / 정렬 / limit 만 적용해서 raw row 들을 받는다. column 단일
* 집계가 아니라 multi-column row 가 필요한 view 컴포넌트용.
*/
export interface SelectRowsOrderBy {
column: string;
direction?: "asc" | "desc";
}
export interface SelectRowsRequest {
columns?: string[];
filters?: Array<Pick<OptionFilter, "column" | "operator" | "value">>;
orderBy?: SelectRowsOrderBy[];
limit?: number;
offset?: number;
}
export interface SelectRowsResponse {
rows: Record<string, any>[];
}
/**
* selectTableRows — multi-column row 들을 받아오는 가벼운 SELECT.
*
* 호출자는 디자인 모드 / 빈 tableName 가드를 책임진다. 실패는 axios error 로 throw.
*/
export async function selectTableRows(
tableName: string,
request: SelectRowsRequest,
): Promise<SelectRowsResponse> {
const res = await apiClient.post(
`/table-management/tables/${encodeURIComponent(tableName)}/select-rows`,
request,
);
const body = res.data;
const payload = (body?.data ?? body) as { rows?: unknown };
const rawRows = Array.isArray(payload?.rows)
? (payload!.rows as Record<string, any>[])
: [];
return { rows: rawRows };
}
-331
View File
@@ -1,331 +0,0 @@
import { apiClient } from "./client";
import {
TableCategoryValue,
CategoryColumn,
} from "@/types/tableCategoryValue";
/**
* 테이블의 카테고리 컬럼 목록 조회
*/
export async function getCategoryColumns(tableName: string) {
try {
const response = await apiClient.get<{
success: boolean;
data: CategoryColumn[];
}>(`/table-categories/${tableName}/columns`);
return response.data;
} catch (error: any) {
console.error("카테고리 컬럼 조회 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 메뉴별 카테고리 컬럼 목록 조회
*
* @param menuObjid 메뉴 OBJID
* @returns 해당 메뉴와 상위 메뉴들이 설정한 모든 카테고리 컬럼
*/
export async function getCategoryColumnsByMenu(menuObjid: number) {
try {
const response = await apiClient.get<{
success: boolean;
data: CategoryColumn[];
}>(`/table-management/menu/${menuObjid}/category-columns`);
return response.data;
} catch (error: any) {
console.error("메뉴별 카테고리 컬럼 조회 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 카테고리 값 목록 조회 (메뉴 스코프)
*
* @param tableName 테이블명
* @param columnName 컬럼명
* @param includeInactive 비활성 값 포함 여부
* @param menuObjid 메뉴 OBJID (선택사항, 제공 시 형제 메뉴의 카테고리 값 포함)
*/
export async function getCategoryValues(
tableName: string,
columnName: string,
includeInactive: boolean = false,
menuObjid?: number
) {
try {
const params: any = { includeInactive };
if (menuObjid) {
params.menuObjid = menuObjid;
}
const response = await apiClient.get<{
success: boolean;
data: TableCategoryValue[];
}>(`/table-categories/${tableName}/${columnName}/values`, {
params,
});
return response.data;
} catch (error: any) {
console.error("카테고리 값 조회 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 카테고리 값 추가 (메뉴 스코프)
*
* @param value 카테고리 값 정보
* @param menuObjid 메뉴 OBJID (필수)
*/
export async function addCategoryValue(
value: TableCategoryValue,
menuObjid: number
) {
try {
const response = await apiClient.post<{
success: boolean;
data: TableCategoryValue;
}>("/table-categories/values", {
...value,
menuObjid, // ← menuObjid 포함
});
return response.data;
} catch (error: any) {
console.error("카테고리 값 추가 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 카테고리 값 수정
*/
export async function updateCategoryValue(
valueId: number,
updates: Partial<TableCategoryValue>
) {
try {
const response = await apiClient.put<{
success: boolean;
data: TableCategoryValue;
}>(`/table-categories/values/${valueId}`, updates);
return response.data;
} catch (error: any) {
console.error("카테고리 값 수정 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 카테고리 값 삭제
*/
export async function deleteCategoryValue(valueId: number) {
try {
const response = await apiClient.delete<{
success: boolean;
message: string;
}>(`/table-categories/values/${valueId}`);
return response.data;
} catch (error: any) {
console.error("카테고리 값 삭제 실패:", error);
// 백엔드에서 반환한 에러 메시지 전달
const errorMessage = error.response?.data?.message || error.message;
return { success: false, error: errorMessage, message: errorMessage };
}
}
/**
* 카테고리 값 일괄 삭제
*/
export async function bulkDeleteCategoryValues(valueIds: number[]) {
try {
const response = await apiClient.post<{
success: boolean;
message: string;
}>("/table-categories/values/bulk-delete", { valueIds });
return response.data;
} catch (error: any) {
console.error("카테고리 값 일괄 삭제 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 카테고리 값 순서 변경
*/
export async function reorderCategoryValues(orderedValueIds: number[]) {
try {
const response = await apiClient.post<{
success: boolean;
message: string;
}>("/table-categories/values/reorder", { orderedValueIds });
return response.data;
} catch (error: any) {
console.error("카테고리 값 순서 변경 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 카테고리 코드로 라벨 조회
*
* @param valueCodes - 카테고리 코드 배열 (예: ["CATEGORY_767659DCUF", "CATEGORY_8292565608"])
* @returns { [code]: label } 형태의 매핑 객체
*/
export async function getCategoryLabelsByCodes(valueCodes: string[]) {
try {
if (!valueCodes || valueCodes.length === 0) {
return { success: true, data: {} };
}
const response = await apiClient.post<{
success: boolean;
data: Record<string, string>;
}>("/table-categories/labels-by-codes", { valueCodes });
return response.data;
} catch (error: any) {
console.error("카테고리 라벨 조회 실패:", error);
return { success: false, error: error.message, data: {} };
}
}
// ================================================
// 컬럼 매핑 관련 API (논리명 ↔ 물리명)
// ================================================
/**
* 컬럼 매핑 조회
*
* @param tableName - 테이블명
* @param menuObjid - 메뉴 OBJID
* @returns { logical_column: physical_column } 형태의 매핑 객체
*/
export async function getColumnMapping(tableName: string, menuObjid: number) {
try {
const response = await apiClient.get<{
success: boolean;
data: Record<string, string>;
}>(`/table-categories/column-mapping/${tableName}/${menuObjid}`);
return response.data;
} catch (error: any) {
console.error("컬럼 매핑 조회 실패:", error);
return { success: false, error: error.message, data: {} };
}
}
/**
* 논리적 컬럼 목록 조회
*
* @param tableName - 테이블명
* @param menuObjid - 메뉴 OBJID
* @returns 논리적 컬럼 목록
*/
export async function getLogicalColumns(tableName: string, menuObjid: number) {
try {
const response = await apiClient.get<{
success: boolean;
data: Array<{
mappingId: number;
logicalColumnName: string;
physicalColumnName: string;
description?: string;
}>;
}>(`/table-categories/logical-columns/${tableName}/${menuObjid}`);
return response.data;
} catch (error: any) {
console.error("논리적 컬럼 목록 조회 실패:", error);
return { success: false, error: error.message, data: [] };
}
}
/**
* 컬럼 매핑 생성/수정
*
* @param data - 컬럼 매핑 정보
*/
export async function createColumnMapping(data: {
tableName: string;
logicalColumnName: string;
physicalColumnName: string;
menuObjid: number;
description?: string;
}) {
try {
const response = await apiClient.post<{
success: boolean;
data: any;
message: string;
}>("/table-categories/column-mapping", data);
return response.data;
} catch (error: any) {
console.error("컬럼 매핑 생성 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 컬럼 매핑 삭제
*
* @param mappingId - 매핑 ID
*/
export async function deleteColumnMapping(mappingId: number) {
try {
const response = await apiClient.delete<{
success: boolean;
message: string;
}>(`/table-categories/column-mapping/${mappingId}`);
return response.data;
} catch (error: any) {
console.error("컬럼 매핑 삭제 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 테이블+컬럼 기준으로 모든 매핑 삭제
*
* 메뉴 선택 변경 시 기존 매핑을 모두 삭제하고 새로운 매핑만 추가하기 위해 사용
*
* @param tableName - 테이블명
* @param columnName - 컬럼명
*/
export async function deleteColumnMappingsByColumn(tableName: string, columnName: string) {
try {
const response = await apiClient.delete<{
success: boolean;
message: string;
deletedCount: number;
}>(`/table-categories/column-mapping/${tableName}/${columnName}/all`);
return response.data;
} catch (error: any) {
console.error("테이블+컬럼 기준 매핑 삭제 실패:", error);
return { success: false, error: error.message, deletedCount: 0 };
}
}
/**
* 2레벨 메뉴 목록 조회
*
* 카테고리 컬럼 매핑 생성 시 메뉴 선택용
*
* @returns 2레벨 메뉴 목록
*/
export async function getSecondLevelMenus() {
try {
const response = await apiClient.get<{
success: boolean;
data: Array<{
menu_objid: number;
menu_name: string;
parent_menu_name: string;
screen_code?: string;
}>;
}>("/table-categories/second-level-menus");
return response.data;
} catch (error: any) {
console.error("2레벨 메뉴 목록 조회 실패:", error);
return { success: false, error: error.message, data: [] };
}
}
+2 -2
View File
@@ -22,7 +22,7 @@ export interface ColumnTypeInfo {
max_length?: number;
numeric_precision?: number;
numeric_scale?: number;
code_category?: string;
code_info?: string;
code_value?: string;
reference_table?: string;
reference_column?: string;
@@ -45,7 +45,7 @@ export interface ColumnSettings {
columnLabel: string;
inputType: string; // 백엔드에서 inputType으로 받음
detailSettings: string;
codeCategory: string;
codeInfo: string;
codeValue: string;
referenceTable: string;
referenceColumn: string;