This commit is contained in:
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 || "전체 카테고리 키 조회 실패",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -73,7 +73,7 @@ export interface ColumnInfo {
|
||||
numericPrecision?: number;
|
||||
numericScale?: number;
|
||||
detailSettings?: string;
|
||||
codeCategory?: string;
|
||||
codeInfo?: string;
|
||||
referenceTable?: string;
|
||||
referenceColumn?: string;
|
||||
isVisible?: string;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ export const menuApi = {
|
||||
addPrefix?: string;
|
||||
},
|
||||
additionalCopyOptions?: {
|
||||
copyCodeCategory?: boolean;
|
||||
copyCodeInfo?: boolean;
|
||||
copyNumberingRules?: boolean;
|
||||
copyCategoryMapping?: boolean;
|
||||
copyTableTypeColumns?: boolean;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 테이블 사용) ======
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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: [] };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user