Files
invyone/frontend/lib/api/multilang.ts
T
hjjeong e16fb16987 어드민 cross-tenant 집계 (SUPER_ADMIN) + 사용자관리 자체 스크롤
SUPER_ADMIN 토큰(company_code=*)이면 등록 회사들 DB 를 순회해 결과를
집계해 돌려주는 CrossTenantAggregator/Controller 추가. 사용자/권한그룹/
배치/다국어 키 4개 도메인의 list API 가 cross-tenant 모드 지원.

UserTable + ResponsiveDataView 에 compact/scrollContainer prop 추가.
페이지 헤더/툴바/페이지네이션은 고정, 테이블만 자체 스크롤.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:52:30 +09:00

427 lines
9.9 KiB
TypeScript

/**
* 다국어 관리 API 클라이언트
* 카테고리, 키 자동 생성, 오버라이드 등 확장 기능 포함
*/
import { apiClient } from "./client";
import { isCrossTenantMode } from "@/lib/auth/crossTenantMode";
// =====================================================
// 타입 정의
// =====================================================
export interface Language {
lang_code: string;
lang_name: string;
lang_native: string;
is_active: string;
sort_order?: number;
}
export interface LangCategory {
category_id: number;
category_code: string;
category_name: string;
parent_id?: number | null;
level: number;
key_prefix: string;
description?: string;
sort_order: number;
is_active: string;
children?: LangCategory[];
}
export interface LangKey {
keyId?: number;
companyCode: string;
menuName?: string;
langKey: string;
description?: string;
isActive: string;
categoryId?: number;
keyMeaning?: string;
usageNote?: string;
baseKeyId?: number;
createdDate?: Date;
}
export interface LangText {
textId?: number;
keyId: number;
langCode: string;
langText: string;
isActive: string;
}
export interface GenerateKeyRequest {
companyCode: string;
categoryId: number;
keyMeaning: string;
usageNote?: string;
texts: Array<{
langCode: string;
langText: string;
}>;
}
export interface CreateOverrideKeyRequest {
companyCode: string;
baseKeyId: number;
texts: Array<{
langCode: string;
langText: string;
}>;
}
export interface KeyPreview {
langKey: string;
exists: boolean;
isOverride: boolean;
baseKeyId?: number;
}
export interface ApiResponse<T> {
success: boolean;
message?: string;
data?: T;
error?: {
code: string;
details?: any;
};
}
// =====================================================
// 카테고리 관련 API
// =====================================================
/**
* 카테고리 트리 조회
*/
export async function getCategories(): Promise<ApiResponse<LangCategory[]>> {
try {
const response = await apiClient.get("/multilang/categories");
return response.data;
} catch (error: any) {
return {
success: false,
error: {
code: "CATEGORY_FETCH_ERROR",
details: error.message,
},
};
}
}
/**
* 카테고리 상세 조회
*/
export async function getCategoryById(categoryId: number): Promise<ApiResponse<LangCategory>> {
try {
const response = await apiClient.get(`/multilang/categories/${categoryId}`);
return response.data;
} catch (error: any) {
return {
success: false,
error: {
code: "CATEGORY_FETCH_ERROR",
details: error.message,
},
};
}
}
/**
* 카테고리 경로 조회 (부모 포함)
*/
export async function getCategoryPath(categoryId: number): Promise<ApiResponse<LangCategory[]>> {
try {
const response = await apiClient.get(`/multilang/categories/${categoryId}/path`);
return response.data;
} catch (error: any) {
return {
success: false,
error: {
code: "CATEGORY_PATH_ERROR",
details: error.message,
},
};
}
}
// =====================================================
// 언어 관련 API
// =====================================================
/**
* 언어 목록 조회
*/
export async function getLanguages(): Promise<ApiResponse<Language[]>> {
try {
const response = await apiClient.get("/multilang/languages");
return response.data;
} catch (error: any) {
return {
success: false,
error: {
code: "LANGUAGE_FETCH_ERROR",
details: error.message,
},
};
}
}
// =====================================================
// 키 관련 API
// =====================================================
/**
* 다국어 키 목록 조회
*
* 분기:
* - cross-tenant 모드 → /admin/cross-tenant/lang-keys
* 응답 행마다 company_code 박혀있어 화면에서 회사 컬럼/필터 가능.
* 1차 구현은 categoryId 재귀 필터 비지원 (필요해지면 후속 추가).
* - 단일 회사 모드 → /multilang/keys (기존)
*/
export async function getLangKeys(params?: {
company_code?: string;
menuCode?: string;
categoryId?: number;
searchText?: string;
}): Promise<ApiResponse<LangKey[]>> {
try {
if (isCrossTenantMode()) {
const ctParams = new URLSearchParams();
// cross-tenant mapper 의 파라미터명 (snake_case) 으로 매핑
if (params?.menuCode) ctParams.append("menu_code", params.menuCode);
if (params?.searchText) ctParams.append("search", params.searchText);
const url = `/admin/cross-tenant/lang-keys${ctParams.toString() ? `?${ctParams.toString()}` : ""}`;
const ctResponse = await apiClient.get(url);
const ct = ctResponse.data;
if (ct && ct.success && ct.data) {
return {
success: true,
data: (ct.data.rows || []) as LangKey[],
} as ApiResponse<LangKey[]>;
}
}
const queryParams = new URLSearchParams();
if (params?.company_code) queryParams.append("companyCode", params.company_code);
if (params?.menuCode) queryParams.append("menuCode", params.menuCode);
if (params?.categoryId) queryParams.append("categoryId", params.categoryId.toString());
if (params?.searchText) queryParams.append("searchText", params.searchText);
const url = `/multilang/keys${queryParams.toString() ? `?${queryParams.toString()}` : ""}`;
const response = await apiClient.get(url);
return response.data;
} catch (error: any) {
return {
success: false,
error: {
code: "KEYS_FETCH_ERROR",
details: error.message,
},
};
}
}
/**
* 키의 텍스트 조회
*/
export async function getLangTexts(keyId: number): Promise<ApiResponse<LangText[]>> {
try {
const response = await apiClient.get(`/multilang/keys/${keyId}/texts`);
return response.data;
} catch (error: any) {
return {
success: false,
error: {
code: "TEXTS_FETCH_ERROR",
details: error.message,
},
};
}
}
/**
* 키 자동 생성
*/
export async function generateKey(data: GenerateKeyRequest): Promise<ApiResponse<number>> {
try {
const response = await apiClient.post("/multilang/keys/generate", data);
return response.data;
} catch (error: any) {
return {
success: false,
error: {
code: "KEY_GENERATE_ERROR",
details: error.response?.data?.error?.details || error.message,
},
};
}
}
/**
* 키 미리보기
*/
export async function previewKey(
categoryId: number,
keyMeaning: string,
companyCode: string
): Promise<ApiResponse<KeyPreview>> {
try {
const response = await apiClient.post("/multilang/keys/preview", {
categoryId,
keyMeaning,
companyCode,
});
return response.data;
} catch (error: any) {
return {
success: false,
error: {
code: "KEY_PREVIEW_ERROR",
details: error.message,
},
};
}
}
/**
* 오버라이드 키 생성
*/
export async function createOverrideKey(
data: CreateOverrideKeyRequest
): Promise<ApiResponse<number>> {
try {
const response = await apiClient.post("/multilang/keys/override", data);
return response.data;
} catch (error: any) {
return {
success: false,
error: {
code: "OVERRIDE_CREATE_ERROR",
details: error.response?.data?.error?.details || error.message,
},
};
}
}
/**
* 회사별 오버라이드 키 목록 조회
*/
export async function getOverrideKeys(companyCode: string): Promise<ApiResponse<LangKey[]>> {
try {
const response = await apiClient.get(`/multilang/keys/overrides/${companyCode}`);
return response.data;
} catch (error: any) {
return {
success: false,
error: {
code: "OVERRIDE_KEYS_FETCH_ERROR",
details: error.message,
},
};
}
}
/**
* 키 텍스트 저장
*/
export async function saveLangTexts(
keyId: number,
texts: Array<{ langCode: string; langText: string }>
): Promise<ApiResponse<string>> {
try {
const response = await apiClient.post(`/multilang/keys/${keyId}/texts`, { texts });
return response.data;
} catch (error: any) {
return {
success: false,
error: {
code: "TEXTS_SAVE_ERROR",
details: error.message,
},
};
}
}
/**
* 키 삭제
*/
export async function deleteLangKey(keyId: number): Promise<ApiResponse<string>> {
try {
const response = await apiClient.delete(`/multilang/keys/${keyId}`);
return response.data;
} catch (error: any) {
return {
success: false,
error: {
code: "KEY_DELETE_ERROR",
details: error.message,
},
};
}
}
/**
* 키 상태 토글
*/
export async function toggleLangKey(keyId: number): Promise<ApiResponse<string>> {
try {
const response = await apiClient.put(`/multilang/keys/${keyId}/toggle`);
return response.data;
} catch (error: any) {
return {
success: false,
error: {
code: "KEY_TOGGLE_ERROR",
details: error.message,
},
};
}
}
// =====================================================
// 화면 라벨 다국어 자동 생성 API
// =====================================================
export interface ScreenLabelKeyResult {
componentId: string;
keyId: number;
langKey: string;
}
export interface GenerateScreenLabelKeysRequest {
screenId: number;
menuObjId?: string;
labels: Array<{
componentId: string;
label: string;
type?: string;
}>;
}
/**
* 화면 라벨 다국어 키 자동 생성
*/
export async function generateScreenLabelKeys(
params: GenerateScreenLabelKeysRequest
): Promise<ApiResponse<ScreenLabelKeyResult[]>> {
try {
const response = await apiClient.post("/multilang/screen-labels", params);
return response.data;
} catch (error: any) {
return {
success: false,
error: {
code: "SCREEN_LABEL_KEY_GENERATION_ERROR",
details: error.message,
},
};
}
}