다국어 키 자동생성 로직
This commit is contained in:
parent
61a7f585b4
commit
24315215de
|
|
@ -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[]> = {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue