This commit is contained in:
parent
832e80cd7f
commit
786576bb76
|
|
@ -22,7 +22,7 @@ import {
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { useMenu } from "@/contexts/MenuContext";
|
import { useMenu } from "@/contexts/MenuContext";
|
||||||
import { useMenuManagementText, setTranslationCache } from "@/lib/utils/multilang";
|
import { useMenuManagementText, setTranslationCache, getMenuTextSync } from "@/lib/utils/multilang";
|
||||||
import { useMultiLang } from "@/hooks/useMultiLang";
|
import { useMultiLang } from "@/hooks/useMultiLang";
|
||||||
import { apiClient } from "@/lib/api/client";
|
import { apiClient } from "@/lib/api/client";
|
||||||
|
|
||||||
|
|
@ -545,9 +545,9 @@ export const MenuManagement: React.FC = () => {
|
||||||
// uiTexts에서 번역 텍스트 찾기
|
// uiTexts에서 번역 텍스트 찾기
|
||||||
let text = uiTexts[key];
|
let text = uiTexts[key];
|
||||||
|
|
||||||
// uiTexts에 없으면 fallback 또는 키 사용
|
// uiTexts에 없으면 getMenuTextSync로 기본 한글 텍스트 가져오기
|
||||||
if (!text) {
|
if (!text) {
|
||||||
text = fallback || key;
|
text = getMenuTextSync(key, userLang) || fallback || key;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 파라미터 치환
|
// 파라미터 치환
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { Badge } from "@/components/ui/badge";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
import { MENU_MANAGEMENT_KEYS } from "@/lib/utils/multilang";
|
import { MENU_MANAGEMENT_KEYS, getMenuTextSync } from "@/lib/utils/multilang";
|
||||||
|
|
||||||
interface MenuTableProps {
|
interface MenuTableProps {
|
||||||
menus: MenuItem[];
|
menus: MenuItem[];
|
||||||
|
|
@ -39,7 +39,8 @@ export const MenuTable: React.FC<MenuTableProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
// 다국어 텍스트 가져오기 함수
|
// 다국어 텍스트 가져오기 함수
|
||||||
const getText = (key: string, fallback?: string): string => {
|
const getText = (key: string, fallback?: string): string => {
|
||||||
return uiTexts[key] || fallback || key;
|
// uiTexts에서 먼저 찾고, 없으면 기본 한글 텍스트를 가져옴
|
||||||
|
return uiTexts[key] || getMenuTextSync(key, "KR") || fallback || key;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 다국어 텍스트 표시 함수 (기본값 처리)
|
// 다국어 텍스트 표시 함수 (기본값 처리)
|
||||||
|
|
|
||||||
|
|
@ -49,25 +49,33 @@ export const CategoryValueManager: React.FC<CategoryValueManagerProps> = ({
|
||||||
const [editingValue, setEditingValue] = useState<TableCategoryValue | null>(
|
const [editingValue, setEditingValue] = useState<TableCategoryValue | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
const [showInactive, setShowInactive] = useState(false); // 비활성 항목 표시 옵션 (기본: 숨김)
|
||||||
|
|
||||||
// 카테고리 값 로드
|
// 카테고리 값 로드
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadCategoryValues();
|
loadCategoryValues();
|
||||||
}, [tableName, columnName]);
|
}, [tableName, columnName]);
|
||||||
|
|
||||||
// 검색 필터링
|
// 검색 필터링 + 비활성 필터링
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let filtered = values;
|
||||||
|
|
||||||
|
// 비활성 항목 필터링 (기본: 활성만 표시, 체크하면 비활성도 표시)
|
||||||
|
if (!showInactive) {
|
||||||
|
filtered = filtered.filter((v) => v.isActive !== false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 검색어 필터링
|
||||||
if (searchQuery) {
|
if (searchQuery) {
|
||||||
const filtered = values.filter(
|
filtered = filtered.filter(
|
||||||
(v) =>
|
(v) =>
|
||||||
v.valueCode.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
v.valueCode.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
v.valueLabel.toLowerCase().includes(searchQuery.toLowerCase())
|
v.valueLabel.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
);
|
);
|
||||||
setFilteredValues(filtered);
|
|
||||||
} else {
|
|
||||||
setFilteredValues(values);
|
|
||||||
}
|
}
|
||||||
}, [searchQuery, values]);
|
|
||||||
|
setFilteredValues(filtered);
|
||||||
|
}, [searchQuery, values, showInactive]);
|
||||||
|
|
||||||
const loadCategoryValues = async () => {
|
const loadCategoryValues = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
@ -264,10 +272,27 @@ export const CategoryValueManager: React.FC<CategoryValueManagerProps> = ({
|
||||||
총 {filteredValues.length}개 항목
|
총 {filteredValues.length}개 항목
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={() => setIsAddDialogOpen(true)} size="sm">
|
<div className="flex items-center gap-3">
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
{/* 비활성 항목 표시 옵션 */}
|
||||||
새 값 추가
|
<div className="flex items-center gap-2">
|
||||||
</Button>
|
<Checkbox
|
||||||
|
id="show-inactive"
|
||||||
|
checked={showInactive}
|
||||||
|
onCheckedChange={(checked) => setShowInactive(checked as boolean)}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="show-inactive"
|
||||||
|
className="text-sm text-muted-foreground cursor-pointer whitespace-nowrap"
|
||||||
|
>
|
||||||
|
비활성 항목 표시
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button onClick={() => setIsAddDialogOpen(true)} size="sm">
|
||||||
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
|
새 값 추가
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 검색바 */}
|
{/* 검색바 */}
|
||||||
|
|
@ -294,73 +319,90 @@ export const CategoryValueManager: React.FC<CategoryValueManagerProps> = ({
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{filteredValues.map((value) => (
|
{filteredValues.map((value) => {
|
||||||
<div
|
const isInactive = value.isActive === false;
|
||||||
key={value.valueId}
|
|
||||||
className="flex items-center gap-3 rounded-md border bg-card p-3 transition-colors hover:bg-accent"
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
checked={selectedValueIds.includes(value.valueId!)}
|
|
||||||
onCheckedChange={() => handleSelectValue(value.valueId!)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex flex-1 items-center gap-2">
|
return (
|
||||||
<Badge variant="outline" className="text-xs">
|
<div
|
||||||
{value.valueCode}
|
key={value.valueId}
|
||||||
</Badge>
|
className={`flex items-center gap-3 rounded-md border bg-card p-3 transition-colors hover:bg-accent ${
|
||||||
<span className="text-sm font-medium">
|
isInactive ? "opacity-50" : ""
|
||||||
{value.valueLabel}
|
}`}
|
||||||
</span>
|
>
|
||||||
{value.description && (
|
<Checkbox
|
||||||
<span className="text-xs text-muted-foreground">
|
checked={selectedValueIds.includes(value.valueId!)}
|
||||||
- {value.description}
|
onCheckedChange={() => handleSelectValue(value.valueId!)}
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{value.isDefault && (
|
|
||||||
<Badge variant="secondary" className="text-[10px]">
|
|
||||||
기본값
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
{value.color && (
|
|
||||||
<div
|
|
||||||
className="h-4 w-4 rounded-full border"
|
|
||||||
style={{ backgroundColor: value.color }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Switch
|
|
||||||
checked={value.isActive !== false}
|
|
||||||
onCheckedChange={() =>
|
|
||||||
handleToggleActive(
|
|
||||||
value.valueId!,
|
|
||||||
value.isActive !== false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="data-[state=checked]:bg-emerald-500"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<div className="flex flex-1 items-center gap-2">
|
||||||
variant="ghost"
|
{/* 색상 표시 (앞쪽으로 이동) */}
|
||||||
size="icon"
|
{value.color && (
|
||||||
onClick={() => setEditingValue(value)}
|
<div
|
||||||
className="h-8 w-8"
|
className="h-4 w-4 rounded-full border flex-shrink-0"
|
||||||
>
|
style={{ backgroundColor: value.color }}
|
||||||
<Edit2 className="h-3 w-3" />
|
/>
|
||||||
</Button>
|
)}
|
||||||
|
|
||||||
<Button
|
{/* 라벨 */}
|
||||||
variant="ghost"
|
<span className={`text-sm font-medium ${isInactive ? "line-through" : ""}`}>
|
||||||
size="icon"
|
{value.valueLabel}
|
||||||
onClick={() => handleDeleteValue(value.valueId!)}
|
</span>
|
||||||
className="h-8 w-8 text-destructive"
|
|
||||||
>
|
{/* 설명 */}
|
||||||
<Trash2 className="h-3 w-3" />
|
{value.description && (
|
||||||
</Button>
|
<span className="text-xs text-muted-foreground">
|
||||||
|
- {value.description}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 기본값 배지 */}
|
||||||
|
{value.isDefault && (
|
||||||
|
<Badge variant="secondary" className="text-[10px]">
|
||||||
|
기본값
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 비활성 배지 */}
|
||||||
|
{isInactive && (
|
||||||
|
<Badge variant="outline" className="text-[10px] text-muted-foreground">
|
||||||
|
비활성
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Switch
|
||||||
|
checked={value.isActive !== false}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleToggleActive(
|
||||||
|
value.valueId!,
|
||||||
|
value.isActive !== false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className="data-[state=checked]:bg-emerald-500"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setEditingValue(value)}
|
||||||
|
className="h-8 w-8"
|
||||||
|
>
|
||||||
|
<Edit2 className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handleDeleteValue(value.valueId!)}
|
||||||
|
className="h-8 w-8 text-destructive"
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
))}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -288,9 +288,19 @@ function getDefaultText(key: string): string {
|
||||||
[MENU_MANAGEMENT_KEYS.USER_MENU]: "사용자 메뉴",
|
[MENU_MANAGEMENT_KEYS.USER_MENU]: "사용자 메뉴",
|
||||||
[MENU_MANAGEMENT_KEYS.ADMIN_DESCRIPTION]: "시스템 관리 및 설정 메뉴",
|
[MENU_MANAGEMENT_KEYS.ADMIN_DESCRIPTION]: "시스템 관리 및 설정 메뉴",
|
||||||
[MENU_MANAGEMENT_KEYS.USER_DESCRIPTION]: "일반 사용자 업무 메뉴",
|
[MENU_MANAGEMENT_KEYS.USER_DESCRIPTION]: "일반 사용자 업무 메뉴",
|
||||||
|
[MENU_MANAGEMENT_KEYS.LIST_TITLE]: "메뉴 목록",
|
||||||
|
[MENU_MANAGEMENT_KEYS.LIST_TOTAL]: "전체",
|
||||||
|
[MENU_MANAGEMENT_KEYS.LIST_SEARCH_RESULT]: "검색 결과",
|
||||||
|
[MENU_MANAGEMENT_KEYS.FILTER_COMPANY]: "회사 필터",
|
||||||
|
[MENU_MANAGEMENT_KEYS.FILTER_COMPANY_ALL]: "전체",
|
||||||
|
[MENU_MANAGEMENT_KEYS.FILTER_COMPANY_COMMON]: "공통",
|
||||||
|
[MENU_MANAGEMENT_KEYS.FILTER_COMPANY_SEARCH]: "회사 검색...",
|
||||||
|
[MENU_MANAGEMENT_KEYS.FILTER_SEARCH]: "검색",
|
||||||
|
[MENU_MANAGEMENT_KEYS.FILTER_SEARCH_PLACEHOLDER]: "메뉴명 검색...",
|
||||||
|
[MENU_MANAGEMENT_KEYS.FILTER_RESET]: "초기화",
|
||||||
[MENU_MANAGEMENT_KEYS.BUTTON_ADD]: "추가",
|
[MENU_MANAGEMENT_KEYS.BUTTON_ADD]: "추가",
|
||||||
[MENU_MANAGEMENT_KEYS.BUTTON_ADD_TOP_LEVEL]: "최상위 메뉴 추가",
|
[MENU_MANAGEMENT_KEYS.BUTTON_ADD_TOP_LEVEL]: "최상위 메뉴 추가",
|
||||||
[MENU_MANAGEMENT_KEYS.BUTTON_ADD_SUB]: "하위 메뉴 추가",
|
[MENU_MANAGEMENT_KEYS.BUTTON_ADD_SUB]: "하위",
|
||||||
[MENU_MANAGEMENT_KEYS.BUTTON_EDIT]: "수정",
|
[MENU_MANAGEMENT_KEYS.BUTTON_EDIT]: "수정",
|
||||||
[MENU_MANAGEMENT_KEYS.BUTTON_DELETE]: "삭제",
|
[MENU_MANAGEMENT_KEYS.BUTTON_DELETE]: "삭제",
|
||||||
[MENU_MANAGEMENT_KEYS.BUTTON_DELETE_SELECTED]: "선택 삭제",
|
[MENU_MANAGEMENT_KEYS.BUTTON_DELETE_SELECTED]: "선택 삭제",
|
||||||
|
|
@ -340,9 +350,48 @@ function getDefaultText(key: string): string {
|
||||||
[MENU_MANAGEMENT_KEYS.STATUS_ACTIVE]: "활성화",
|
[MENU_MANAGEMENT_KEYS.STATUS_ACTIVE]: "활성화",
|
||||||
[MENU_MANAGEMENT_KEYS.STATUS_INACTIVE]: "비활성화",
|
[MENU_MANAGEMENT_KEYS.STATUS_INACTIVE]: "비활성화",
|
||||||
[MENU_MANAGEMENT_KEYS.STATUS_UNSPECIFIED]: "미지정",
|
[MENU_MANAGEMENT_KEYS.STATUS_UNSPECIFIED]: "미지정",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_LOADING]: "로딩 중...",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_DELETE_PROCESSING]: "메뉴를 삭제하는 중...",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_SAVE_SUCCESS]: "메뉴가 성공적으로 저장되었습니다.",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_SAVE_FAILED]: "메뉴 저장에 실패했습니다.",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_DELETE_SUCCESS]: "메뉴가 성공적으로 삭제되었습니다.",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_DELETE_FAILED]: "메뉴 삭제에 실패했습니다.",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_DELETE_BATCH_SUCCESS]: "{count}개의 메뉴가 성공적으로 삭제되었습니다.",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_DELETE_BATCH_PARTIAL]: "{success}개 삭제됨, {failed}개 실패",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_STATUS_TOGGLE_SUCCESS]: "메뉴 상태가 변경되었습니다.",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_MENU_STATUS_TOGGLE_FAILED]: "메뉴 상태 변경에 실패했습니다.",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_MENU_NAME_REQUIRED]: "메뉴명을 입력하세요.",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_COMPANY_REQUIRED]: "회사를 선택하세요.",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_VALIDATION_SELECT_MENU_DELETE]: "삭제할 메뉴를 선택하세요.",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_MENU_LIST]: "메뉴 목록을 불러오는데 실패했습니다.",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_MENU_INFO]: "메뉴 정보를 불러오는데 실패했습니다.",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_COMPANY_LIST]: "회사 목록을 불러오는데 실패했습니다.",
|
||||||
|
[MENU_MANAGEMENT_KEYS.MESSAGE_ERROR_LOAD_LANG_KEY_LIST]: "다국어 키 목록을 불러오는데 실패했습니다.",
|
||||||
|
[MENU_MANAGEMENT_KEYS.UI_EXPAND]: "펼치기",
|
||||||
|
[MENU_MANAGEMENT_KEYS.UI_COLLAPSE]: "접기",
|
||||||
|
[MENU_MANAGEMENT_KEYS.UI_MENU_COLLAPSE]: "메뉴 접기",
|
||||||
|
[MENU_MANAGEMENT_KEYS.UI_LANGUAGE]: "언어",
|
||||||
|
// 추가 매핑: key 문자열 자체도 한글로 매핑
|
||||||
|
"menu.type.title": "메뉴 타입",
|
||||||
|
"menu.management.admin": "관리자",
|
||||||
|
"menu.management.admin.description": "시스템 관리 및 설정 메뉴",
|
||||||
|
"menu.management.user": "사용자",
|
||||||
|
"menu.management.user.description": "일반 사용자 업무 메뉴",
|
||||||
|
"menu.list.title": "메뉴 목록",
|
||||||
|
"filter.company.all": "전체",
|
||||||
|
"filter.search.placeholder": "메뉴명 검색...",
|
||||||
|
"filter.reset": "초기화",
|
||||||
|
"button.add.top.level": "최상위 메뉴 추가",
|
||||||
|
"button.delete.selected": "선택 삭제",
|
||||||
|
"table.header.menu.name": "메뉴명",
|
||||||
|
"table.header.sequence": "순서",
|
||||||
|
"table.header.company": "회사",
|
||||||
|
"table.header.menu.url": "URL",
|
||||||
|
"table.header.status": "상태",
|
||||||
|
"table.header.actions": "작업",
|
||||||
};
|
};
|
||||||
|
|
||||||
return defaultTexts[key] || key;
|
return defaultTexts[key] || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue