다국어 키 자동생성 로직

This commit is contained in:
kjs 2026-01-14 11:05:57 +09:00
parent 61a7f585b4
commit 24315215de
5 changed files with 123 additions and 33 deletions

View File

@ -190,7 +190,7 @@ export const getLangKeys = async (
res: Response res: Response
): Promise<void> => { ): Promise<void> => {
try { try {
const { companyCode, menuCode, keyType, searchText } = req.query; const { companyCode, menuCode, keyType, searchText, categoryId } = req.query;
logger.info("다국어 키 목록 조회 요청", { logger.info("다국어 키 목록 조회 요청", {
query: req.query, query: req.query,
user: req.user, user: req.user,
@ -202,6 +202,7 @@ export const getLangKeys = async (
menuCode: menuCode as string, menuCode: menuCode as string,
keyType: keyType as string, keyType: keyType as string,
searchText: searchText as string, searchText: searchText as string,
categoryId: categoryId ? parseInt(categoryId as string, 10) : undefined,
}); });
const response: ApiResponse<any[]> = { const response: ApiResponse<any[]> = {

View File

@ -696,6 +696,21 @@ export class MultiLangService {
values.push(params.menuCode); values.push(params.menuCode);
} }
// 카테고리 필터 (하위 카테고리 포함)
if (params.categoryId) {
whereConditions.push(`category_id IN (
WITH RECURSIVE category_tree AS (
SELECT category_id FROM multi_lang_category WHERE category_id = $${paramIndex}
UNION ALL
SELECT c.category_id FROM multi_lang_category c
INNER JOIN category_tree ct ON c.parent_id = ct.category_id
)
SELECT category_id FROM category_tree
)`);
values.push(params.categoryId);
paramIndex++;
}
// 검색 조건 (OR) // 검색 조건 (OR)
if (params.searchText) { if (params.searchText) {
whereConditions.push( whereConditions.push(
@ -717,12 +732,13 @@ export class MultiLangService {
lang_key: string; lang_key: string;
description: string | null; description: string | null;
is_active: string | null; is_active: string | null;
category_id: number | null;
created_date: Date | null; created_date: Date | null;
created_by: string | null; created_by: string | null;
updated_date: Date | null; updated_date: Date | null;
updated_by: string | null; updated_by: string | null;
}>( }>(
`SELECT key_id, company_code, usage_note, lang_key, description, is_active, `SELECT key_id, company_code, usage_note, lang_key, description, is_active, category_id,
created_date, created_by, updated_date, updated_by created_date, created_by, updated_date, updated_by
FROM multi_lang_key_master FROM multi_lang_key_master
${whereClause} ${whereClause}
@ -737,6 +753,7 @@ export class MultiLangService {
langKey: key.lang_key, langKey: key.lang_key,
description: key.description || undefined, description: key.description || undefined,
isActive: key.is_active || "Y", isActive: key.is_active || "Y",
categoryId: key.category_id || undefined,
createdDate: key.created_date || undefined, createdDate: key.created_date || undefined,
createdBy: key.created_by || undefined, createdBy: key.created_by || undefined,
updatedDate: key.updated_date || undefined, updatedDate: key.updated_date || undefined,

View File

@ -35,6 +35,7 @@ interface LangKey {
langKey: string; langKey: string;
description: string; description: string;
isActive: string; isActive: string;
categoryId?: number;
} }
interface LangText { interface LangText {
@ -102,9 +103,14 @@ export default function I18nPage() {
}; };
// 다국어 키 목록 조회 // 다국어 키 목록 조회
const fetchLangKeys = async () => { const fetchLangKeys = async (categoryId?: number | null) => {
try { try {
const response = await apiClient.get("/multilang/keys"); const params = new URLSearchParams();
if (categoryId) {
params.append("categoryId", categoryId.toString());
}
const url = `/multilang/keys${params.toString() ? `?${params.toString()}` : ""}`;
const response = await apiClient.get(url);
const data = response.data; const data = response.data;
if (data.success) { if (data.success) {
setLangKeys(data.data); setLangKeys(data.data);
@ -481,6 +487,13 @@ export default function I18nPage() {
initializeData(); initializeData();
}, []); }, []);
// 카테고리 변경 시 키 목록 다시 조회
useEffect(() => {
if (!loading) {
fetchLangKeys(selectedCategory?.categoryId);
}
}, [selectedCategory?.categoryId]);
const columns = [ const columns = [
{ {
id: "select", id: "select",
@ -879,7 +892,7 @@ export default function I18nPage() {
companyCode={user?.companyCode || ""} companyCode={user?.companyCode || ""}
isSuperAdmin={user?.companyCode === "*"} isSuperAdmin={user?.companyCode === "*"}
onSuccess={() => { onSuccess={() => {
fetchLangKeys(); fetchLangKeys(selectedCategory?.categoryId);
}} }}
/> />
</div> </div>

View File

@ -26,7 +26,8 @@ function CategoryNode({
onSelectCategory, onSelectCategory,
onDoubleClickCategory, onDoubleClickCategory,
}: CategoryNodeProps) { }: CategoryNodeProps) {
const [isExpanded, setIsExpanded] = useState(true); // 기본값: 접힌 상태로 시작
const [isExpanded, setIsExpanded] = useState(false);
const hasChildren = category.children && category.children.length > 0; const hasChildren = category.children && category.children.length > 0;
const isSelected = selectedCategoryId === category.categoryId; const isSelected = selectedCategoryId === category.categoryId;

View File

@ -1460,44 +1460,102 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
try { try {
// 모든 컴포넌트에서 라벨 정보 추출 // 모든 컴포넌트에서 라벨 정보 추출
const labels: Array<{ componentId: string; label: string; type?: string }> = []; const labels: Array<{ componentId: string; label: string; type?: string }> = [];
const addedLabels = new Set<string>(); // 중복 방지
const addLabel = (componentId: string, label: string, type: string) => {
const key = `${label}_${type}`;
if (label && label.trim() && !addedLabels.has(key)) {
addedLabels.add(key);
labels.push({ componentId, label: label.trim(), type });
}
};
const extractLabels = (components: ComponentData[]) => { const extractLabels = (components: ComponentData[]) => {
components.forEach((comp) => { components.forEach((comp) => {
const anyComp = comp as any; const anyComp = comp as any;
const config = anyComp.componentConfig;
// 라벨 추출 // 1. 기본 라벨 추출
if (anyComp.label && typeof anyComp.label === "string" && anyComp.label.trim()) { if (anyComp.label && typeof anyComp.label === "string") {
labels.push({ addLabel(comp.id, anyComp.label, "label");
componentId: comp.id,
label: anyComp.label.trim(),
type: "label",
});
} }
// 제목 추출 (컨테이너, 카드 등) // 2. 제목 추출 (컨테이너, 카드 등)
if (anyComp.title && typeof anyComp.title === "string" && anyComp.title.trim()) { if (anyComp.title && typeof anyComp.title === "string") {
labels.push({ addLabel(comp.id, anyComp.title, "title");
componentId: comp.id,
label: anyComp.title.trim(),
type: "title",
});
} }
// 버튼 텍스트 추출 // 3. 버튼 텍스트 추출
if (anyComp.componentConfig?.text && typeof anyComp.componentConfig.text === "string") { if (config?.text && typeof config.text === "string") {
labels.push({ addLabel(comp.id, config.text, "button");
componentId: comp.id,
label: anyComp.componentConfig.text.trim(),
type: "button",
});
} }
// placeholder 추출 // 4. placeholder 추출
if (anyComp.placeholder && typeof anyComp.placeholder === "string" && anyComp.placeholder.trim()) { if (anyComp.placeholder && typeof anyComp.placeholder === "string") {
labels.push({ addLabel(comp.id, anyComp.placeholder, "placeholder");
componentId: comp.id, }
label: anyComp.placeholder.trim(),
type: "placeholder", // 5. 테이블 컬럼 헤더 추출 (table-list, split-panel-layout 등)
if (config?.columns && Array.isArray(config.columns)) {
config.columns.forEach((col: any, index: number) => {
if (col.displayName && typeof col.displayName === "string") {
addLabel(`${comp.id}_col_${index}`, col.displayName, "column");
}
});
}
// 6. 분할 패널 - 좌측/우측 패널 컬럼 추출
if (config?.leftPanel?.columns && Array.isArray(config.leftPanel.columns)) {
config.leftPanel.columns.forEach((col: any, index: number) => {
if (col.displayName && typeof col.displayName === "string") {
addLabel(`${comp.id}_left_col_${index}`, col.displayName, "column");
}
});
}
if (config?.rightPanel?.columns && Array.isArray(config.rightPanel.columns)) {
config.rightPanel.columns.forEach((col: any, index: number) => {
if (col.displayName && typeof col.displayName === "string") {
addLabel(`${comp.id}_right_col_${index}`, col.displayName, "column");
}
});
}
// 7. 검색 필터 필드 추출
if (config?.filter?.filters && Array.isArray(config.filter.filters)) {
config.filter.filters.forEach((filter: any, index: number) => {
if (filter.label && typeof filter.label === "string") {
addLabel(`${comp.id}_filter_${index}`, filter.label, "filter");
}
});
}
// 8. 폼 필드 라벨 추출 (input-form 등)
if (config?.fields && Array.isArray(config.fields)) {
config.fields.forEach((field: any, index: number) => {
if (field.label && typeof field.label === "string") {
addLabel(`${comp.id}_field_${index}`, field.label, "field");
}
if (field.placeholder && typeof field.placeholder === "string") {
addLabel(`${comp.id}_field_ph_${index}`, field.placeholder, "placeholder");
}
});
}
// 9. 탭 라벨 추출
if (config?.tabs && Array.isArray(config.tabs)) {
config.tabs.forEach((tab: any, index: number) => {
if (tab.label && typeof tab.label === "string") {
addLabel(`${comp.id}_tab_${index}`, tab.label, "tab");
}
});
}
// 10. 액션 버튼 추출
if (config?.actions?.actions && Array.isArray(config.actions.actions)) {
config.actions.actions.forEach((action: any, index: number) => {
if (action.label && typeof action.label === "string") {
addLabel(`${comp.id}_action_${index}`, action.label, "action");
}
}); });
} }