다국어 키 자동생성 로직

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

View File

@@ -696,6 +696,21 @@ export class MultiLangService {
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)
if (params.searchText) {
whereConditions.push(
@@ -717,12 +732,13 @@ export class MultiLangService {
lang_key: string;
description: string | null;
is_active: string | null;
category_id: number | null;
created_date: Date | null;
created_by: string | null;
updated_date: Date | 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
FROM multi_lang_key_master
${whereClause}
@@ -737,6 +753,7 @@ export class MultiLangService {
langKey: key.lang_key,
description: key.description || undefined,
isActive: key.is_active || "Y",
categoryId: key.category_id || undefined,
createdDate: key.created_date || undefined,
createdBy: key.created_by || undefined,
updatedDate: key.updated_date || undefined,

View File

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

View File

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

View File

@@ -1460,44 +1460,102 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
try {
// 모든 컴포넌트에서 라벨 정보 추출
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[]) => {
components.forEach((comp) => {
const anyComp = comp as any;
const config = anyComp.componentConfig;
// 라벨 추출
if (anyComp.label && typeof anyComp.label === "string" && anyComp.label.trim()) {
labels.push({
componentId: comp.id,
label: anyComp.label.trim(),
type: "label",
});
// 1. 기본 라벨 추출
if (anyComp.label && typeof anyComp.label === "string") {
addLabel(comp.id, anyComp.label, "label");
}
// 제목 추출 (컨테이너, 카드 등)
if (anyComp.title && typeof anyComp.title === "string" && anyComp.title.trim()) {
labels.push({
componentId: comp.id,
label: anyComp.title.trim(),
type: "title",
});
// 2. 제목 추출 (컨테이너, 카드 등)
if (anyComp.title && typeof anyComp.title === "string") {
addLabel(comp.id, anyComp.title, "title");
}
// 버튼 텍스트 추출
if (anyComp.componentConfig?.text && typeof anyComp.componentConfig.text === "string") {
labels.push({
componentId: comp.id,
label: anyComp.componentConfig.text.trim(),
type: "button",
});
// 3. 버튼 텍스트 추출
if (config?.text && typeof config.text === "string") {
addLabel(comp.id, config.text, "button");
}
// placeholder 추출
if (anyComp.placeholder && typeof anyComp.placeholder === "string" && anyComp.placeholder.trim()) {
labels.push({
componentId: comp.id,
label: anyComp.placeholder.trim(),
type: "placeholder",
// 4. placeholder 추출
if (anyComp.placeholder && typeof anyComp.placeholder === "string") {
addLabel(comp.id, anyComp.placeholder, "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");
}
});
}