[agent-pipeline] pipe-20260329072859-n5mz round-1

This commit is contained in:
DDD1542
2026-03-29 16:57:32 +09:00
parent 6c025aa8ed
commit 9277c93ddc
86 changed files with 805 additions and 815 deletions
@@ -722,10 +722,10 @@ function ProxyTab() {
const data = res?.data || res || [];
const rawUsers: any[] = Array.isArray(data) ? data : [];
const users: UserSearchResult[] = rawUsers.map((u: any) => ({
userId: u.userId || u.user_id || "",
userName: u.userName || u.user_name || "",
positionName: u.positionName || u.position_name || "",
deptName: u.deptName || u.dept_name || "",
userId: u.user_id || "",
userName: u.user_name || "",
positionName: u.position_name || "",
deptName: u.dept_name || "",
}));
setResults(users);
} catch {
@@ -163,8 +163,8 @@ function UserSearchInput({
);
const selectUser = (user: any) => {
const userId = user.user_id || user.userId || "";
const userName = user.user_name || user.userName || userId;
const userId = user.user_id || "";
const userName = user.user_name || userId;
onSelect(userId, userName);
setSearchText("");
setShowResults(false);
@@ -196,8 +196,8 @@ function UserSearchInput({
className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs hover:bg-accent"
onClick={() => selectUser(user)}
>
<span className="font-medium">{user.user_name || user.userName}</span>
<span className="text-muted-foreground">({user.user_id || user.userId})</span>
<span className="font-medium">{user.user_name}</span>
<span className="text-muted-foreground">({user.user_id})</span>
</button>
))}
</div>
+1 -1
View File
@@ -291,7 +291,7 @@ function groupByDate(entries: AuditLogEntry[]): Map<string, AuditLogEntry[]> {
export default function AuditLogPage() {
const { user } = useAuth();
const isSuperAdmin = user?.companyCode === "*" || user?.company_code === "*";
const isSuperAdmin = user?.company_code === "*";
const [entries, setEntries] = useState<AuditLogEntry[]>([]);
const [total, setTotal] = useState(0);
@@ -102,38 +102,38 @@ export default function HierarchyTab() {
// snake_case를 camelCase로 변환하는 함수
const transformGroup = (g: any): HierarchyGroup => ({
groupId: g.group_id || g.groupId,
groupCode: g.group_code || g.groupCode,
groupName: g.group_name || g.groupName,
groupId: g.group_id,
groupCode: g.group_code,
groupName: g.group_name,
description: g.description,
hierarchyType: g.hierarchy_type || g.hierarchyType,
maxLevels: g.max_levels || g.maxLevels,
isFixedLevels: g.is_fixed_levels || g.isFixedLevels,
selfRefTable: g.self_ref_table || g.selfRefTable,
selfRefIdColumn: g.self_ref_id_column || g.selfRefIdColumn,
selfRefParentColumn: g.self_ref_parent_column || g.selfRefParentColumn,
selfRefValueColumn: g.self_ref_value_column || g.selfRefValueColumn,
selfRefLabelColumn: g.self_ref_label_column || g.selfRefLabelColumn,
selfRefLevelColumn: g.self_ref_level_column || g.selfRefLevelColumn,
selfRefOrderColumn: g.self_ref_order_column || g.selfRefOrderColumn,
bomTable: g.bom_table || g.bomTable,
bomParentColumn: g.bom_parent_column || g.bomParentColumn,
bomChildColumn: g.bom_child_column || g.bomChildColumn,
bomItemTable: g.bom_item_table || g.bomItemTable,
bomItemIdColumn: g.bom_item_id_column || g.bomItemIdColumn,
bomItemLabelColumn: g.bom_item_label_column || g.bomItemLabelColumn,
bomQtyColumn: g.bom_qty_column || g.bomQtyColumn,
bomLevelColumn: g.bom_level_column || g.bomLevelColumn,
emptyMessage: g.empty_message || g.emptyMessage,
noOptionsMessage: g.no_options_message || g.noOptionsMessage,
loadingMessage: g.loading_message || g.loadingMessage,
companyCode: g.company_code || g.companyCode,
isActive: g.is_active || g.isActive,
createdBy: g.created_by || g.createdBy,
createdDate: g.created_date || g.createdDate,
updatedBy: g.updated_by || g.updatedBy,
updatedDate: g.updated_date || g.updatedDate,
levelCount: g.level_count || g.levelCount || 0,
hierarchyType: g.hierarchy_type,
maxLevels: g.max_levels,
isFixedLevels: g.is_fixed_levels,
selfRefTable: g.self_ref_table,
selfRefIdColumn: g.self_ref_id_column,
selfRefParentColumn: g.self_ref_parent_column,
selfRefValueColumn: g.self_ref_value_column,
selfRefLabelColumn: g.self_ref_label_column,
selfRefLevelColumn: g.self_ref_level_column,
selfRefOrderColumn: g.self_ref_order_column,
bomTable: g.bom_table,
bomParentColumn: g.bom_parent_column,
bomChildColumn: g.bom_child_column,
bomItemTable: g.bom_item_table,
bomItemIdColumn: g.bom_item_id_column,
bomItemLabelColumn: g.bom_item_label_column,
bomQtyColumn: g.bom_qty_column,
bomLevelColumn: g.bom_level_column,
emptyMessage: g.empty_message,
noOptionsMessage: g.no_options_message,
loadingMessage: g.loading_message,
companyCode: g.company_code,
isActive: g.is_active,
createdBy: g.created_by,
createdDate: g.created_date,
updatedBy: g.updated_by,
updatedDate: g.updated_date,
levelCount: g.level_count || 0,
levels: g.levels,
});
+5 -5
View File
@@ -26,11 +26,11 @@ export default function AdminDebugPage() {
{user && (
<div className="rounded bg-primary/10 p-4">
<h2 className="mb-2 font-semibold"> </h2>
<p>ID: {user.userId}</p>
<p>: {user.userName}</p>
<p>: {user.userType}</p>
<p>: {user.deptName}</p>
<p>: {user.companyCode}</p>
<p>ID: {user.user_id}</p>
<p>: {user.user_name}</p>
<p>: {user.user_type}</p>
<p>: {user.dept_name}</p>
<p>: {user.company_code}</p>
</div>
)}
+1 -1
View File
@@ -53,7 +53,7 @@ export default function MenuPage() {
const { userLang } = useMultiLang({ companyCode: "*" });
// SUPER_ADMIN 여부 확인
const isSuperAdmin = user?.userType === "SUPER_ADMIN";
const isSuperAdmin = user?.user_type === "SUPER_ADMIN";
// 다국어 텍스트 상태
const [uiTexts, setUiTexts] = useState<Record<string, string>>({});
@@ -525,15 +525,15 @@ export default function DashboardDesignerPage({ params }: { params: Promise<{ id
const menu = menuResponse.data;
const updateData = {
menuUrl: dashboardUrl,
parentObjId: menu.parent_obj_id ?? menu.PARENT_OBJ_ID ?? "0",
menuNameKor: menu.menu_name_kor ?? menu.MENU_NAME_KOR ?? "",
menuDesc: menu.menu_desc ?? menu.MENU_DESC ?? "",
menu_url: dashboardUrl,
parent_obj_id: menu.parent_obj_id ?? menu.PARENT_OBJ_ID ?? "0",
menu_name_kor: menu.menu_name_kor ?? menu.MENU_NAME_KOR ?? "",
menu_desc: menu.menu_desc ?? menu.MENU_DESC ?? "",
seq: menu.seq ?? menu.SEQ ?? 1,
menuType: menu.menu_type ?? menu.MENU_TYPE ?? "1",
menu_type: menu.menu_type ?? menu.MENU_TYPE ?? "1",
status: menu.status ?? menu.STATUS ?? "active",
companyCode: menu.company_code ?? menu.COMPANY_CODE ?? "",
langKey: menu.lang_key ?? menu.LANG_KEY ?? "",
company_code: menu.company_code ?? menu.COMPANY_CODE ?? "",
lang_key: menu.lang_key ?? menu.LANG_KEY ?? "",
};
// 메뉴 URL 업데이트
@@ -22,28 +22,28 @@ import { apiClient } from "@/lib/api/client";
import { LangCategory } from "@/lib/api/multilang";
interface Language {
langCode: string;
langName: string;
langNative: string;
isActive: string;
lang_code: string;
lang_name: string;
lang_native: string;
is_active: string;
}
interface LangKey {
keyId: number;
companyCode: string;
menuName: string;
langKey: string;
key_id: number;
company_code: string;
menu_name: string;
lang_key: string;
description: string;
isActive: string;
categoryId?: number;
is_active: string;
category_id?: number;
}
interface LangText {
textId: number;
keyId: number;
langCode: string;
langText: string;
isActive: string;
text_id: number;
key_id: number;
lang_code: string;
lang_text: string;
is_active: string;
}
export default function I18nPage() {
@@ -126,17 +126,17 @@ export default function I18nPage() {
// 회사 필터링
if (selectedCompany && selectedCompany !== "all") {
filteredKeys = filteredKeys.filter((key) => key.companyCode === selectedCompany);
filteredKeys = filteredKeys.filter((key) => key.company_code === selectedCompany);
}
// 텍스트 검색 필터링
if (searchText.trim()) {
const searchLower = searchText.toLowerCase();
filteredKeys = filteredKeys.filter((key) => {
const langKey = (key.langKey || "").toLowerCase();
const langKey = (key.lang_key || "").toLowerCase();
const description = (key.description || "").toLowerCase();
const menuName = (key.menuName || "").toLowerCase();
const companyName = companies.find((c) => c.code === key.companyCode)?.name?.toLowerCase() || "";
const menuName = (key.menu_name || "").toLowerCase();
const companyName = companies.find((c) => c.code === key.company_code)?.name?.toLowerCase() || "";
return (
langKey.includes(searchLower) ||
@@ -168,23 +168,23 @@ export default function I18nPage() {
// 언어 키 선택 처리
const handleKeySelect = (key: LangKey) => {
setSelectedKey(key);
fetchLangTexts(key.keyId);
fetchLangTexts(key.key_id);
};
// 텍스트 변경 처리
const handleTextChange = (langCode: string, value: string) => {
const newEditingTexts = [...editingTexts];
const existingIndex = newEditingTexts.findIndex((t) => t.langCode === langCode);
const existingIndex = newEditingTexts.findIndex((t) => t.lang_code === langCode);
if (existingIndex >= 0) {
newEditingTexts[existingIndex].langText = value;
newEditingTexts[existingIndex].lang_text = value;
} else {
newEditingTexts.push({
textId: 0,
keyId: selectedKey!.keyId,
langCode: langCode,
langText: value,
isActive: "Y",
text_id: 0,
key_id: selectedKey!.key_id,
lang_code: langCode,
lang_text: value,
is_active: "Y",
});
}
@@ -198,19 +198,19 @@ export default function I18nPage() {
try {
const requestData = {
texts: editingTexts.map((text) => ({
langCode: text.langCode,
langText: text.langText,
isActive: text.isActive || "Y",
createdBy: user?.userId || "system",
updatedBy: user?.userId || "system",
lang_code: text.lang_code,
lang_text: text.lang_text,
is_active: text.is_active || "Y",
createdBy: user?.user_id || "system",
updatedBy: user?.user_id || "system",
})),
};
const response = await apiClient.post(`/multilang/keys/${selectedKey.keyId}/texts`, requestData);
const response = await apiClient.post(`/multilang/keys/${selectedKey.key_id}/texts`, requestData);
const data = response.data;
if (data.success) {
alert("저장되었습니다.");
fetchLangTexts(selectedKey.keyId);
fetchLangTexts(selectedKey.key_id);
}
} catch (error) {
alert("저장에 실패했습니다.");
@@ -240,13 +240,13 @@ export default function I18nPage() {
try {
const requestData = {
...languageData,
createdBy: user?.userId || "admin",
updatedBy: user?.userId || "admin",
createdBy: user?.user_id || "admin",
updatedBy: user?.user_id || "admin",
};
let response;
if (editingLanguage) {
response = await apiClient.put(`/multilang/languages/${editingLanguage.langCode}`, requestData);
response = await apiClient.put(`/multilang/languages/${editingLanguage.lang_code}`, requestData);
} else {
response = await apiClient.post("/multilang/languages", requestData);
}
@@ -314,7 +314,7 @@ export default function I18nPage() {
// 언어 전체 선택/해제
const handleSelectAllLanguages = (checked: boolean) => {
if (checked) {
setSelectedLanguages(new Set(languages.map((lang) => lang.langCode)));
setSelectedLanguages(new Set(languages.map((lang) => lang.lang_code)));
} else {
setSelectedLanguages(new Set());
}
@@ -331,13 +331,13 @@ export default function I18nPage() {
try {
const requestData = {
...keyData,
createdBy: user?.userId || "admin",
updatedBy: user?.userId || "admin",
createdBy: user?.user_id || "admin",
updatedBy: user?.user_id || "admin",
};
let response;
if (editingKey) {
response = await apiClient.put(`/multilang/keys/${editingKey.keyId}`, requestData);
response = await apiClient.put(`/multilang/keys/${editingKey.key_id}`, requestData);
} else {
response = await apiClient.post("/multilang/keys", requestData);
}
@@ -406,7 +406,7 @@ export default function I18nPage() {
// 전체 선택/해제
const handleSelectAll = (checked: boolean) => {
if (checked) {
const allKeyIds = getFilteredLangKeys().map((key) => key.keyId);
const allKeyIds = getFilteredLangKeys().map((key) => key.key_id);
setSelectedKeys(new Set(allKeyIds));
} else {
setSelectedKeys(new Set());
@@ -439,7 +439,7 @@ export default function I18nPage() {
setSelectedKeys(new Set());
fetchLangKeys();
if (selectedKey && selectedKeys.has(selectedKey.keyId)) {
if (selectedKey && selectedKeys.has(selectedKey.key_id)) {
handleCancel();
}
} else {
@@ -462,7 +462,7 @@ export default function I18nPage() {
if (data.success) {
alert("언어 키가 영구적으로 삭제되었습니다.");
fetchLangKeys();
if (selectedKey && selectedKey.keyId === keyId) {
if (selectedKey && selectedKey.key_id === keyId) {
handleCancel();
}
}
@@ -511,44 +511,44 @@ export default function I18nPage() {
cell: ({ row }: any) => (
<input
type="checkbox"
checked={selectedKeys.has(row.original.keyId)}
onChange={(e) => handleCheckboxChange(row.original.keyId, e.target.checked)}
checked={selectedKeys.has(row.original.key_id)}
onChange={(e) => handleCheckboxChange(row.original.key_id, e.target.checked)}
onClick={(e) => e.stopPropagation()}
className="h-4 w-4"
disabled={row.original.isActive === "N"}
disabled={row.original.is_active === "N"}
/>
),
},
{
accessorKey: "companyCode",
accessorKey: "company_code",
header: "회사",
cell: ({ row }: any) => {
const companyName =
row.original.companyCode === "*"
row.original.company_code === "*"
? "공통"
: companies.find((c) => c.code === row.original.companyCode)?.name || row.original.companyCode;
: companies.find((c) => c.code === row.original.company_code)?.name || row.original.company_code;
return <span className={row.original.isActive === "N" ? "text-muted-foreground/70" : ""}>{companyName}</span>;
return <span className={row.original.is_active === "N" ? "text-muted-foreground/70" : ""}>{companyName}</span>;
},
},
{
accessorKey: "menuName",
accessorKey: "menu_name",
header: "메뉴명",
cell: ({ row }: any) => (
<span className={row.original.isActive === "N" ? "text-muted-foreground/70" : ""}>{row.original.menuName}</span>
<span className={row.original.is_active === "N" ? "text-muted-foreground/70" : ""}>{row.original.menu_name}</span>
),
},
{
accessorKey: "langKey",
accessorKey: "lang_key",
header: "언어 키",
cell: ({ row }: any) => (
<div
className={`cursor-pointer rounded p-1 hover:bg-muted ${
row.original.isActive === "N" ? "text-muted-foreground/70" : ""
row.original.is_active === "N" ? "text-muted-foreground/70" : ""
}`}
onDoubleClick={() => handleEditKey(row.original)}
>
{row.original.langKey}
{row.original.lang_key}
</div>
),
},
@@ -556,22 +556,22 @@ export default function I18nPage() {
accessorKey: "description",
header: "설명",
cell: ({ row }: any) => (
<span className={row.original.isActive === "N" ? "text-muted-foreground/70" : ""}>{row.original.description}</span>
<span className={row.original.is_active === "N" ? "text-muted-foreground/70" : ""}>{row.original.description}</span>
),
},
{
accessorKey: "isActive",
accessorKey: "is_active",
header: "상태",
cell: ({ row }: any) => (
<button
onClick={() => handleToggleStatus(row.original.keyId)}
onClick={() => handleToggleStatus(row.original.key_id)}
className={`rounded px-2 py-1 text-xs font-medium transition-colors ${
row.original.isActive === "Y"
row.original.is_active === "Y"
? "bg-emerald-100 text-emerald-800 hover:bg-emerald-200"
: "bg-muted text-foreground hover:bg-muted/80"
}`}
>
{row.original.isActive === "Y" ? "활성" : "비활성"}
{row.original.is_active === "Y" ? "활성" : "비활성"}
</button>
),
},
@@ -592,55 +592,55 @@ export default function I18nPage() {
cell: ({ row }: any) => (
<input
type="checkbox"
checked={selectedLanguages.has(row.original.langCode)}
onChange={(e) => handleLanguageCheckboxChange(row.original.langCode, e.target.checked)}
checked={selectedLanguages.has(row.original.lang_code)}
onChange={(e) => handleLanguageCheckboxChange(row.original.lang_code, e.target.checked)}
onClick={(e) => e.stopPropagation()}
className="h-4 w-4"
disabled={row.original.isActive === "N"}
disabled={row.original.is_active === "N"}
/>
),
},
{
accessorKey: "langCode",
accessorKey: "lang_code",
header: "언어 코드",
cell: ({ row }: any) => (
<div
className={`cursor-pointer rounded p-1 hover:bg-muted ${
row.original.isActive === "N" ? "text-muted-foreground/70" : ""
row.original.is_active === "N" ? "text-muted-foreground/70" : ""
}`}
onDoubleClick={() => handleEditLanguage(row.original)}
>
{row.original.langCode}
{row.original.lang_code}
</div>
),
},
{
accessorKey: "langName",
accessorKey: "lang_name",
header: "언어명 (영문)",
cell: ({ row }: any) => (
<span className={row.original.isActive === "N" ? "text-muted-foreground/70" : ""}>{row.original.langName}</span>
<span className={row.original.is_active === "N" ? "text-muted-foreground/70" : ""}>{row.original.lang_name}</span>
),
},
{
accessorKey: "langNative",
accessorKey: "lang_native",
header: "언어명 (원어)",
cell: ({ row }: any) => (
<span className={row.original.isActive === "N" ? "text-muted-foreground/70" : ""}>{row.original.langNative}</span>
<span className={row.original.is_active === "N" ? "text-muted-foreground/70" : ""}>{row.original.lang_native}</span>
),
},
{
accessorKey: "isActive",
accessorKey: "is_active",
header: "상태",
cell: ({ row }: any) => (
<button
onClick={() => handleToggleLanguageStatus(row.original.langCode)}
onClick={() => handleToggleLanguageStatus(row.original.lang_code)}
className={`rounded px-2 py-1 text-xs font-medium transition-colors ${
row.original.isActive === "Y"
row.original.is_active === "Y"
? "bg-emerald-100 text-emerald-800 hover:bg-emerald-200"
: "bg-muted text-foreground hover:bg-muted/80"
}`}
>
{row.original.isActive === "Y" ? "활성" : "비활성"}
{row.original.is_active === "Y" ? "활성" : "비활성"}
</button>
),
},
@@ -813,7 +813,7 @@ export default function I18nPage() {
<>
:{" "}
<Badge variant="secondary" className="ml-2">
{selectedKey.companyCode}.{selectedKey.menuName}.{selectedKey.langKey}
{selectedKey.company_code}.{selectedKey.menu_name}.{selectedKey.lang_key}
</Badge>
</>
) : (
@@ -827,18 +827,18 @@ export default function I18nPage() {
{/* 스크롤 가능한 텍스트 영역 */}
<div className="max-h-80 space-y-4 overflow-y-auto pr-2">
{languages
.filter((lang) => lang.isActive === "Y")
.filter((lang) => lang.is_active === "Y")
.map((lang) => {
const text = editingTexts.find((t) => t.langCode === lang.langCode);
const text = editingTexts.find((t) => t.lang_code === lang.lang_code);
return (
<div key={lang.langCode} className="flex items-center space-x-4">
<div key={lang.lang_code} className="flex items-center space-x-4">
<Badge variant="outline" className="w-20 flex-shrink-0 text-center">
{lang.langName}
{lang.lang_name}
</Badge>
<Input
placeholder={`${lang.langName} 텍스트 입력`}
value={text?.langText || ""}
onChange={(e) => handleTextChange(lang.langCode, e.target.value)}
placeholder={`${lang.lang_name} 텍스트 입력`}
value={text?.lang_text || ""}
onChange={(e) => handleTextChange(lang.lang_code, e.target.value)}
className="flex-1"
/>
</div>
@@ -132,7 +132,7 @@ export default function TableManagementPage() {
const [typeFilter, setTypeFilter] = useState<string | null>(null);
// 최고 관리자 여부 확인 (회사코드가 "*" AND userType이 "SUPER_ADMIN")
const isSuperAdmin = user?.companyCode === "*" && user?.userType === "SUPER_ADMIN";
const isSuperAdmin = user?.company_code === "*" && user?.user_type === "SUPER_ADMIN";
// 다국어 텍스트 로드
useEffect(() => {
@@ -27,7 +27,7 @@ export default function RoleDetailPage({ params }: { params: Promise<{ id: strin
const router = useRouter();
const { refreshMenus } = useMenu();
const isSuperAdmin = currentUser?.companyCode === "*" && currentUser?.userType === "SUPER_ADMIN";
const isSuperAdmin = currentUser?.company_code === "*" && currentUser?.user_type === "SUPER_ADMIN";
// 상태 관리
const [roleGroup, setRoleGroup] = useState<RoleGroup | null>(null);
@@ -77,9 +77,9 @@ export default function RoleDetailPage({ params }: { params: Promise<{ id: strin
if (membersResponse.success && membersResponse.data) {
setSelectedUsers(
membersResponse.data.map((member: any) => ({
id: member.userId,
label: member.userName || member.userId,
description: member.deptName,
id: member.user_id,
label: member.user_name || member.user_id,
description: member.dept_name,
})),
);
}
@@ -88,12 +88,12 @@ export default function RoleDetailPage({ params }: { params: Promise<{ id: strin
const userAPI = await import("@/lib/api/user");
console.log("🔍 사용자 목록 조회 요청:", {
companyCode: roleGroup.companyCode,
companyCode: roleGroup.company_code,
size: 1000,
});
const usersResponse = await userAPI.userAPI.getList({
companyCode: roleGroup.companyCode,
companyCode: roleGroup.company_code,
size: 1000, // 대량 조회
});
@@ -106,9 +106,9 @@ export default function RoleDetailPage({ params }: { params: Promise<{ id: strin
if (usersResponse.success && usersResponse.data) {
setAvailableUsers(
usersResponse.data.map((user: any) => ({
id: user.userId,
label: user.userName || user.userId,
description: user.deptName,
id: user.user_id,
label: user.user_name || user.user_id,
description: user.dept_name,
})),
);
console.log("📋 설정된 전체 사용자 수:", usersResponse.data.length);
@@ -124,8 +124,8 @@ export default function RoleDetailPage({ params }: { params: Promise<{ id: strin
console.log("🔍 [loadMenuPermissions] 메뉴 권한 로드 시작", {
roleGroupId: roleGroup.objid,
roleGroupName: roleGroup.authName,
companyCode: roleGroup.companyCode,
roleGroupName: roleGroup.auth_name,
companyCode: roleGroup.company_code,
});
try {
@@ -252,9 +252,9 @@ export default function RoleDetailPage({ params }: { params: Promise<{ id: strin
<ArrowLeft className="h-5 w-5" />
</Button>
<div className="flex-1">
<h1 className="text-3xl font-bold tracking-tight">{roleGroup.authName}</h1>
<h1 className="text-3xl font-bold tracking-tight">{roleGroup.auth_name}</h1>
<p className="text-muted-foreground text-sm">
{roleGroup.authCode} {roleGroup.companyCode}
{roleGroup.auth_code} {roleGroup.company_code}
</p>
</div>
<span
@@ -32,9 +32,9 @@ export default function RolesPage() {
// 회사 관리자 또는 최고 관리자 여부
const isAdmin =
(currentUser?.companyCode === "*" && currentUser?.userType === "SUPER_ADMIN") ||
currentUser?.userType === "COMPANY_ADMIN";
const isSuperAdmin = currentUser?.companyCode === "*" && currentUser?.userType === "SUPER_ADMIN";
(currentUser?.company_code === "*" && currentUser?.user_type === "SUPER_ADMIN") ||
currentUser?.user_type === "COMPANY_ADMIN";
const isSuperAdmin = currentUser?.company_code === "*" && currentUser?.user_type === "SUPER_ADMIN";
// 상태 관리
const [roleGroups, setRoleGroups] = useState<RoleGroup[]>([]);
@@ -81,7 +81,7 @@ export default function RolesPage() {
? selectedCompany
: isSuperAdmin
? undefined
: currentUser?.companyCode;
: currentUser?.company_code;
console.log("권한 그룹 목록 조회:", { isSuperAdmin, selectedCompany, companyFilter });
@@ -101,7 +101,7 @@ export default function RolesPage() {
} finally {
setIsLoading(false);
}
}, [isSuperAdmin, selectedCompany, currentUser?.companyCode]);
}, [isSuperAdmin, selectedCompany, currentUser?.company_code]);
useEffect(() => {
if (isAdmin) {
@@ -268,8 +268,8 @@ export default function RolesPage() {
>
<div className="mb-4 flex items-start justify-between">
<div className="flex-1">
<h3 className="text-base font-semibold">{role.authName}</h3>
<p className="text-muted-foreground mt-1 font-mono text-sm">{role.authCode}</p>
<h3 className="text-base font-semibold">{role.auth_name}</h3>
<p className="text-muted-foreground mt-1 font-mono text-sm">{role.auth_code}</p>
</div>
<span
className={`rounded-full px-2 py-1 text-xs font-medium ${
@@ -287,7 +287,7 @@ export default function RolesPage() {
<div className="flex justify-between text-sm">
<span className="text-muted-foreground"></span>
<span className="font-medium">
{companies.find((c) => c.company_code === role.companyCode)?.company_name || role.companyCode}
{companies.find((c) => c.company_code === role.company_code)?.company_name || role.company_code}
</span>
</div>
)}
@@ -296,14 +296,14 @@ export default function RolesPage() {
<Users className="h-3 w-3" />
</span>
<span className="font-medium">{role.memberCount || 0}</span>
<span className="font-medium">{role.member_count || 0}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground flex items-center gap-1">
<Menu className="h-3 w-3" />
</span>
<span className="font-medium">{role.menuCount || 0}</span>
<span className="font-medium">{role.menu_count || 0}</span>
</div>
</div>
</div>
@@ -20,7 +20,7 @@ export default function UserAuthPage() {
const { user: currentUser } = useAuth();
// 최고 관리자 여부
const isSuperAdmin = currentUser?.companyCode === "*" && currentUser?.userType === "SUPER_ADMIN";
const isSuperAdmin = currentUser?.company_code === "*" && currentUser?.user_type === "SUPER_ADMIN";
// 상태 관리
const [users, setUsers] = useState<any[]>([]);
+1 -1
View File
@@ -17,7 +17,7 @@ export default function MainPage() {
const router = useRouter();
const { user } = useAuth();
const userName = user?.userName || "사용자";
const userName = user?.user_name || "사용자";
const today = new Date();
const dateStr = today.toLocaleDateString("ko-KR", { year: "numeric", month: "long", day: "numeric", weekday: "long" });
+40 -40
View File
@@ -14,28 +14,28 @@ import { useAuth } from "@/hooks/useAuth";
import { apiClient } from "@/lib/api/client";
interface Language {
langCode: string;
langName: string;
langNative: string;
isActive: string;
lang_code: string;
lang_name: string;
lang_native: string;
is_active: string;
}
interface LangKey {
keyId: number;
companyCode: string;
menuCode: string;
langKey: string;
keyType: string;
key_id: number;
company_code: string;
menu_code: string;
lang_key: string;
key_type: string;
description: string;
isActive: string;
is_active: string;
}
interface LangText {
textId: number;
keyId: number;
langCode: string;
langText: string;
isActive: string;
text_id: number;
key_id: number;
lang_code: string;
lang_text: string;
is_active: string;
}
export default function MultiLangPage() {
@@ -132,28 +132,28 @@ export default function MultiLangPage() {
// 회사 필터링
if (selectedCompany) {
filteredKeys = filteredKeys.filter((key) => key.companyCode === selectedCompany);
filteredKeys = filteredKeys.filter((key) => key.company_code === selectedCompany);
}
// 메뉴 필터링
if (selectedMenu) {
filteredKeys = filteredKeys.filter((key) => key.menuCode === selectedMenu);
filteredKeys = filteredKeys.filter((key) => key.menu_code === selectedMenu);
}
// 키 타입 필터링
if (selectedKeyType) {
filteredKeys = filteredKeys.filter((key) => key.keyType === selectedKeyType);
filteredKeys = filteredKeys.filter((key) => key.key_type === selectedKeyType);
}
// 텍스트 검색 필터링
if (searchText.trim()) {
const searchLower = searchText.toLowerCase();
filteredKeys = filteredKeys.filter((key) => {
const langKey = (key.langKey || "").toLowerCase();
const langKey = (key.lang_key || "").toLowerCase();
const description = (key.description || "").toLowerCase();
const companyName = companies.find((c) => c.code === key.companyCode)?.name?.toLowerCase() || "";
const menuName = menus.find((m) => m.code === key.menuCode)?.name?.toLowerCase() || "";
const keyTypeName = keyTypes.find((t) => t.code === key.keyType)?.name?.toLowerCase() || "";
const companyName = companies.find((c) => c.code === key.company_code)?.name?.toLowerCase() || "";
const menuName = menus.find((m) => m.code === key.menu_code)?.name?.toLowerCase() || "";
const keyTypeName = keyTypes.find((t) => t.code === key.key_type)?.name?.toLowerCase() || "";
return (
langKey.includes(searchLower) ||
@@ -170,31 +170,31 @@ export default function MultiLangPage() {
const columns = [
{
accessorKey: "companyCode",
accessorKey: "company_code",
header: "회사",
cell: ({ row }: any) => {
const company = companies.find((c) => c.code === row.original.companyCode);
return company ? company.name : row.original.companyCode;
const company = companies.find((c) => c.code === row.original.company_code);
return company ? company.name : row.original.company_code;
},
},
{
accessorKey: "menuCode",
accessorKey: "menu_code",
header: "메뉴",
cell: ({ row }: any) => {
const menu = menus.find((m) => m.code === row.original.menuCode);
return menu ? menu.name : row.original.menuCode;
const menu = menus.find((m) => m.code === row.original.menu_code);
return menu ? menu.name : row.original.menu_code;
},
},
{
accessorKey: "langKey",
accessorKey: "lang_key",
header: "언어 키",
},
{
accessorKey: "keyType",
accessorKey: "key_type",
header: "타입",
cell: ({ row }: any) => {
const type = keyTypes.find((t) => t.code === row.original.keyType);
return type ? type.name : row.original.keyType;
const type = keyTypes.find((t) => t.code === row.original.key_type);
return type ? type.name : row.original.key_type;
},
},
{
@@ -202,11 +202,11 @@ export default function MultiLangPage() {
header: "설명",
},
{
accessorKey: "isActive",
accessorKey: "is_active",
header: "상태",
cell: ({ row }: any) => (
<Badge variant={row.original.isActive === "Y" ? "default" : "secondary"}>
{row.original.isActive === "Y" ? "활성" : "비활성"}
<Badge variant={row.original.is_active === "Y" ? "default" : "secondary"}>
{row.original.is_active === "Y" ? "활성" : "비활성"}
</Badge>
),
},
@@ -415,11 +415,11 @@ export default function MultiLangPage() {
<CardContent>
<div className="grid grid-cols-1 gap-4 md:grid-cols-4">
{languages.map((lang) => (
<div key={lang.langCode} className="rounded-lg border p-4">
<div className="font-semibold">{lang.langName}</div>
<div className="text-sm text-muted-foreground">{lang.langNative}</div>
<Badge variant={lang.isActive === "Y" ? "default" : "secondary"} className="mt-2">
{lang.isActive === "Y" ? "활성" : "비활성"}
<div key={lang.lang_code} className="rounded-lg border p-4">
<div className="font-semibold">{lang.lang_name}</div>
<div className="text-sm text-muted-foreground">{lang.lang_native}</div>
<Badge variant={lang.is_active === "Y" ? "default" : "secondary"} className="mt-2">
{lang.is_active === "Y" ? "활성" : "비활성"}
</Badge>
</div>
))}
+1 -1
View File
@@ -17,7 +17,7 @@ export default function MainHomePage() {
const router = useRouter();
const { user } = useAuth();
const userName = user?.userName || "사용자";
const userName = user?.user_name || "사용자";
const today = new Date();
const dateStr = today.toLocaleDateString("ko-KR", { year: "numeric", month: "long", day: "numeric", weekday: "long" });
@@ -30,10 +30,10 @@ export default function ScreenCodeRedirectPage() {
});
const items = res.data?.data?.data || res.data?.data || [];
const arr = Array.isArray(items) ? items : [];
const exact = arr.find((s: any) => s.screenCode === screenCode);
const exact = arr.find((s: any) => s.screen_code === screenCode);
const target = exact || arr[0];
if (target) {
router.replace(`/screens/${target.screenId || target.screen_id}`);
router.replace(`/screens/${target.screen_id}`);
} else {
router.replace("/");
}
@@ -582,7 +582,7 @@ function ScreenViewPage({ screenIdProp, menuObjidProp }: ScreenViewPageProps = {
menuObjid,
screenId,
tableName: screen?.tableName,
userId: user?.userId,
userId: user?.user_id,
userName,
companyCode,
selectedRowsData,
@@ -218,7 +218,7 @@ function PopScreenViewPage() {
<ArrowLeft className="h-4 w-4 mr-1" />
</Button>
<span className="text-sm font-medium">{screen.screenName}</span>
<span className="text-sm font-medium">{screen.screen_name}</span>
<span className="text-xs text-muted-foreground/70">
({currentModeKey.replace("_", " ")})
</span>
@@ -295,7 +295,7 @@ function PopScreenViewPage() {
<LayoutGrid className="h-3.5 w-3.5" />
POP
</Button>
<span className="text-xs text-gray-500">{screen.screenName}</span>
<span className="text-xs text-gray-500">{screen.screen_name}</span>
<Button variant="ghost" size="sm" onClick={() => router.push("/")} className="gap-1 text-xs">
<Monitor className="h-3.5 w-3.5" />
PC
+11 -11
View File
@@ -33,21 +33,21 @@ export function CompanySwitcher({ onClose, isOpen = false }: CompanySwitcherProp
const [searchText, setSearchText] = useState("");
const [loading, setLoading] = useState(false);
// WACE 관리자 권한 체크 (userType만 확인)
const isWaceAdmin = user?.userType === "SUPER_ADMIN";
// WACE 관리자 권한 체크 (user_type만 확인)
const isWaceAdmin = user?.user_type === "SUPER_ADMIN";
// 현재 선택된 회사명 표시
const currentCompanyName = React.useMemo(() => {
if (!user?.companyCode) return "로딩 중...";
if (user.companyCode === "*") {
if (!user?.company_code) return "로딩 중...";
if (user.company_code === "*") {
return "WACE (최고 관리자)";
}
// companies 배열에서 현재 회사 찾기
const currentCompany = companies.find(c => c.company_code === user.companyCode);
return currentCompany?.company_name || user.companyCode;
}, [user?.companyCode, companies]);
const currentCompany = companies.find(c => c.company_code === user.company_code);
return currentCompany?.company_name || user.company_code;
}, [user?.company_code, companies]);
// 회사 목록 조회
useEffect(() => {
@@ -170,7 +170,7 @@ export function CompanySwitcher({ onClose, isOpen = false }: CompanySwitcherProp
<div
key={company.company_code}
className={`flex cursor-pointer items-center justify-between rounded-md px-3 py-2 text-sm transition-colors hover:bg-accent ${
company.company_code === user?.companyCode
company.company_code === user?.company_code
? "bg-accent/50 font-semibold"
: ""
}`}
@@ -184,7 +184,7 @@ export function CompanySwitcher({ onClose, isOpen = false }: CompanySwitcherProp
{company.company_code}
</span>
</div>
{company.company_code === user?.companyCode && (
{company.company_code === user?.company_code && (
<span className="text-xs text-primary"></span>
)}
</div>
+3 -3
View File
@@ -34,9 +34,9 @@ export default function LangKeyModal({ isOpen, onClose, onSave, keyData, compani
if (keyData) {
// 수정 모드
setFormData({
companyCode: keyData.companyCode || "",
menuName: keyData.menuName || "",
langKey: keyData.langKey || "",
companyCode: keyData.company_code || "",
menuName: keyData.menu_name || "",
langKey: keyData.lang_key || "",
description: keyData.description || "",
});
} else {
@@ -88,8 +88,8 @@ export function RoleDeleteModal({ isOpen, onClose, onSuccess, role }: RoleDelete
. :
</p>
<ul className="list-inside list-disc space-y-1 text-xs text-orange-800">
<li> ({role.memberCount || 0})</li>
<li> ({role.menuCount || 0})</li>
<li> ({role.member_count || 0})</li>
<li> ({role.menu_count || 0})</li>
</ul>
</div>
</div>
@@ -100,20 +100,20 @@ export function RoleDeleteModal({ isOpen, onClose, onSuccess, role }: RoleDelete
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground"> </span>
<span className="font-medium">{role.authName}</span>
<span className="font-medium">{role.auth_name}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground"> </span>
<span className="font-mono font-medium">{role.authCode}</span>
<span className="font-mono font-medium">{role.auth_code}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground"></span>
<span className="font-medium">{role.companyCode}</span>
<span className="font-medium">{role.company_code}</span>
</div>
{role.memberNames && (
{role.member_names && (
<div className="border-t pt-2">
<span className="text-muted-foreground text-xs">:</span>
<p className="mt-1 text-xs">{role.memberNames}</p>
<p className="mt-1 text-xs">{role.member_names}</p>
</div>
)}
</div>
+12 -12
View File
@@ -43,13 +43,13 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
const isEditMode = !!editingRole;
// 최고 관리자 여부
const isSuperAdmin = currentUser?.companyCode === "*" && currentUser?.userType === "SUPER_ADMIN";
const isSuperAdmin = currentUser?.company_code === "*" && currentUser?.user_type === "SUPER_ADMIN";
// 폼 데이터
const [formData, setFormData] = useState({
authName: "",
authCode: "",
companyCode: currentUser?.companyCode || "",
companyCode: currentUser?.company_code || "",
status: "active",
});
@@ -106,9 +106,9 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
if (isEditMode && editingRole) {
// 수정 모드: 기존 데이터 로드
setFormData({
authName: editingRole.authName || "",
authCode: editingRole.authCode || "",
companyCode: editingRole.companyCode || "",
authName: editingRole.auth_name || "",
authCode: editingRole.auth_code || "",
companyCode: editingRole.company_code || "",
status: editingRole.status || "active",
});
} else {
@@ -116,13 +116,13 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
setFormData({
authName: "",
authCode: "",
companyCode: currentUser?.companyCode || "",
companyCode: currentUser?.company_code || "",
status: "active",
});
}
setShowAlert(false);
}
}, [isOpen, isEditMode, editingRole, currentUser?.companyCode, isSuperAdmin, loadCompanies]);
}, [isOpen, isEditMode, editingRole, currentUser?.company_code, isSuperAdmin, loadCompanies]);
// 입력 핸들러
const handleInputChange = useCallback((field: string, value: string) => {
@@ -143,16 +143,16 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
if (isEditMode && editingRole) {
// 수정
response = await roleAPI.update(editingRole.objid, {
authName: formData.authName,
authCode: formData.authCode,
auth_name: formData.authName,
auth_code: formData.authCode,
status: formData.status,
});
} else {
// 생성
response = await roleAPI.create({
authName: formData.authName,
authCode: formData.authCode,
companyCode: formData.companyCode,
auth_name: formData.authName,
auth_code: formData.authCode,
company_code: formData.companyCode,
});
}
+11 -11
View File
@@ -35,7 +35,7 @@ export function UserAuthEditModal({ isOpen, onClose, onSuccess, user }: UserAuth
// 모달 열릴 때 현재 권한 설정
useEffect(() => {
if (isOpen && user) {
setSelectedUserType(user.userType || "USER");
setSelectedUserType(user.user_type || "USER");
setShowConfirmation(false);
}
}, [isOpen, user]);
@@ -82,7 +82,7 @@ export function UserAuthEditModal({ isOpen, onClose, onSuccess, user }: UserAuth
const selectedOption = userTypeOptions.find((opt) => opt.value === selectedUserType);
// 권한 변경 여부 확인
const isUserTypeChanged = user && selectedUserType !== user.userType;
const isUserTypeChanged = user && selectedUserType !== user.user_type;
// 권한 변경 처리
const handleSave = async () => {
@@ -101,11 +101,11 @@ export function UserAuthEditModal({ isOpen, onClose, onSuccess, user }: UserAuth
try {
const response = await userAPI.update({
userId: user.userId,
userName: user.userName,
companyCode: user.companyCode,
deptCode: user.deptCode,
userType: selectedUserType,
user_id: user.user_id,
user_name: user.user_name,
company_code: user.company_code,
dept_code: user.dept_code,
user_type: selectedUserType,
});
if (response.success) {
@@ -136,20 +136,20 @@ export function UserAuthEditModal({ isOpen, onClose, onSuccess, user }: UserAuth
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground"> ID</span>
<span className="font-mono font-medium">{user.userId}</span>
<span className="font-mono font-medium">{user.user_id}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground"></span>
<span className="font-medium">{user.userName}</span>
<span className="font-medium">{user.user_name}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground"></span>
<span className="font-medium">{user.companyName || user.companyCode}</span>
<span className="font-medium">{user.company_name || user.company_code}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground"> </span>
<span className="font-medium">
{userTypeOptions.find((opt) => opt.value === user.userType)?.label || user.userType}
{userTypeOptions.find((opt) => opt.value === user.user_type)?.label || user.user_type}
</span>
</div>
</div>
+13 -13
View File
@@ -82,32 +82,32 @@ export function UserAuthTable({ users, isLoading, paginationInfo, onEditAuth, on
render: (_value, _row, index) => <span>{getRowNumber(index)}</span>,
},
{
key: "userId",
key: "user_id",
label: "사용자 ID",
render: (value) => <span className="font-mono">{value}</span>,
},
{
key: "userName",
key: "user_name",
label: "사용자명",
},
{
key: "companyName",
key: "company_name",
label: "회사",
hideOnMobile: true,
render: (_value, row) => <span>{row.companyName || row.companyCode}</span>,
render: (_value, row) => <span>{row.company_name || row.company_code}</span>,
},
{
key: "deptName",
key: "dept_name",
label: "부서",
hideOnMobile: true,
render: (value) => <span>{value || "-"}</span>,
},
{
key: "userType",
key: "user_type",
label: "현재 권한",
className: "text-center",
render: (_value, row) => {
const typeInfo = getUserTypeInfo(row.userType);
const typeInfo = getUserTypeInfo(row.user_type);
return (
<Badge variant="outline" className={`gap-1 ${typeInfo.className}`}>
{typeInfo.icon}
@@ -122,11 +122,11 @@ export function UserAuthTable({ users, isLoading, paginationInfo, onEditAuth, on
const cardFields: RDVCardField<any>[] = [
{
label: "회사",
render: (user) => <span>{user.companyName || user.companyCode}</span>,
render: (user) => <span>{user.company_name || user.company_code}</span>,
},
{
label: "부서",
render: (user) => <span>{user.deptName || "-"}</span>,
render: (user) => <span>{user.dept_name || "-"}</span>,
},
];
@@ -135,14 +135,14 @@ export function UserAuthTable({ users, isLoading, paginationInfo, onEditAuth, on
<ResponsiveDataView<any>
data={users}
columns={columns}
keyExtractor={(u) => u.userId}
keyExtractor={(u) => u.user_id}
isLoading={isLoading}
emptyMessage="등록된 사용자가 없습니다."
skeletonCount={10}
cardTitle={(u) => u.userName}
cardSubtitle={(u) => <span className="font-mono">{u.userId}</span>}
cardTitle={(u) => u.user_name}
cardSubtitle={(u) => <span className="font-mono">{u.user_id}</span>}
cardHeaderRight={(u) => {
const typeInfo = getUserTypeInfo(u.userType);
const typeInfo = getUserTypeInfo(u.user_type);
return (
<Badge variant="outline" className={`gap-1 ${typeInfo.className}`}>
{typeInfo.icon}
@@ -70,10 +70,10 @@ export function UserHistoryModal({ isOpen, onClose, userId, userName }: UserHist
if (response && response.success && Array.isArray(response.data)) {
const responseTotal = response.total || 0;
// No 컬럼을 rowNum 값으로 설정 (페이징 고려)
// No 컬럼을 row_num 값으로 설정 (페이징 고려)
const mappedHistoryList = response.data.map((item, index) => ({
...item,
no: item.rowNum || responseTotal - (pageToLoad - 1) * pageSize - index, // rowNum 우선, 없으면 계산
no: item.row_num || responseTotal - (pageToLoad - 1) * pageSize - index, // row_num 우선, 없으면 계산
}));
setHistoryList(mappedHistoryList);
@@ -211,22 +211,22 @@ export function UserHistoryModal({ isOpen, onClose, userId, userName }: UserHist
<TableRow key={index}>
<TableCell className="text-center font-mono text-sm">{history.no}</TableCell>
<TableCell className="text-center text-sm">{history.sabun || "-"}</TableCell>
<TableCell className="text-center text-sm">{history.userId || "-"}</TableCell>
<TableCell className="text-center text-sm">{history.userName || "-"}</TableCell>
<TableCell className="text-center text-sm">{history.deptName || "-"}</TableCell>
<TableCell className="text-center text-sm">{history.user_id || "-"}</TableCell>
<TableCell className="text-center text-sm">{history.user_name || "-"}</TableCell>
<TableCell className="text-center text-sm">{history.dept_name || "-"}</TableCell>
<TableCell className="text-center">
<Badge variant={getStatusBadgeVariant(history.status || "")}>
{getStatusText(history.status || "")}
</Badge>
</TableCell>
<TableCell className="text-center">
<Badge variant={getChangeTypeBadgeVariant(history.historyType || "")}>
{history.historyType || "-"}
<Badge variant={getChangeTypeBadgeVariant(history.history_type || "")}>
{history.history_type || "-"}
</Badge>
</TableCell>
<TableCell className="text-center text-sm">{history.writerName || "-"}</TableCell>
<TableCell className="text-center text-sm">{history.writer_name || "-"}</TableCell>
<TableCell className="text-center text-sm">
{history.regDateTitle || formatDate(history.regDate || "")}
{history.reg_date_title || formatDate(history.reg_date || "")}
</TableCell>
</TableRow>
))
+10 -10
View File
@@ -42,12 +42,12 @@ export function UserTable({
// 히스토리 모달 상태 관리
const [historyModal, setHistoryModal] = useState<{
isOpen: boolean;
userId: string;
userName: string;
user_id: string;
user_name: string;
}>({
isOpen: false,
userId: "",
userName: "",
user_id: "",
user_name: "",
});
// NO 컬럼 계산 함수 (페이지네이션 고려)
@@ -88,8 +88,8 @@ export function UserTable({
const handleOpenHistoryModal = (user: User) => {
setHistoryModal({
isOpen: true,
userId: user.user_id,
userName: user.user_name || user.user_id,
user_id: user.user_id,
user_name: user.user_name || user.user_id,
});
};
@@ -97,8 +97,8 @@ export function UserTable({
const handleCloseHistoryModal = () => {
setHistoryModal({
isOpen: false,
userId: "",
userName: "",
user_id: "",
user_name: "",
});
};
@@ -300,8 +300,8 @@ export function UserTable({
<UserHistoryModal
isOpen={historyModal.isOpen}
onClose={handleCloseHistoryModal}
userId={historyModal.userId}
userName={historyModal.userName}
userId={historyModal.user_id}
userName={historyModal.user_name}
/>
</>
);
@@ -43,7 +43,7 @@ export function DepartmentMembers({
// 부서원 삭제 확인 모달
const [removeConfirmOpen, setRemoveConfirmOpen] = useState(false);
const [memberToRemove, setMemberToRemove] = useState<{ userId: string; name: string } | null>(null);
const [memberToRemove, setMemberToRemove] = useState<{ user_id: string; name: string } | null>(null);
// 부서원 목록 로드
useEffect(() => {
@@ -144,7 +144,7 @@ export function DepartmentMembers({
// 부서원 제거 확인 요청
const handleRemoveMemberRequest = (userId: string, userName: string) => {
setMemberToRemove({ userId, name: userName });
setMemberToRemove({ user_id: userId, name: userName });
setRemoveConfirmOpen(true);
};
@@ -155,7 +155,7 @@ export function DepartmentMembers({
try {
const response = await departmentAPI.removeDepartmentMember(
selectedDepartment.dept_code,
memberToRemove.userId
memberToRemove.user_id
);
if (response.success) {
@@ -43,8 +43,8 @@ import {
import { apiClient } from "@/lib/api/client";
interface Company {
companyCode: string;
companyName: string;
company_code: string;
company_name: string;
}
interface KeyGenerateModalProps {
@@ -104,12 +104,11 @@ export function KeyGenerateModal({
try {
const response = await apiClient.get("/admin/companies");
if (response.data.success && response.data.data) {
// snake_case를 camelCase로 변환하고 공통(*)은 제외
const companyList = response.data.data
.filter((c: any) => c.company_code !== "*")
.map((c: any) => ({
companyCode: c.company_code,
companyName: c.company_name,
company_code: c.company_code,
company_name: c.company_name,
}));
setCompanies(companyList);
}
@@ -354,8 +353,8 @@ export function KeyGenerateModal({
>
{targetCompanyCode === "*"
? "공통 (*) - 모든 회사 적용"
: companies.find((c) => c.companyCode === targetCompanyCode)
? `${companies.find((c) => c.companyCode === targetCompanyCode)?.companyName} (${targetCompanyCode})`
: companies.find((c) => c.company_code === targetCompanyCode)
? `${companies.find((c) => c.company_code === targetCompanyCode)?.company_name} (${targetCompanyCode})`
: "대상 선택"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
@@ -390,10 +389,10 @@ export function KeyGenerateModal({
</CommandItem>
{companies.map((company) => (
<CommandItem
key={company.companyCode}
value={`${company.companyName} ${company.companyCode}`}
key={company.company_code}
value={`${company.company_name} ${company.company_code}`}
onSelect={() => {
setTargetCompanyCode(company.companyCode);
setTargetCompanyCode(company.company_code);
setCompanySearchOpen(false);
}}
className="text-xs sm:text-sm"
@@ -401,10 +400,10 @@ export function KeyGenerateModal({
<Check
className={cn(
"mr-2 h-4 w-4",
targetCompanyCode === company.companyCode ? "opacity-100" : "opacity-0"
targetCompanyCode === company.company_code ? "opacity-100" : "opacity-0"
)}
/>
{company.companyName} ({company.companyCode})
{company.company_name} ({company.company_code})
</CommandItem>
))}
</CommandGroup>
@@ -63,16 +63,12 @@ interface ApprovalRequestModalProps {
}
interface UserSearchResult {
userId: string;
userName: string;
positionName?: string;
deptName?: string;
deptCode?: string;
email?: string;
user_id?: string;
user_name?: string;
user_id: string;
user_name: string;
position_name?: string;
dept_name?: string;
dept_code?: string;
email?: string;
}
function genId(): string {
@@ -136,14 +132,14 @@ export const ApprovalRequestModal: React.FC<ApprovalRequestModalProps> = ({
const data = res?.data || res || [];
const rawUsers: any[] = Array.isArray(data) ? data : [];
const users: UserSearchResult[] = rawUsers.map((u: any) => ({
userId: u.userId || u.user_id || "",
userName: u.userName || u.user_name || "",
positionName: u.positionName || u.position_name || "",
deptName: u.deptName || u.dept_name || "",
deptCode: u.deptCode || u.dept_code || "",
user_id: u.user_id || "",
user_name: u.user_name || "",
position_name: u.position_name || "",
dept_name: u.dept_name || "",
dept_code: u.dept_code || "",
email: u.email || "",
}));
setAllUsers(users.filter((u) => u.userId));
setAllUsers(users.filter((u) => u.user_id));
} catch {
setAllUsers([]);
} finally {
@@ -203,7 +199,7 @@ export const ApprovalRequestModal: React.FC<ApprovalRequestModalProps> = ({
// Combobox에서 이미 선택된 사용자 제외한 목록
const availableUsers = allUsers.filter(
(u) => !approvers.some((a) => a.user_id === u.userId)
(u) => !approvers.some((a) => a.user_id === u.user_id)
);
const addApprover = (user: UserSearchResult) => {
@@ -211,10 +207,10 @@ export const ApprovalRequestModal: React.FC<ApprovalRequestModalProps> = ({
...prev,
{
id: genId(),
user_id: user.userId,
user_name: user.userName,
position_name: user.positionName || "",
dept_name: user.deptName || "",
user_id: user.user_id,
user_name: user.user_name,
position_name: user.position_name || "",
dept_name: user.dept_name || "",
},
]);
setComboboxOpen(false);
@@ -511,8 +507,8 @@ export const ApprovalRequestModal: React.FC<ApprovalRequestModalProps> = ({
<CommandGroup>
{availableUsers.map((user) => (
<CommandItem
key={user.userId}
value={`${user.userName} ${user.userId} ${user.deptName || ""} ${user.positionName || ""}`}
key={user.user_id}
value={`${user.user_name} ${user.user_id} ${user.dept_name || ""} ${user.position_name || ""}`}
onSelect={() => addApprover(user)}
className="flex cursor-pointer items-center gap-3 px-3 py-2 text-xs sm:text-sm"
>
@@ -521,13 +517,13 @@ export const ApprovalRequestModal: React.FC<ApprovalRequestModalProps> = ({
</div>
<div className="min-w-0 flex-1">
<p className="truncate font-medium">
{user.userName}
{user.user_name}
<span className="text-muted-foreground ml-1 text-[10px]">
({user.userId})
({user.user_id})
</span>
</p>
<p className="text-muted-foreground truncate text-[10px]">
{[user.deptName, user.positionName].filter(Boolean).join(" / ") || "-"}
{[user.dept_name, user.position_name].filter(Boolean).join(" / ") || "-"}
</p>
</div>
<Plus className="text-muted-foreground h-4 w-4 shrink-0" />
+4 -4
View File
@@ -47,13 +47,13 @@ export function LoginForm({
{/* 사용자 ID */}
<div className="space-y-2">
<Label htmlFor="userId"> ID</Label>
<Label htmlFor="user_id"> ID</Label>
<Input
id="userId"
name="userId"
id="user_id"
name="user_id"
type="text"
placeholder="사용자 ID를 입력하세요"
value={formData.userId}
value={formData.user_id}
onChange={onInputChange}
disabled={isLoading}
className="h-11"
+2 -2
View File
@@ -1257,7 +1257,7 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
groupedData={selectedData}
userId={userId}
userName={userName}
companyCode={user?.companyCode}
companyCode={user?.company_code || user?.companyCode}
isInModal={true}
/>
);
@@ -1301,7 +1301,7 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
}}
userId={userId}
userName={userName}
companyCode={user?.companyCode}
companyCode={user?.company_code || user?.companyCode}
isInModal={true}
/>
);
@@ -553,7 +553,7 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
const parsed = JSON.parse(marker.description);
// 다양한 필드명 지원 (plate_no 우선 - 차량 번호판으로 경로 구분)
userId = parsed.plate_no || parsed.plateNo || parsed.car_number || parsed.carNumber ||
parsed.user_id || parsed.userId || parsed.driver_id || parsed.driverId ||
parsed.user_id || parsed.driver_id ||
parsed.car_no || parsed.carNo || parsed.vehicle_no || parsed.vehicleNo ||
parsed.id || parsed.code || "";
vehicleId = parsed.vehicle_id || parsed.vehicleId || parsed.car_id || parsed.carId;
@@ -1916,7 +1916,7 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
const parsed = JSON.parse(marker.description || "{}");
// 식별자 찾기 (user_id 또는 vehicle_number)
const identifier = parsed.user_id || parsed.userId || parsed.vehicle_number ||
const identifier = parsed.user_id || parsed.vehicle_number ||
parsed.vehicleNumber || parsed.plate_no || parsed.plateNo ||
parsed.car_number || parsed.carNumber || marker.name;
@@ -2076,7 +2076,7 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
const parsed = JSON.parse(marker.description || "{}");
// 다양한 필드명 지원 (plate_no 우선)
const visibleUserId = parsed.plate_no || parsed.plateNo || parsed.car_number || parsed.carNumber ||
parsed.user_id || parsed.userId || parsed.driver_id || parsed.driverId ||
parsed.user_id || parsed.driver_id ||
parsed.car_no || parsed.carNo || parsed.vehicle_no || parsed.vehicleNo ||
parsed.id || parsed.code || marker.name;
if (visibleUserId) {
@@ -137,8 +137,8 @@ export default function VehicleMapOnlyWidget({ element, refreshInterval = 30000
: "inactive",
speed: parseFloat(row.speed) || 0,
destination: row.destination || "대기 중",
userId: row.user_id || row.userId || undefined,
tripId: row.trip_id || row.tripId || undefined,
userId: row.user_id || undefined,
tripId: row.trip_id || undefined,
};
})
// 유효한 위도/경도가 있는 차량만 필터링
@@ -39,7 +39,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
const { user: authUser } = useAuth();
// 실제 사용자 회사 코드 사용 (prop보다 사용자 정보 우선)
const companyCode = authUser?.company_code || authUser?.companyCode || propCompanyCode;
const companyCode = authUser?.company_code || propCompanyCode;
// 커스텀 훅 사용
const {
@@ -690,14 +690,14 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
targetDiagramId!,
saveRequest,
companyCode,
authUser?.userId || "SYSTEM",
authUser?.user_id || "SYSTEM",
);
} else {
// 새로운 관계도 생성
const newDiagram = await DataFlowAPI.createJsonDataFlowDiagram(
saveRequest,
companyCode,
authUser?.userId || "SYSTEM",
authUser?.user_id || "SYSTEM",
);
// 새로 생성된 다이어그램 ID를 내부 상태에 저장 (다음 저장부터는 업데이트 모드)
@@ -758,7 +758,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
diagramId,
currentDiagramId,
companyCode,
authUser?.userId,
authUser?.user_id,
setIsSaving,
setHasUnsavedChanges,
setShowSaveModal,
+7 -7
View File
@@ -11,18 +11,18 @@ export function AdminButton({ user }: AdminButtonProps) {
// 디버깅용 로그
console.log("=== AdminButton 디버깅 ===");
console.log("user:", user);
console.log("user?.userType:", user?.userType);
console.log("user?.user_type:", user?.user_type);
console.log("user?.isAdmin:", user?.isAdmin);
console.log("user?.userId:", user?.userId);
console.log("user?.user_id:", user?.user_id);
// 관리자 권한 확인 로직 (3단계 권한 체계)
const isAdmin =
user?.isAdmin ||
user?.userType === "SUPER_ADMIN" ||
user?.userType === "COMPANY_ADMIN" ||
user?.userType === "ADMIN" ||
user?.userType === "admin" ||
user?.userId === "plm_admin";
user?.user_type === "SUPER_ADMIN" ||
user?.user_type === "COMPANY_ADMIN" ||
user?.user_type === "ADMIN" ||
user?.user_type === "admin" ||
user?.user_id === "plm_admin";
console.log("최종 관리자 권한 확인:", isAdmin);
+28 -28
View File
@@ -54,25 +54,25 @@ import { CompanySwitcher } from "@/components/admin/CompanySwitcher";
import { getIconComponent } from "@/components/admin/MenuIconPicker";
interface ExtendedUserInfo {
userId: string;
userName: string;
user_id: string;
user_name: string;
userNameEng?: string;
userNameCn?: string;
deptCode?: string;
deptName?: string;
dept_name?: string;
positionCode?: string;
positionName?: string;
position_name?: string;
email?: string;
tel?: string;
cellPhone?: string;
userType?: string;
user_type?: string;
userTypeName?: string;
authName?: string;
partnerCd?: string;
isAdmin: boolean;
sabun?: string;
photo?: string | null;
companyCode?: string;
company_code?: string;
locale?: string;
}
@@ -263,8 +263,8 @@ function AppLayoutInner({ children }: AppLayoutProps) {
// 현재 회사명 조회 (SUPER_ADMIN 전용)
useEffect(() => {
const fetchCurrentCompanyName = async () => {
if ((user as ExtendedUserInfo)?.userType === "SUPER_ADMIN") {
const companyCode = (user as ExtendedUserInfo)?.companyCode;
if ((user as ExtendedUserInfo)?.user_type === "SUPER_ADMIN") {
const companyCode = (user as ExtendedUserInfo)?.company_code;
if (companyCode === "*") {
setCurrentCompanyName("WACE (최고 관리자)");
@@ -283,7 +283,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
};
fetchCurrentCompanyName();
}, [(user as ExtendedUserInfo)?.companyCode, (user as ExtendedUserInfo)?.userType]);
}, [(user as ExtendedUserInfo)?.company_code, (user as ExtendedUserInfo)?.user_type]);
// 화면 크기 감지 및 사이드바 초기 상태 설정
useEffect(() => {
@@ -627,12 +627,12 @@ function AppLayoutInner({ children }: AppLayoutProps) {
{user.photo && user.photo.trim() !== "" && user.photo !== "null" ? (
<img
src={user.photo}
alt={user.userName || "User"}
alt={user.user_name || "User"}
className="aspect-square h-full w-full object-cover"
/>
) : (
<div className="bg-muted text-foreground flex h-full w-full items-center justify-center rounded-full text-sm font-semibold">
{user.userName?.substring(0, 1)?.toUpperCase() || "U"}
{user.user_name?.substring(0, 1)?.toUpperCase() || "U"}
</div>
)}
</div>
@@ -641,9 +641,9 @@ function AppLayoutInner({ children }: AppLayoutProps) {
<DropdownMenuContent className="w-56" align="end">
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1">
<p className="text-sm leading-none font-medium">{user.userName || "사용자"}</p>
<p className="text-sm leading-none font-medium">{user.user_name || "사용자"}</p>
<p className="text-muted-foreground text-xs leading-none">
{user.deptName || user.email || user.userId}
{user.dept_name || user.email || user.user_id}
</p>
</div>
</DropdownMenuLabel>
@@ -697,7 +697,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
</div>
)}
{(user as ExtendedUserInfo)?.userType === "SUPER_ADMIN" && (
{(user as ExtendedUserInfo)?.user_type === "SUPER_ADMIN" && (
<div className="border-border bg-muted/50 mx-3 mt-3 rounded-md border p-3">
<div className="flex items-center gap-2">
<Building2 className="text-primary h-4 w-4 shrink-0" />
@@ -711,9 +711,9 @@ function AppLayoutInner({ children }: AppLayoutProps) {
</div>
)}
{((user as ExtendedUserInfo)?.userType === "SUPER_ADMIN" ||
(user as ExtendedUserInfo)?.userType === "COMPANY_ADMIN" ||
(user as ExtendedUserInfo)?.userType === "admin") && (
{((user as ExtendedUserInfo)?.user_type === "SUPER_ADMIN" ||
(user as ExtendedUserInfo)?.user_type === "COMPANY_ADMIN" ||
(user as ExtendedUserInfo)?.user_type === "admin") && (
<div className="border-border space-y-2 border-b p-3">
<Button
onClick={handleModeSwitch}
@@ -736,7 +736,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
)}
</Button>
{(user as ExtendedUserInfo)?.userType === "SUPER_ADMIN" && (
{(user as ExtendedUserInfo)?.user_type === "SUPER_ADMIN" && (
<Button
onClick={() => {
console.log("🔴 회사 선택 버튼 클릭!");
@@ -777,19 +777,19 @@ function AppLayoutInner({ children }: AppLayoutProps) {
{user.photo && user.photo.trim() !== "" && user.photo !== "null" ? (
<img
src={user.photo}
alt={user.userName || "User"}
alt={user.user_name || "User"}
className="aspect-square h-full w-full object-cover"
/>
) : (
<div className="bg-muted text-foreground flex h-full w-full items-center justify-center rounded-full text-sm font-semibold">
{user.userName?.substring(0, 1)?.toUpperCase() || "U"}
{user.user_name?.substring(0, 1)?.toUpperCase() || "U"}
</div>
)}
</div>
<div className="min-w-0 flex-1">
<p className="text-foreground truncate text-sm font-medium">{user.userName || "사용자"}</p>
<p className="text-foreground truncate text-sm font-medium">{user.user_name || "사용자"}</p>
<p className="text-muted-foreground truncate text-xs">
{user.deptName || user.email || user.userId}
{user.dept_name || user.email || user.user_id}
</p>
</div>
</button>
@@ -801,24 +801,24 @@ function AppLayoutInner({ children }: AppLayoutProps) {
{user.photo && user.photo.trim() !== "" && user.photo !== "null" ? (
<img
src={user.photo}
alt={user.userName || "User"}
alt={user.user_name || "User"}
className="aspect-square h-full w-full object-cover"
/>
) : (
<div className="bg-muted text-foreground flex h-full w-full items-center justify-center rounded-full text-base font-semibold">
{user.userName?.substring(0, 1)?.toUpperCase() || "U"}
{user.user_name?.substring(0, 1)?.toUpperCase() || "U"}
</div>
)}
</div>
<div className="flex flex-col space-y-1">
<p className="text-sm leading-none font-medium">
{user.userName || "사용자"} ({user.userId || ""})
{user.user_name || "사용자"} ({user.user_id || ""})
</p>
<p className="text-muted-foreground text-xs leading-none font-semibold">{user.email || ""}</p>
<p className="text-muted-foreground text-xs leading-none font-semibold">
{user.deptName && user.positionName
? `${user.deptName}, ${user.positionName}`
: user.deptName || user.positionName || "부서 정보 없음"}
{user.dept_name && user.position_name
? `${user.dept_name}, ${user.position_name}`
: user.dept_name || user.position_name || "부서 정보 없음"}
</p>
</div>
</div>
+1 -1
View File
@@ -278,7 +278,7 @@ export function ProfileModal({
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="userId"> ID</Label>
<Input id="userId" value={user?.userId || ""} disabled className="bg-muted" />
<Input id="userId" value={user?.user_id || ""} disabled className="bg-muted" />
</div>
<div className="space-y-2">
<Label htmlFor="userName"></Label>
+8 -8
View File
@@ -34,12 +34,12 @@ export function UserDropdown({ user, onProfileClick, onPopModeClick, onLogout }:
{user.photo && user.photo.trim() !== "" && user.photo !== "null" ? (
<img
src={user.photo}
alt={user.userName || "User"}
alt={user.user_name || "User"}
className="aspect-square h-full w-full object-cover"
/>
) : (
<div className="flex h-full w-full items-center justify-center rounded-full bg-slate-200 font-semibold text-slate-700">
{user.userName?.substring(0, 1)?.toUpperCase() || "U"}
{user.user_name?.substring(0, 1)?.toUpperCase() || "U"}
</div>
)}
</div>
@@ -53,12 +53,12 @@ export function UserDropdown({ user, onProfileClick, onPopModeClick, onLogout }:
{user.photo && user.photo.trim() !== "" && user.photo !== "null" ? (
<img
src={user.photo}
alt={user.userName || "User"}
alt={user.user_name || "User"}
className="aspect-square h-full w-full object-cover"
/>
) : (
<div className="flex h-full w-full items-center justify-center rounded-full bg-slate-200 text-base font-semibold text-slate-700">
{user.userName?.substring(0, 1)?.toUpperCase() || "U"}
{user.user_name?.substring(0, 1)?.toUpperCase() || "U"}
</div>
)}
</div>
@@ -66,13 +66,13 @@ export function UserDropdown({ user, onProfileClick, onPopModeClick, onLogout }:
{/* 사용자 정보 */}
<div className="flex flex-col space-y-1">
<p className="text-sm leading-none font-medium">
{user.userName || "사용자"} ({user.userId || ""})
{user.user_name || "사용자"} ({user.user_id || ""})
</p>
<p className="text-muted-foreground text-xs leading-none font-semibold">{user.email || ""}</p>
<p className="text-muted-foreground text-xs leading-none font-semibold">
{user.deptName && user.positionName
? `${user.deptName}, ${user.positionName}`
: user.deptName || user.positionName || "부서 정보 없음"}
{user.dept_name && user.position_name
? `${user.dept_name}, ${user.position_name}`
: user.dept_name || user.position_name || "부서 정보 없음"}
</p>
</div>
</div>
+52 -52
View File
@@ -12,27 +12,27 @@ import { Badge } from "@/components/ui/badge";
import { apiClient } from "@/lib/api/client";
interface Language {
langCode: string;
langName: string;
langNative: string;
lang_code: string;
lang_name: string;
lang_native: string;
}
interface LangKey {
keyId?: number;
companyCode: string;
menuCode: string;
langKey: string;
keyType: string;
key_id?: number;
company_code: string;
menu_code: string;
lang_key: string;
key_type: string;
description: string;
isActive: string;
is_active: string;
}
interface LangText {
textId?: number;
keyId?: number;
langCode: string;
langText: string;
isActive: string;
text_id?: number;
key_id?: number;
lang_code: string;
lang_text: string;
is_active: string;
}
interface LangKeyModalProps {
@@ -57,12 +57,12 @@ export function LangKeyModal({
onSave,
}: LangKeyModalProps) {
const [keyData, setKeyData] = useState<LangKey>({
companyCode: "",
menuCode: "",
langKey: "",
keyType: "TEXT",
company_code: "",
menu_code: "",
lang_key: "",
key_type: "TEXT",
description: "",
isActive: "Y",
is_active: "Y",
});
const [textData, setTextData] = useState<LangText[]>([]);
const [loading, setLoading] = useState(false);
@@ -70,15 +70,15 @@ export function LangKeyModal({
useEffect(() => {
if (langKey) {
setKeyData(langKey);
fetchLangTexts(langKey.keyId!);
fetchLangTexts(langKey.key_id!);
} else {
setKeyData({
companyCode: "",
menuCode: "",
langKey: "",
keyType: "TEXT",
company_code: "",
menu_code: "",
lang_key: "",
key_type: "TEXT",
description: "",
isActive: "Y",
is_active: "Y",
});
initializeTextData();
}
@@ -86,9 +86,9 @@ export function LangKeyModal({
const initializeTextData = () => {
const initialTexts = languages.map((lang) => ({
langCode: lang.langCode,
langText: "",
isActive: "Y",
lang_code: lang.lang_code,
lang_text: "",
is_active: "Y",
}));
setTextData(initialTexts);
};
@@ -100,12 +100,12 @@ export function LangKeyModal({
if (data.success) {
const texts = data.data;
const allTexts = languages.map((lang) => {
const existingText = texts.find((t: LangText) => t.langCode === lang.langCode);
const existingText = texts.find((t: LangText) => t.lang_code === lang.lang_code);
return (
existingText || {
langCode: lang.langCode,
langText: "",
isActive: "Y",
lang_code: lang.lang_code,
lang_text: "",
is_active: "Y",
}
);
});
@@ -118,7 +118,7 @@ export function LangKeyModal({
};
const handleSave = async () => {
if (!keyData.companyCode || !keyData.menuCode || !keyData.langKey) {
if (!keyData.company_code || !keyData.menu_code || !keyData.lang_key) {
alert("필수 항목을 입력해주세요.");
return;
}
@@ -136,7 +136,7 @@ export function LangKeyModal({
};
const updateTextData = (langCode: string, value: string) => {
setTextData((prev) => prev.map((text) => (text.langCode === langCode ? { ...text, langText: value } : text)));
setTextData((prev) => prev.map((text) => (text.lang_code === langCode ? { ...text, lang_text: value } : text)));
};
return (
@@ -155,10 +155,10 @@ export function LangKeyModal({
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="companyCode"> *</Label>
<Label htmlFor="company_code"> *</Label>
<Select
value={keyData.companyCode}
onValueChange={(value) => setKeyData((prev) => ({ ...prev, companyCode: value }))}
value={keyData.company_code}
onValueChange={(value) => setKeyData((prev) => ({ ...prev, company_code: value }))}
>
<SelectTrigger>
<SelectValue placeholder="회사 선택" />
@@ -173,10 +173,10 @@ export function LangKeyModal({
</Select>
</div>
<div>
<Label htmlFor="menuCode"> *</Label>
<Label htmlFor="menu_code"> *</Label>
<Select
value={keyData.menuCode}
onValueChange={(value) => setKeyData((prev) => ({ ...prev, menuCode: value }))}
value={keyData.menu_code}
onValueChange={(value) => setKeyData((prev) => ({ ...prev, menu_code: value }))}
>
<SelectTrigger>
<SelectValue placeholder="메뉴 선택" />
@@ -193,19 +193,19 @@ export function LangKeyModal({
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="langKey"> *</Label>
<Label htmlFor="lang_key"> *</Label>
<Input
id="langKey"
value={keyData.langKey}
onChange={(e) => setKeyData((prev) => ({ ...prev, langKey: e.target.value }))}
id="lang_key"
value={keyData.lang_key}
onChange={(e) => setKeyData((prev) => ({ ...prev, lang_key: e.target.value }))}
placeholder="예: menu.dashboard, button.save"
/>
</div>
<div>
<Label htmlFor="keyType"> </Label>
<Label htmlFor="key_type"> </Label>
<Select
value={keyData.keyType}
onValueChange={(value) => setKeyData((prev) => ({ ...prev, keyType: value }))}
value={keyData.key_type}
onValueChange={(value) => setKeyData((prev) => ({ ...prev, key_type: value }))}
>
<SelectTrigger>
<SelectValue />
@@ -241,17 +241,17 @@ export function LangKeyModal({
<CardContent>
<div className="space-y-4">
{textData.map((text, index) => (
<div key={text.langCode} className="flex items-center space-x-4">
<div key={text.lang_code} className="flex items-center space-x-4">
<div className="w-24">
<Badge variant="outline" className="w-full justify-center">
{languages.find((l) => l.langCode === text.langCode)?.langName}
{languages.find((l) => l.lang_code === text.lang_code)?.lang_name}
</Badge>
</div>
<div className="flex-1">
<Input
value={text.langText}
onChange={(e) => updateTextData(text.langCode, e.target.value)}
placeholder={`${languages.find((l) => l.langCode === text.langCode)?.langName} 텍스트 입력`}
value={text.lang_text}
onChange={(e) => updateTextData(text.lang_code, e.target.value)}
placeholder={`${languages.find((l) => l.lang_code === text.lang_code)?.lang_name} 텍스트 입력`}
/>
</div>
</div>
@@ -107,9 +107,9 @@ export function PopDeployModal({
if (isGroupMode && groupScreens) {
setGroupEntries(
groupScreens.map((s) => ({
screenId: s.screenId,
screenName: s.screenName,
newScreenName: s.screenName,
screenId: s.screen_id ?? s.screenId,
screenName: s.screen_name ?? s.screenName,
newScreenName: s.screen_name ?? s.screenName,
newScreenCode: "",
included: true,
})),
@@ -117,10 +117,10 @@ export function PopDeployModal({
setScreenName("");
setScreenCode("");
} else if (screen) {
setScreenName(screen.screenName);
setScreenName(screen.screen_name ?? screen.screenName);
setScreenCode("");
setGroupEntries([]);
analyzeLinks(screen.screenId);
analyzeLinks(screen.screen_id ?? screen.screenId);
}
}, [open, screen, groupScreens, isGroupMode]);
@@ -169,21 +169,21 @@ export function PopDeployModal({
const linked: LinkedScreenInfo[] = result.linkedScreenIds.map(
(linkedId) => {
const linkedScreen = allScreens.find(
(s) => s.screenId === linkedId,
(s) => (s.screen_id ?? s.screenId) === linkedId,
);
const refs = result.references.filter(
(r) => r.targetScreenId === linkedId,
);
return {
screenId: linkedId,
screenName: linkedScreen?.screenName || `화면 ${linkedId}`,
screenCode: linkedScreen?.screenCode || "",
screenName: (linkedScreen?.screen_name ?? linkedScreen?.screenName) || `화면 ${linkedId}`,
screenCode: (linkedScreen?.screen_code ?? linkedScreen?.screenCode) || "",
references: refs.map((r) => ({
componentId: r.componentId,
referenceType: r.referenceType,
})),
deploy: true,
newScreenName: linkedScreen?.screenName || `화면 ${linkedId}`,
newScreenName: (linkedScreen?.screen_name ?? linkedScreen?.screenName) || `화면 ${linkedId}`,
newScreenCode: "",
};
},
@@ -219,7 +219,7 @@ export function PopDeployModal({
if (!screen || !screenName || !screenCode) return;
screensToSend = [
{
sourceScreenId: screen.screenId,
sourceScreenId: screen.screen_id ?? screen.screenId,
screenName,
screenCode,
},
@@ -283,7 +283,7 @@ export function PopDeployModal({
{isGroupMode
? `"${groupName}" 카테고리의 화면 ${groupScreens!.length}개를 다른 회사로 복사합니다.`
: screen
? `"${screen.screenName}" (ID: ${screen.screenId}) 화면을 다른 회사로 복사합니다.`
? `"${screen.screen_name ?? screen.screenName}" (ID: ${screen.screen_id ?? screen.screenId}) 화면을 다른 회사로 복사합니다.`
: "화면을 선택해주세요."}
</DialogDescription>
</DialogHeader>
@@ -46,7 +46,7 @@ export function PopScreenPreview({ screen, className }: PopScreenPreviewProps) {
const checkLayout = async () => {
try {
setLoading(true);
const layout = await screenApi.getLayoutPop(screen.screenId);
const layout = await screenApi.getLayoutPop(screen.screen_id ?? screen.screenId);
// v2 레이아웃: sections는 객체 (Record<string, PopSectionDefinition>)
// v1 레이아웃: sections는 배열
@@ -71,7 +71,7 @@ export function PopScreenPreview({ screen, className }: PopScreenPreviewProps) {
}, [screen]);
// 미리보기 URL
const previewUrl = screen ? `/pop/screens/${screen.screenId}?preview=true&device=${deviceType}` : null;
const previewUrl = screen ? `/pop/screens/${screen.screen_id ?? screen.screenId}?preview=true&device=${deviceType}` : null;
// 새 탭에서 열기
const openInNewTab = () => {
@@ -98,7 +98,7 @@ export function PopScreenPreview({ screen, className }: PopScreenPreviewProps) {
<h3 className="text-sm font-medium"></h3>
{screen && (
<span className="text-xs text-muted-foreground truncate max-w-[150px]">
{screen.screenName}
{screen.screen_name ?? screen.screenName}
</span>
)}
</div>
@@ -88,7 +88,7 @@ export function PopScreenSettingModal({
if (!open || !screen) return;
// 화면 정보 설정
setScreenName(screen.screenName || "");
setScreenName((screen.screen_name ?? screen.screenName) || "");
setScreenDescription(screen.description || "");
setScreenIcon("");
setSelectedCategoryId("");
@@ -114,7 +114,7 @@ export function PopScreenSettingModal({
try {
setLoading(true);
const layout = await screenApi.getLayoutPop(screen.screenId);
const layout = await screenApi.getLayoutPop(screen.screen_id ?? screen.screenId);
if (layout && layout.subScreens) {
setSubScreens(
@@ -172,8 +172,8 @@ export function PopScreenSettingModal({
};
// screen_definitions 테이블에 화면명/설명 업데이트
if (screenName !== screen.screenName || screenDescription !== (screen.description || "")) {
await screenApi.updateScreenInfo(screen.screenId, {
if (screenName !== (screen.screen_name ?? screen.screenName) || screenDescription !== (screen.description || "")) {
await screenApi.updateScreenInfo(screen.screen_id ?? screen.screenId, {
screenName,
description: screenDescription,
isActive: "Y",
@@ -181,7 +181,7 @@ export function PopScreenSettingModal({
}
// 레이아웃에 하위 화면 정보 저장
const currentLayout = await screenApi.getLayoutPop(screen.screenId);
const currentLayout = await screenApi.getLayoutPop(screen.screen_id ?? screen.screenId);
const updatedLayout = {
...currentLayout,
version: "pop-1.0",
@@ -192,7 +192,7 @@ export function PopScreenSettingModal({
})),
};
await screenApi.saveLayoutPop(screen.screenId, updatedLayout);
await screenApi.saveLayoutPop(screen.screen_id ?? screen.screenId, updatedLayout);
toast.success("화면 설정이 저장되었습니다.");
onSave?.(screenUpdate);
@@ -213,7 +213,7 @@ export function PopScreenSettingModal({
<DialogHeader className="px-6 pt-6 pb-4 shrink-0">
<DialogTitle className="text-base sm:text-lg">POP </DialogTitle>
<DialogDescription className="text-xs sm:text-sm">
{screen.screenName} [{screen.screenCode}]
{screen.screen_name ?? screen.screenName} [{screen.screen_code ?? screen.screenCode}]
</DialogDescription>
</DialogHeader>
@@ -228,7 +228,7 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated, isPop
screenCode: screenCode.trim(),
companyCode,
description: description.trim() || undefined,
createdBy: (user as any)?.userId,
createdBy: (user as any)?.user_id,
dataSourceType: dataSourceType,
};
@@ -1599,7 +1599,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
// console.log("💾 저장 시작");
// ✅ 사용자 정보가 로드되지 않았으면 저장 불가
if (!user?.userId) {
if (!user?.user_id) {
alert("사용자 정보를 불러오는 중입니다. 잠시 후 다시 시도해주세요.");
return;
}
@@ -1743,14 +1743,14 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
(allComponents.find(c => (c as any).columnName) as any)?.tableName ||
"dynamic_form_data"; // 기본값
// 🆕 자동으로 작성자 정보 추가 (user.userId가 확실히 있음)
const writerValue = user.userId;
const companyCodeValue = user.companyCode || "";
// 🆕 자동으로 작성자 정보 추가 (user.user_id가 확실히 있음)
const writerValue = user.user_id;
const companyCodeValue = user.company_code || "";
console.log("👤 현재 사용자 정보:", {
userId: user.userId,
userId: user.user_id,
userName: userName,
companyCode: user.companyCode, // ✅ 회사 코드
companyCode: user.company_code, // ✅ 회사 코드
formDataWriter: mappedData.writer, // ✅ 폼에서 입력한 writer 값
formDataCompanyCode: mappedData.company_code, // ✅ 폼에서 입력한 company_code 값
defaultWriterValue: writerValue,
@@ -2093,7 +2093,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
componentId: fileComponent.id,
currentUploadedFiles: fileComponent.uploadedFiles?.length || 0,
hasOnFormDataChange: !!onFormDataChange,
userInfo: user ? { userId: user.userId, companyCode: user.companyCode } : "no user"
userInfo: user ? { userId: user.user_id, companyCode: user.company_code } : "no user"
});
const handleFileUpdate = useCallback(async (updates: Partial<FileComponent>) => {
@@ -423,9 +423,9 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
screenId={screenInfo?.id}
tableName={screenInfo?.tableName}
menuObjid={menuObjid}
userId={user?.userId}
userName={user?.userName}
companyCode={user?.companyCode}
userId={user?.user_id}
userName={user?.user_name}
companyCode={user?.company_code}
onSave={onSave}
allComponents={allComponents}
selectedRowsData={selectedRowsData}
@@ -797,13 +797,13 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
if (mapping.userField) {
switch (mapping.userField) {
case "userId":
value = user?.userId;
value = user?.user_id;
break;
case "userName":
value = userName;
break;
case "companyCode":
value = user?.companyCode;
value = user?.company_code;
break;
case "deptCode":
value = authUser?.deptCode;
@@ -224,9 +224,9 @@ export const OptimizedButtonComponent: React.FC<OptimizedButtonProps> = ({
config.dataflowConfig.flowConfig,
{
buttonId: component.id,
screenId: component.screenId,
screenId: component.screen_id,
companyCode,
userId: formData.userId ?? formData.user_id,
userId: formData.user_id,
formData,
selectedRows: selectedRows || [],
selectedRowsData: selectedRowsData || [],
@@ -405,17 +405,17 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
// 선택된 화면의 테이블만 로드 (최적화된 API 사용)
useEffect(() => {
const fetchScreenTable = async () => {
if (!selectedScreen?.tableName) {
if (!selectedScreen?.table_name) {
setTables([]);
return;
}
try {
// console.log(`=== 테이블 정보 조회 시작: ${selectedScreen.tableName} ===`);
// console.log(`=== 테이블 정보 조회 시작: ${selectedScreen.table_name} ===`);
const startTime = performance.now();
// 최적화된 단일 테이블 조회 API 사용
const response = await apiClient.get(`/screen-management/tables/${selectedScreen.tableName}`);
const response = await apiClient.get(`/screen-management/tables/${selectedScreen.table_name}`);
const endTime = performance.now();
// console.log(`테이블 조회 완료: ${(endTime - startTime).toFixed(2)}ms`);
@@ -423,20 +423,20 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
const data = response.data;
if (data.success && data.data) {
setTables([data.data]);
// console.log(`테이블 ${selectedScreen.tableName} 로드 완료, 컬럼 ${data.data.columns.length}개`);
// console.log(`테이블 ${selectedScreen.table_name} 로드 완료, 컬럼 ${data.data.columns.length}개`);
} else {
// console.error("테이블 조회 실패:", data.message);
setTables([createMockTableForScreen(selectedScreen.tableName)]);
setTables([createMockTableForScreen(selectedScreen.table_name)]);
}
} catch (error) {
// console.error("테이블 조회 중 오류:", error);
// 선택된 화면의 테이블에 대한 임시 데이터 생성
setTables([createMockTableForScreen(selectedScreen.tableName)]);
setTables([createMockTableForScreen(selectedScreen.table_name)]);
}
};
fetchScreenTable();
}, [selectedScreen?.tableName]);
}, [selectedScreen?.table_name]);
// 검색된 테이블 필터링
const filteredTables = useMemo(() => {
@@ -968,7 +968,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
try {
setIsSaving(true);
await screenApi.saveLayout(selectedScreen.screenId, layout);
await screenApi.saveLayout(selectedScreen.screen_id, layout);
setHasUnsavedChanges(false); // 저장 완료 시 변경사항 플래그 해제
toast.success("레이아웃이 성공적으로 저장되었습니다.");
} catch (error) {
@@ -985,7 +985,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
try {
setIsLoading(true);
const savedLayout = await screenApi.getLayout(selectedScreen.screenId);
const savedLayout = await screenApi.getLayout(selectedScreen.screen_id);
if (savedLayout && savedLayout.components) {
// 격자 설정이 없는 경우 기본값 추가
@@ -1386,8 +1386,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
<div className="flex h-screen w-full flex-col bg-gray-100">
{/* 상단 툴바 */}
<DesignerToolbar
screenName={selectedScreen?.screenName}
tableName={selectedScreen?.tableName}
screenName={selectedScreen?.screen_name}
tableName={selectedScreen?.table_name}
onBack={onBackToList}
onSave={handleSave}
onUndo={undo}
@@ -1416,7 +1416,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
onDrop={handleDrop}
onDragOver={handleDragOver}
<Badge variant="outline" className="font-mono">
{selectedScreen.tableName}
{selectedScreen.table_name}
</Badge>
{clipboard && clipboard.data.length > 0 && (
<Badge variant="secondary" className="text-xs">
@@ -1578,10 +1578,10 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
{selectedScreen && (
<div className="mt-2 rounded-md bg-accent p-3">
<div className="text-sm font-medium text-blue-900"> </div>
<div className="text-xs text-blue-700">{selectedScreen.screenName}</div>
<div className="text-xs text-blue-700">{selectedScreen.screen_name}</div>
<div className="mt-1 flex items-center space-x-2">
<Database className="h-3 w-3 text-primary" />
<span className="font-mono text-xs text-blue-800">{selectedScreen.tableName}</span>
<span className="font-mono text-xs text-blue-800">{selectedScreen.table_name}</span>
</div>
</div>
)}
@@ -1618,7 +1618,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
</h3>
<p className="text-xs text-gray-500">
{selectedScreen
? `${selectedScreen.tableName} 테이블의 컬럼 정보를 조회하고 있습니다.`
? `${selectedScreen.table_name} 테이블의 컬럼 정보를 조회하고 있습니다.`
: "화면을 선택하면 해당 테이블의 컬럼 정보가 표시됩니다."}
</p>
</div>
@@ -116,7 +116,7 @@ export function ScreenGroupModal({
const response = await apiClient.get("/auth/me");
const result = response.data;
if (result.success && result.data) {
const companyCode = result.data.companyCode || result.data.company_code || "";
const companyCode = result.data.company_code || "";
setCurrentCompanyCode(companyCode);
setIsSuperAdmin(companyCode === "*");
}
@@ -185,10 +185,10 @@ export function ScreenGroupTreeView({
const [isSyncCompanySelectOpen, setIsSyncCompanySelectOpen] = useState(false);
// 현재 사용자가 최고 관리자인지 확인
const isSuperAdmin = user?.companyCode === "*";
const isSuperAdmin = user?.company_code === "*";
// 실제 사용할 회사 코드 (props → 선택 → 사용자 기본값)
const effectiveCompanyCode = companyCode || selectedCompanyCode || (isSuperAdmin ? "" : user?.companyCode) || "";
const effectiveCompanyCode = companyCode || selectedCompanyCode || (isSuperAdmin ? "" : user?.company_code) || "";
// 그룹 목록 및 그룹별 화면 로드
useEffect(() => {
@@ -208,7 +208,7 @@ export function ScreenGroupTreeView({
// 미분류 화면들 (어떤 그룹에도 속하지 않은 화면)
const getUngroupedScreens = (): ScreenDefinition[] => {
const groupedIds = getGroupedScreenIds();
return screens.filter((screen) => !groupedIds.has(screen.screenId));
return screens.filter((screen) => !groupedIds.has(screen.screen_id));
};
// 그룹에 속한 화면들 (display_order 오름차순 정렬)
@@ -216,7 +216,7 @@ export function ScreenGroupTreeView({
const group = groups.find((g) => g.id === groupId);
if (!group?.screens) {
const screenIds = groupScreensMap.get(groupId) || [];
return screens.filter((screen) => screenIds.includes(screen.screenId));
return screens.filter((screen) => screenIds.includes(screen.screen_id));
}
// 그룹의 screens 배열에서 display_order 정보를 가져와서 정렬
@@ -225,7 +225,7 @@ export function ScreenGroupTreeView({
.map((s) => s.screen_id);
return sortedScreenIds
.map((id) => screens.find((screen) => screen.screenId === id))
.map((id) => screens.find((screen) => screen.screen_id === id))
.filter((screen): screen is ScreenDefinition => screen !== undefined);
};
@@ -259,7 +259,7 @@ export function ScreenGroupTreeView({
if (onScreenSelectInGroup) {
onScreenSelectInGroup(
{ id: group.id, name: group.group_name, company_code: group.company_code },
screen.screenId
screen.screen_id
);
} else {
// fallback: 기존 동작
@@ -297,8 +297,8 @@ export function ScreenGroupTreeView({
}
// 최고 관리자가 아니면 바로 상태 조회
if (!isSuperAdmin && user?.companyCode) {
const response = await getMenuScreenSyncStatus(user.companyCode);
if (!isSuperAdmin && user?.company_code) {
const response = await getMenuScreenSyncStatus(user.company_code);
if (response.success && response.data) {
setSyncStatus(response.data);
}
@@ -323,7 +323,7 @@ export function ScreenGroupTreeView({
// 동기화 실행
const handleSync = async (direction: "screen-to-menu" | "menu-to-screen") => {
// 사용할 회사 코드 결정
const targetCompanyCode = isSuperAdmin ? selectedCompanyCode : user?.companyCode;
const targetCompanyCode = isSuperAdmin ? selectedCompanyCode : user?.company_code;
if (!targetCompanyCode) {
toast.error("회사를 선택해주세요.");
@@ -550,9 +550,9 @@ export function ScreenGroupTreeView({
setDeleteProgress({
current: currentStep,
total: totalSteps,
message: `화면 삭제 중: ${screen.screenName}`
message: `화면 삭제 중: ${screen.screen_name}`
});
await screenApi.deleteScreen(screen.screenId, "그룹 삭제와 함께 삭제", true); // force: true로 의존성 무시
await screenApi.deleteScreen(screen.screen_id, "그룹 삭제와 함께 삭제", true); // force: true로 의존성 무시
}
console.log(`✅ 그룹 및 하위 그룹 내 화면 ${allScreens.length}개 삭제 완료`);
}
@@ -619,8 +619,8 @@ export function ScreenGroupTreeView({
try {
setIsScreenDeleting(true);
const { screenApi } = await import("@/lib/api/screen");
await screenApi.deleteScreen(deletingScreen.screenId, "사용자 요청으로 삭제");
toast.success(`"${deletingScreen.screenName}" 화면이 삭제되었습니다`);
await screenApi.deleteScreen(deletingScreen.screen_id, "사용자 요청으로 삭제");
toast.success(`"${deletingScreen.screen_name}" 화면이 삭제되었습니다`);
await loadGroupsData();
window.dispatchEvent(new CustomEvent("screen-list-refresh"));
} catch (error) {
@@ -636,17 +636,17 @@ export function ScreenGroupTreeView({
// 화면 수정 모달 열기 (이름 변경 + 그룹 이동)
const handleOpenEditScreenModal = (screen: ScreenDefinition) => {
setEditingScreen(screen);
setEditScreenName(screen.screenName);
setEditScreenName(screen.screen_name);
// 현재 화면이 속한 그룹 정보 찾기
let currentGroupId: number | null = null;
let currentScreenRole: string = "";
let currentDisplayOrder: number = 1;
// 현재 화면이 속한 그룹 찾기
for (const group of groups) {
if (group.screens && Array.isArray(group.screens)) {
const screenInfo = group.screens.find((s: any) => Number(s.screen_id) === Number(screen.screenId));
const screenInfo = group.screens.find((s: any) => Number(s.screen_id) === Number(screen.screen_id));
if (screenInfo) {
currentGroupId = group.id;
currentScreenRole = screenInfo.screen_role || "";
@@ -668,7 +668,7 @@ export function ScreenGroupTreeView({
let currentGroupId: number | null = null;
for (const group of groups) {
if (group.screens && Array.isArray(group.screens)) {
const found = group.screens.find((s: any) => Number(s.screen_id) === Number(screen.screenId));
const found = group.screens.find((s: any) => Number(s.screen_id) === Number(screen.screen_id));
if (found) {
currentGroupId = group.id;
break;
@@ -736,15 +736,15 @@ export function ScreenGroupTreeView({
try {
// 1. 화면 이름이 변경되었으면 업데이트
if (editScreenName.trim() && editScreenName !== editingScreen.screenName) {
await screenApi.updateScreen(editingScreen.screenId, {
if (editScreenName.trim() && editScreenName !== editingScreen.screen_name) {
await screenApi.updateScreen(editingScreen.screen_id, {
screenName: editScreenName.trim(),
});
}
// 2. 현재 그룹에서 제거
const currentGroupId = Array.from(groupScreensMap.entries()).find(([_, screenIds]) =>
screenIds.includes(editingScreen.screenId)
screenIds.includes(editingScreen.screen_id)
)?.[0];
if (currentGroupId) {
@@ -752,7 +752,7 @@ export function ScreenGroupTreeView({
const currentGroup = groups.find((g) => g.id === currentGroupId);
if (currentGroup && currentGroup.screens) {
const screenGroupScreen = currentGroup.screens.find(
(s: any) => s.screen_id === editingScreen.screenId
(s: any) => s.screen_id === editingScreen.screen_id
);
if (screenGroupScreen) {
await removeScreenFromGroup(screenGroupScreen.id);
@@ -764,7 +764,7 @@ export function ScreenGroupTreeView({
if (selectedGroupForMove !== null) {
await addScreenToGroup({
group_id: selectedGroupForMove,
screen_id: editingScreen.screenId,
screen_id: editingScreen.screen_id,
screen_role: screenRole,
display_order: displayOrder,
is_default: "N",
@@ -1295,20 +1295,20 @@ export function ScreenGroupTreeView({
) : (
grandScreens.map((screen) => (
<div
key={screen.screenId}
key={screen.screen_id}
className={cn(
"flex items-center gap-2 rounded-md px-2 py-1.5 cursor-pointer transition-colors duration-150",
"text-xs hover:bg-muted/60",
selectedScreen?.screenId === screen.screenId && "bg-primary/10 border-l-2 border-primary"
selectedScreen?.screen_id === screen.screen_id && "bg-primary/10 border-l-2 border-primary"
)}
onClick={() => handleScreenClickInGroup(screen, grandChild)}
onDoubleClick={() => handleScreenDoubleClick(screen)}
onContextMenu={(e) => handleContextMenu(e, screen)}
>
<Monitor className="h-3 w-3 shrink-0 text-primary" />
<span className="truncate flex-1">{screen.screenName}</span>
<span className="truncate flex-1">{screen.screen_name}</span>
<span className="text-[10px] text-muted-foreground truncate max-w-[80px]">
{screen.screenCode}
{screen.screen_code}
</span>
</div>
))
@@ -1331,20 +1331,20 @@ export function ScreenGroupTreeView({
) : (
childScreens.map((screen) => (
<div
key={screen.screenId}
key={screen.screen_id}
className={cn(
"flex items-center gap-2 rounded-md px-2 py-1.5 cursor-pointer transition-colors duration-150",
"text-xs hover:bg-muted/60",
selectedScreen?.screenId === screen.screenId && "bg-primary/10 border-l-2 border-primary"
selectedScreen?.screen_id === screen.screen_id && "bg-primary/10 border-l-2 border-primary"
)}
onClick={() => handleScreenClickInGroup(screen, childGroup)}
onDoubleClick={() => handleScreenDoubleClick(screen)}
onContextMenu={(e) => handleContextMenu(e, screen)}
>
<Monitor className="h-3 w-3 shrink-0 text-primary" />
<span className="truncate flex-1">{screen.screenName}</span>
<span className="truncate flex-1">{screen.screen_name}</span>
<span className="text-[10px] text-muted-foreground truncate max-w-[80px]">
{screen.screenCode}
{screen.screen_code}
</span>
</div>
))
@@ -1367,20 +1367,20 @@ export function ScreenGroupTreeView({
) : (
groupScreens.map((screen) => (
<div
key={screen.screenId}
key={screen.screen_id}
className={cn(
"flex items-center gap-2 rounded-md px-2 py-1.5 cursor-pointer transition-colors duration-150",
"text-sm hover:bg-muted/60 group/screen",
selectedScreen?.screenId === screen.screenId && "bg-primary/10 border-l-2 border-primary"
selectedScreen?.screen_id === screen.screen_id && "bg-primary/10 border-l-2 border-primary"
)}
onClick={() => handleScreenClickInGroup(screen, group)}
onDoubleClick={() => handleScreenDoubleClick(screen)}
onContextMenu={(e) => handleContextMenu(e, screen)}
>
<Monitor className="h-4 w-4 shrink-0 text-primary" />
<span className="truncate flex-1">{screen.screenName}</span>
<span className="truncate flex-1">{screen.screen_name}</span>
<span className="text-xs text-muted-foreground truncate max-w-[100px]">
{screen.screenCode}
{screen.screen_code}
</span>
</div>
))
@@ -1417,20 +1417,20 @@ export function ScreenGroupTreeView({
<div className="ml-4 mt-1 space-y-0.5">
{ungroupedScreens.map((screen) => (
<div
key={screen.screenId}
key={screen.screen_id}
className={cn(
"flex items-center gap-2 rounded-md px-2 py-1.5 cursor-pointer transition-colors duration-150",
"text-sm hover:bg-muted/60",
selectedScreen?.screenId === screen.screenId && "bg-primary/10 border-l-2 border-primary"
selectedScreen?.screen_id === screen.screen_id && "bg-primary/10 border-l-2 border-primary"
)}
onClick={() => handleScreenClick(screen)}
onDoubleClick={() => handleScreenDoubleClick(screen)}
onContextMenu={(e) => handleContextMenu(e, screen)}
>
<Monitor className="h-4 w-4 shrink-0 text-primary" />
<span className="truncate flex-1">{screen.screenName}</span>
<span className="truncate flex-1">{screen.screen_name}</span>
<span className="text-xs text-muted-foreground truncate max-w-[100px]">
{screen.screenCode}
{screen.screen_code}
</span>
</div>
))}
@@ -1639,7 +1639,7 @@ export function ScreenGroupTreeView({
<div className="text-xs sm:text-sm">
<div className="mt-2 rounded-md bg-destructive/10 border border-destructive/30 p-3">
<p className="font-semibold text-destructive">
&quot;{deletingScreen?.screenName}&quot; ?
&quot;{deletingScreen?.screen_name}&quot; ?
</p>
<p className="mt-2 text-destructive/80">
, . .
@@ -183,7 +183,7 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
// 그룹 또는 화면이 변경될 때 포커스 초기화
useEffect(() => {
setFocusedScreenId(null);
}, [selectedGroup?.id, screen?.screenId]);
}, [selectedGroup?.id, screen?.screen_id]);
// 외부에서 전달된 초기 포커스 ID 적용 (화면 이동 없이 강조만)
useEffect(() => {
@@ -286,35 +286,35 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
// 화면-테이블 매핑 저장 (포커스 시 연결선 강조용)
const newScreenTableMap: Record<number, string> = {};
screenList.forEach((scr: any) => {
if (scr.tableName) {
newScreenTableMap[scr.screenId] = scr.tableName;
if (scr.table_name) {
newScreenTableMap[scr.screen_id] = scr.table_name;
}
});
setScreenTableMap(newScreenTableMap);
// 관계 데이터 로드 (첫 번째 화면 기준)
const [joinsRes, flowsRes, relationsRes] = await Promise.all([
getFieldJoins(screenList[0].screenId).catch(() => ({ success: false, data: [] })),
getFieldJoins(screenList[0].screen_id).catch(() => ({ success: false, data: [] })),
getDataFlows().catch(() => ({ success: false, data: [] })),
getTableRelations({ screen_id: screenList[0].screenId }).catch(() => ({ success: false, data: [] })),
getTableRelations({ screen_id: screenList[0].screen_id }).catch(() => ({ success: false, data: [] })),
]);
const joins = joinsRes.success ? joinsRes.data || [] : [];
const flows = flowsRes.success ? flowsRes.data || [] : [];
const relations = relationsRes.success ? relationsRes.data || [] : [];
// 데이터 흐름에서 연결된 화면들 추가 (개별 화면 모드에서만 - 그룹 모드에서는 그룹 내 화면만 표시)
if (!selectedGroup && screen) {
flows.forEach((flow: any) => {
if (flow.source_screen_id === screen.screenId && flow.target_screen_id) {
const exists = screenList.some((s) => s.screenId === flow.target_screen_id);
if (flow.source_screen_id === screen.screen_id && flow.target_screen_id) {
const exists = screenList.some((s) => s.screen_id === flow.target_screen_id);
if (!exists) {
screenList.push({
screenId: flow.target_screen_id,
screenName: flow.target_screen_name || `화면 ${flow.target_screen_id}`,
screenCode: "",
tableName: "",
companyCode: screen.companyCode,
screen_id: flow.target_screen_id,
screen_name: flow.target_screen_name || `화면 ${flow.target_screen_id}`,
screen_code: "",
table_name: "",
company_code: screen.company_code,
isActive: "Y",
createdDate: new Date(),
updatedDate: new Date(),
@@ -325,7 +325,7 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
}
// 화면 레이아웃 요약 정보 로드
const screenIds = screenList.map((s) => s.screenId);
const screenIds = screenList.map((s) => s.screen_id);
let layoutSummaries: Record<number, ScreenLayoutSummary> = {};
let subTablesData: Record<number, ScreenSubTablesData> = {};
try {
@@ -359,14 +359,14 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
// 화면별 사용 컬럼 정보 추출 (layoutSummaries에서)
const usedColumnsMap: Record<number, Record<string, string[]>> = {};
screenList.forEach((screenItem) => {
const layout = layoutSummaries[screenItem.screenId];
const layout = layoutSummaries[screenItem.screen_id];
if (layout && layout.layoutItems) {
const mainTable = screenItem.tableName;
const mainTable = screenItem.table_name;
if (mainTable) {
// layoutItems에서 사용 컬럼과 조인 컬럼 추출
const allUsedColumns: string[] = [];
const allJoinColumns: string[] = [];
layout.layoutItems.forEach((item) => {
// usedColumns 배열에서 추출 (columns_config에서 가져온 컬럼명)
if (item.usedColumns && Array.isArray(item.usedColumns)) {
@@ -389,13 +389,13 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
allUsedColumns.push(item.bindField);
}
});
if (!usedColumnsMap[screenItem.screenId]) {
usedColumnsMap[screenItem.screenId] = {};
if (!usedColumnsMap[screenItem.screen_id]) {
usedColumnsMap[screenItem.screen_id] = {};
}
// 사용 컬럼과 조인 컬럼을 별도 키로 저장
usedColumnsMap[screenItem.screenId][mainTable] = allUsedColumns;
usedColumnsMap[screenItem.screenId][`${mainTable}__join`] = allJoinColumns;
usedColumnsMap[screenItem.screen_id][mainTable] = allUsedColumns;
usedColumnsMap[screenItem.screen_id][`${mainTable}__join`] = allJoinColumns;
}
}
});
@@ -418,18 +418,18 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
};
screenList.forEach((scr: any, idx) => {
const isMain = screen && scr.screenId === screen.screenId;
const summary = layoutSummaries[scr.screenId];
const isMain = screen && scr.screen_id === screen.screen_id;
const summary = layoutSummaries[scr.screen_id];
const roleLabel = getRoleLabel(scr.screenRole);
// 포커스 여부 결정 (그룹 모드 & 개별 화면 모드 모두 지원)
const isInGroup = !!selectedGroup;
let isFocused: boolean;
let isFaded: boolean;
if (isInGroup) {
// 그룹 모드: 클릭한 화면만 포커스
isFocused = focusedScreenId === scr.screenId;
isFocused = focusedScreenId === scr.screen_id;
isFaded = focusedScreenId !== null && !isFocused;
} else {
// 개별 화면 모드: 메인 화면(선택된 화면)만 포커스, 연결 화면은 흐리게
@@ -438,15 +438,15 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
}
screenNodes.push({
id: `screen-${scr.screenId}`,
id: `screen-${scr.screen_id}`,
type: "screenNode",
position: { x: screenStartX + idx * (NODE_WIDTH + NODE_GAP), y: SCREEN_Y },
data: {
label: scr.screenName,
label: scr.screen_name,
subLabel: selectedGroup ? `${roleLabel} (#${scr.displayOrder || idx + 1})` : (isMain ? "메인 화면" : "연결 화면"),
type: "screen",
isMain: selectedGroup ? idx === 0 : !!isMain,
tableName: scr.tableName,
tableName: scr.table_name,
layoutSummary: summary,
// 화면 포커스 관련 속성 (그룹 모드 & 개별 모드 공통)
isInGroup,
@@ -464,8 +464,8 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
// 모든 화면의 메인 테이블 추가
screenList.forEach((scr) => {
if (scr.tableName) {
mainTableSet.add(scr.tableName);
if (scr.table_name) {
mainTableSet.add(scr.table_name);
}
});
@@ -577,10 +577,10 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
// 각 테이블이 어떤 화면들의 메인 테이블인지 매핑
const tableToScreensMap = new Map<string, string[]>();
screenList.forEach((scr: any) => {
if (scr.tableName) {
const screens = tableToScreensMap.get(scr.tableName) || [];
screens.push(scr.screenName);
tableToScreensMap.set(scr.tableName, screens);
if (scr.table_name) {
const screens = tableToScreensMap.get(scr.table_name) || [];
screens.push(scr.screen_name);
tableToScreensMap.set(scr.table_name, screens);
}
});
@@ -702,8 +702,8 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
newEdges.push({
id: `edge-screen-flow-${i}`,
source: `screen-${currentScreen.screenId}`,
target: `screen-${nextScreen.screenId}`,
source: `screen-${currentScreen.screen_id}`,
target: `screen-${nextScreen.screen_id}`,
sourceHandle: "right",
targetHandle: "left",
type: "animatedFlow",
@@ -722,11 +722,11 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
// 각 화면 → 해당 메인 테이블 연결선 생성 (실선)
// 모든 화면-테이블 연결은 동일한 스타일 (각 화면의 메인 테이블이므로)
screenList.forEach((scr) => {
if (scr.tableName && mainTableSet.has(scr.tableName)) {
if (scr.table_name && mainTableSet.has(scr.table_name)) {
newEdges.push({
id: `edge-screen-table-${scr.screenId}`,
source: `screen-${scr.screenId}`,
target: `table-${scr.tableName}`,
id: `edge-screen-table-${scr.screen_id}`,
source: `screen-${scr.screen_id}`,
target: `table-${scr.table_name}`,
sourceHandle: "bottom",
targetHandle: "top",
type: "animatedFlow",
@@ -1018,15 +1018,15 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
// 테이블 관계 엣지 (추가 관계) - 참조용 화면(개별 모드: screen, 그룹 모드: screenList[0])
const refScreen = screen ?? screenList[0];
relations.forEach((rel: any, idx: number) => {
if (rel.table_name && rel.table_name !== refScreen.tableName) {
if (rel.table_name && rel.table_name !== refScreen.table_name) {
// 화면 → 연결 테이블
const edgeExists = newEdges.some(
(e) => e.source === `screen-${refScreen.screenId}` && e.target === `table-${rel.table_name}`
(e) => e.source === `screen-${refScreen.screen_id}` && e.target === `table-${rel.table_name}`
);
if (!edgeExists) {
newEdges.push({
id: `edge-rel-${idx}`,
source: `screen-${refScreen.screenId}`,
source: `screen-${refScreen.screen_id}`,
target: `table-${rel.table_name}`,
sourceHandle: "bottom",
targetHandle: "top",
@@ -1044,12 +1044,12 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
// 데이터 흐름 엣지 (화면 간)
flows
.filter((flow: any) => flow.source_screen_id === refScreen.screenId)
.filter((flow: any) => flow.source_screen_id === refScreen.screen_id)
.forEach((flow: any, idx: number) => {
if (flow.target_screen_id) {
newEdges.push({
id: `edge-flow-${idx}`,
source: `screen-${refScreen.screenId}`,
source: `screen-${refScreen.screen_id}`,
target: `screen-${flow.target_screen_id}`,
sourceHandle: "right",
targetHandle: "left",
@@ -1937,7 +1937,7 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
const styledEdges = React.useMemo(() => {
// 개별 화면 모드: 메인 화면의 연결선만 강조
if (!selectedGroup && screen) {
const mainScreenId = screen.screenId;
const mainScreenId = screen.screen_id;
return edges.map((edge) => {
// 화면 간 연결선
@@ -2418,8 +2418,8 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
{screen && !selectedGroup && (
<>
<Monitor className="h-4 w-4 text-primary" />
<span className="text-sm font-medium">{screen.screenName}</span>
<span className="text-xs text-muted-foreground/80 dark:text-muted-foreground/50 font-mono">{screen.screenCode}</span>
<span className="text-sm font-medium">{screen.screen_name}</span>
<span className="text-xs text-muted-foreground/80 dark:text-muted-foreground/50 font-mono">{screen.screen_code}</span>
</>
)}
@@ -360,11 +360,11 @@ export function TableSettingModal({
screensResponse.data.forEach((screen: any) => {
// 메인 테이블로 사용하는 경우
if (screen.tableName === tableName) {
if (screen.table_name === tableName) {
usingScreens.push({
screenId: screen.screenId,
screenName: screen.screenName,
screenCode: screen.screenCode,
screenId: screen.screen_id,
screenName: screen.screen_name,
screenCode: screen.screen_code,
tableRole: "main",
});
}
@@ -106,8 +106,8 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({ component,
if (response.data.success && Array.isArray(response.data.data)) {
const screenList = response.data.data.map((screen: any) => ({
id: screen.screenId,
name: screen.screenName,
id: screen.screen_id,
name: screen.screen_name,
description: screen.description,
}));
setScreens(screenList);
@@ -212,8 +212,8 @@ export const ActionTab: React.FC<ButtonTabProps> = ({
if (response.data.success && response.data.data) {
const tables = response.data.data.map((table: any) => ({
name: table.tableName,
label: table.displayName || table.tableName,
name: table.table_name,
label: table.display_name || table.table_name,
}));
setAvailableTables(tables);
}
@@ -249,8 +249,8 @@ export const ActionTab: React.FC<ButtonTabProps> = ({
if (Array.isArray(columnData)) {
const columns = columnData.map((col: any) => {
const name = col.name || col.columnName;
const label = col.displayName || col.label || col.columnLabel || name;
const name = col.name || col.column_name;
const label = col.display_name || col.label || col.column_label || name;
console.log(` - 컬럼: ${name} → "${label}"`);
return { name, label };
});
@@ -276,8 +276,8 @@ export const ActionTab: React.FC<ButtonTabProps> = ({
if (Array.isArray(columnData)) {
return columnData.map((col: any) => ({
name: col.name || col.columnName,
label: col.displayName || col.label || col.columnLabel || col.name || col.columnName,
name: col.name || col.column_name,
label: col.display_name || col.label || col.column_label || col.name || col.column_name,
}));
}
}
@@ -370,10 +370,10 @@ export const ActionTab: React.FC<ButtonTabProps> = ({
try {
const screenResponse = await apiClient.get(`/screen-management/screens/${targetScreenId}`);
if (screenResponse.data.success && screenResponse.data.data) {
targetTableName = screenResponse.data.data.tableName || null;
} else if (screenResponse.data?.tableName) {
targetTableName = screenResponse.data.data.table_name || null;
} else if (screenResponse.data?.table_name) {
// 직접 데이터 반환 형식인 경우
targetTableName = screenResponse.data.tableName || null;
targetTableName = screenResponse.data.table_name || null;
}
} catch (error) {
console.error("대상 화면 정보 로드 실패:", error);
@@ -392,8 +392,8 @@ export const ActionTab: React.FC<ButtonTabProps> = ({
if (Array.isArray(columnData)) {
const columns = columnData.map((col: any) => ({
name: col.name || col.columnName,
label: col.displayName || col.label || col.columnLabel || col.name || col.columnName,
name: col.name || col.column_name,
label: col.display_name || col.label || col.column_label || col.name || col.column_name,
}));
setModalActionSourceColumns(columns);
}
@@ -414,8 +414,8 @@ export const ActionTab: React.FC<ButtonTabProps> = ({
if (Array.isArray(columnData)) {
const columns = columnData.map((col: any) => ({
name: col.name || col.columnName,
label: col.displayName || col.label || col.columnLabel || col.name || col.columnName,
name: col.name || col.column_name,
label: col.display_name || col.label || col.column_label || col.name || col.column_name,
}));
setModalActionTargetColumns(columns);
}
@@ -459,8 +459,8 @@ export const ActionTab: React.FC<ButtonTabProps> = ({
if (Array.isArray(columnData)) {
const columns = columnData.map((col: any) => ({
name: col.name || col.columnName,
label: col.displayName || col.label || col.columnLabel || col.name || col.columnName,
name: col.name || col.column_name,
label: col.display_name || col.label || col.column_label || col.name || col.column_name,
}));
setCurrentTableColumns(columns);
}
@@ -556,14 +556,12 @@ export const ActionTab: React.FC<ButtonTabProps> = ({
if (Array.isArray(columnData)) {
const columns = columnData.map((col: any) => ({
name: col.name || col.columnName || col.column_name,
name: col.name || col.column_name,
label:
col.displayName ||
col.label ||
col.columnLabel ||
col.display_name ||
col.label ||
col.column_label ||
col.name ||
col.columnName ||
col.column_name,
}));
setModalSourceColumns(columns);
@@ -584,7 +582,7 @@ export const ActionTab: React.FC<ButtonTabProps> = ({
console.log("[openModalWithData] 타겟 화면 응답:", screenResponse.data);
if (screenResponse.data.success && screenResponse.data.data) {
const targetTableName = screenResponse.data.data.tableName;
const targetTableName = screenResponse.data.data.table_name;
console.log("[openModalWithData] 타겟 화면 테이블명:", targetTableName);
if (targetTableName) {
@@ -596,14 +594,12 @@ export const ActionTab: React.FC<ButtonTabProps> = ({
if (Array.isArray(columnData)) {
const columns = columnData.map((col: any) => ({
name: col.name || col.columnName || col.column_name,
name: col.name || col.column_name,
label:
col.displayName ||
col.label ||
col.columnLabel ||
col.display_name ||
col.label ||
col.column_label ||
col.name ||
col.columnName ||
col.column_name,
}));
setModalTargetColumns(columns);
@@ -647,8 +643,8 @@ export const ActionTab: React.FC<ButtonTabProps> = ({
if (response.data.success && Array.isArray(response.data.data)) {
const screenList = response.data.data.map((screen: any) => ({
id: screen.screenId,
name: screen.screenName,
id: screen.screen_id,
name: screen.screen_name,
description: screen.description,
}));
setScreens(screenList);
@@ -721,8 +717,8 @@ export const ActionTab: React.FC<ButtonTabProps> = ({
// ID 컬럼과 날짜 관련 컬럼 제외
const filteredColumns = columnData
.filter((col: any) => {
const colName = col.columnName.toLowerCase();
const dataType = col.dataType?.toLowerCase() || "";
const colName = col.column_name.toLowerCase();
const dataType = col.data_type?.toLowerCase() || "";
// ID 컬럼 제외 (id, _id로 끝나는 컬럼)
if (colName === "id" || colName.endsWith("_id")) {
@@ -747,7 +743,7 @@ export const ActionTab: React.FC<ButtonTabProps> = ({
return true;
})
.map((col: any) => col.columnName);
.map((col: any) => col.column_name);
setTableColumns(filteredColumns);
}
@@ -3935,12 +3931,12 @@ const MasterDetailExcelUploadConfig: React.FC<{
const response = await apiClient.get(`/table-management/tables/${masterTable}/columns`);
if (response.data?.success && response.data?.data?.columns) {
const cols = response.data.data.columns.map((col: any) => ({
columnName: col.columnName || col.column_name,
columnLabel: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name,
inputType: col.inputType || col.input_type || "text",
referenceTable: col.referenceTable || col.reference_table,
referenceColumn: col.referenceColumn || col.reference_column,
displayColumn: col.displayColumn || col.display_column,
columnName: col.column_name,
columnLabel: col.display_name || col.column_label || col.column_name,
inputType: col.input_type || "text",
referenceTable: col.reference_table,
referenceColumn: col.reference_column,
displayColumn: col.display_column,
}));
setMasterColumns(cols);
}
@@ -3968,8 +3964,8 @@ const MasterDetailExcelUploadConfig: React.FC<{
const response = await apiClient.get(`/table-management/tables/${field.referenceTable}/columns`);
if (response.data?.success && response.data?.data?.columns) {
const cols = response.data.data.columns.map((c: any) => ({
name: c.columnName || c.column_name,
label: c.displayName || c.columnLabel || c.column_label || c.columnName || c.column_name,
name: c.column_name,
label: c.display_name || c.column_label || c.column_name,
}));
setRefTableColumns((prev) => ({
...prev,
@@ -4004,11 +4000,11 @@ const MasterDetailExcelUploadConfig: React.FC<{
if (response.data?.success && response.data?.data?.columns) {
const columns = response.data.data.columns;
// referenceTable이 마스터 테이블인 컬럼 찾기
const fkColumn = columns.find((col: any) => col.referenceTable === masterTable);
const fkColumn = columns.find((col: any) => col.reference_table === masterTable);
if (fkColumn) {
const detailFk = fkColumn.columnName || fkColumn.column_name;
const masterKey = fkColumn.referenceColumn || fkColumn.reference_column;
const detailFk = fkColumn.column_name;
const masterKey = fkColumn.reference_column;
setRelationInfo({
masterTable,
@@ -335,8 +335,8 @@ export const V2ButtonConfigPanel: React.FC<V2ButtonConfigPanelProps> = ({
const response = await apiClient.get("/table-management/tables");
if (response.data.success && response.data.data) {
const tables = response.data.data.map((t: any) => ({
name: t.tableName || t.name,
label: t.displayName || t.tableLabel || t.label || t.tableName || t.name,
name: t.table_name || t.name,
label: t.display_name || t.table_label || t.label || t.table_name || t.name,
}));
setAvailableTables(tables);
}
@@ -357,8 +357,8 @@ export const V2ButtonConfigPanel: React.FC<V2ButtonConfigPanelProps> = ({
if (!Array.isArray(columnData) && columnData?.data) columnData = columnData.data;
if (Array.isArray(columnData)) {
return columnData.map((col: any) => ({
name: col.name || col.columnName,
label: col.displayName || col.label || col.columnLabel || col.name || col.columnName,
name: col.name || col.column_name,
label: col.display_name || col.label || col.column_label || col.name || col.column_name,
}));
}
}
@@ -444,8 +444,8 @@ export const V2ButtonConfigPanel: React.FC<V2ButtonConfigPanelProps> = ({
const response = await apiClient.get("/screen-management/screens?size=1000");
if (response.data.success && response.data.data) {
const screenList = response.data.data.map((s: any) => ({
id: s.id || s.screenId,
name: s.name || s.screenName,
id: s.id || s.screen_id,
name: s.name || s.screen_name,
description: s.description || "",
}));
setScreens(screenList);
@@ -83,7 +83,13 @@ function ColumnCombobox({ value, onChange, tableName, placeholder }: {
try {
const { tableManagementApi } = await import("@/lib/api/tableManagement");
const res = await tableManagementApi.getColumnList(tableName);
if (res.success && res.data?.columns) setColumns(res.data.columns);
if (res.success && res.data?.columns) setColumns(
(res.data.columns as any[]).map((c) => ({
columnName: c.column_name,
displayName: c.display_name,
dataType: c.data_type,
}))
);
} catch { /* ignore */ } finally { setLoading(false); }
};
load();
@@ -138,7 +144,7 @@ function ScreenCombobox({ value, onChange }: { value?: number; onChange: (v?: nu
const res = await screenApi.getScreens({ page: 1, size: 1000 });
if (res.data) {
setScreens(res.data.map((s: any) => ({
screenId: s.screenId, screenName: s.screenName || `화면 ${s.screenId}`, screenCode: s.screenCode || "",
screenId: s.screen_id, screenName: s.screen_name || `화면 ${s.screen_id}`, screenCode: s.screen_code || "",
})));
}
} catch { /* ignore */ } finally { setLoading(false); }
@@ -298,7 +304,7 @@ export const V2ItemRoutingConfigPanel: React.FC<V2ItemRoutingConfigPanelProps> =
const { tableManagementApi } = await import("@/lib/api/tableManagement");
const res = await tableManagementApi.getTableList();
if (res.success && res.data) {
setTables(res.data.map((t: any) => ({ tableName: t.tableName, displayName: t.displayName || t.tableName })));
setTables(res.data.map((t: any) => ({ tableName: t.table_name, displayName: t.display_name || t.table_name })));
}
} catch { /* ignore */ } finally { setLoadingTables(false); }
};
@@ -548,9 +548,9 @@ export default function VehicleReport() {
</TableRow>
) : (
driverData.map((row) => (
<TableRow key={row.userId}>
<TableRow key={row.user_id}>
<TableCell className="font-medium">
{row.userName}
{row.user_name}
</TableCell>
<TableCell className="text-right">
{row.tripCount}
+3 -3
View File
@@ -100,10 +100,10 @@ export function MenuProvider({ children }: { children: ReactNode }) {
};
useEffect(() => {
// user.companyCode가 변경되면 메뉴 다시 로드
// console.log("🔄 MenuContext: user.companyCode 변경 감지, 메뉴 재로드", user?.companyCode);
// user.company_code가 변경되면 메뉴 다시 로드
// console.log("🔄 MenuContext: user.company_code 변경 감지, 메뉴 재로드", user?.company_code);
loadMenus();
}, [user?.companyCode]); // companyCode 변경 시 재로드
}, [user?.company_code]); // company_code 변경 시 재로드
return (
<MenuContext.Provider value={{ adminMenus, userMenus, loading, refreshMenus }}>{children}</MenuContext.Provider>
+19 -23
View File
@@ -4,20 +4,17 @@ import { apiCall } from "@/lib/api/client";
import { AuthLogger } from "@/lib/authLogger";
interface UserInfo {
userId: string;
user_id?: string;
userName: string;
user_name?: string;
userNameEng?: string;
userNameCn?: string;
deptCode?: string;
deptName?: string;
dept_name?: string;
positionCode?: string;
positionName?: string;
position_name?: string;
email?: string;
tel?: string;
cellPhone?: string;
userType?: string;
user_type?: string;
userTypeName?: string;
authName?: string;
@@ -26,7 +23,6 @@ interface UserInfo {
isAdmin: boolean;
sabun?: string;
photo?: string | null;
companyCode?: string;
company_code?: string;
}
@@ -129,10 +125,10 @@ export const useAuth = () => {
const data = response.data;
return {
...data,
user_type: data.user_type ?? data.userType,
company_code: data.company_code ?? data.companyCode,
user_id: data.user_id ?? data.userId,
user_name: data.user_name ?? data.userName,
user_type: data.user_type,
company_code: data.company_code,
user_id: data.user_id,
user_name: data.user_name,
};
}
@@ -197,23 +193,23 @@ export const useAuth = () => {
if (userInfo) {
setUser(userInfo);
const isAdminFromUser = userInfo.userId === "plm_admin" || userInfo.userType === "ADMIN";
const isAdminFromUser = userInfo.user_id === "plm_admin" || userInfo.user_type === "ADMIN";
const finalAuthStatus = {
isLoggedIn: true,
isAdmin: authStatusData.isAdmin || isAdminFromUser,
};
setAuthStatus(finalAuthStatus);
AuthLogger.log("AUTH_CHECK_SUCCESS", `사용자: ${userInfo.userId}, 인증: ${finalAuthStatus.isLoggedIn}`);
AuthLogger.log("AUTH_CHECK_SUCCESS", `사용자: ${userInfo.user_id}, 인증: ${finalAuthStatus.isLoggedIn}`);
} else {
AuthLogger.log("AUTH_CHECK_FAIL", "userInfo 조회 실패 → 토큰 기반 임시 인증 유지 시도");
try {
const payload = JSON.parse(atob(token.split(".")[1]));
const tempUser: UserInfo = {
userId: payload.userId || payload.id || "unknown",
userName: payload.userName || payload.name || "사용자",
companyCode: payload.companyCode || payload.company_code || "",
isAdmin: payload.userId === "plm_admin" || payload.userType === "ADMIN",
user_id: payload.user_id || payload.id || "unknown",
user_name: payload.user_name || payload.name || "사용자",
company_code: payload.company_code || "",
isAdmin: payload.user_id === "plm_admin" || payload.user_type === "ADMIN",
};
setUser(tempUser);
@@ -233,10 +229,10 @@ export const useAuth = () => {
try {
const payload = JSON.parse(atob(token.split(".")[1]));
const tempUser: UserInfo = {
userId: payload.userId || payload.id || "unknown",
userName: payload.userName || payload.name || "사용자",
companyCode: payload.companyCode || payload.company_code || "",
isAdmin: payload.userId === "plm_admin" || payload.userType === "ADMIN",
user_id: payload.user_id || payload.id || "unknown",
user_name: payload.user_name || payload.name || "사용자",
company_code: payload.company_code || "",
isAdmin: payload.user_id === "plm_admin" || payload.user_type === "ADMIN",
};
setUser(tempUser);
@@ -448,9 +444,9 @@ export const useAuth = () => {
isLoggedIn: authStatus.isLoggedIn,
isAdmin: authStatus.isAdmin,
userId: user?.userId,
userName: user?.userName,
companyCode: user?.companyCode || user?.company_code,
userId: user?.user_id,
userName: user?.user_name,
companyCode: user?.company_code,
login,
logout,
+3 -3
View File
@@ -15,7 +15,7 @@ export const useLogin = () => {
// 상태 관리
const [formData, setFormData] = useState<LoginFormData>({
userId: "",
user_id: "",
password: "",
});
const [isLoading, setIsLoading] = useState(false);
@@ -64,7 +64,7 @@ export const useLogin = () => {
*
*/
const validateForm = useCallback((): string | null => {
if (!formData.userId.trim()) {
if (!formData.user_id.trim()) {
return FORM_VALIDATION.MESSAGES.USER_ID_REQUIRED;
}
if (!formData.password.trim()) {
@@ -117,7 +117,7 @@ export const useLogin = () => {
firstMenuPath?: string;
popLandingPath?: string;
}>("POST", AUTH_CONFIG.ENDPOINTS.LOGIN, {
userId: formData.userId,
userId: formData.user_id,
password: formData.password,
});
+1 -1
View File
@@ -115,7 +115,7 @@ export const useMenu = (user: any, authLoading: boolean) => {
if (assignedScreens.length > 0) {
const firstScreen = assignedScreens[0];
router.push(`/screens/${firstScreen.screenId}?menuObjid=${menuObjid}`);
router.push(`/screens/${firstScreen.screen_id}?menuObjid=${menuObjid}`);
return;
}
}
+3 -3
View File
@@ -155,10 +155,10 @@ export const useProfile = (user: any, refreshUserData: () => Promise<void>, refr
...prev,
isOpen: true,
formData: {
userName: user.userName || "",
userName: user.user_name || "",
email: user.email || "",
deptName: user.deptName || "",
positionName: user.positionName || "",
deptName: user.dept_name || "",
positionName: user.position_name || "",
locale: user.locale || "KR", // 기본값을 KR로 설정
},
selectedImage: user.photo || null,
+3 -3
View File
@@ -260,7 +260,7 @@ export function useConfiguredDataTransfer(config: ScreenDataTransferConfig) {
const { source, target, trigger, condition } = config;
const { sendData } = useScreenDataTransfer({
screenId: source.screenId,
screenId: source.screen_id,
componentId: source.componentId,
});
@@ -307,11 +307,11 @@ export function useConfiguredDataTransfer(config: ScreenDataTransferConfig) {
// 전달
sendData(sourceData, {
targetScreenId: target.screenId,
targetScreenId: target.screen_id,
mappings: target.mappings,
trigger,
});
}, [source.fields, target.screenId, target.mappings, trigger, condition, sendData]);
}, [source.fields, target.screen_id, target.mappings, trigger, condition, sendData]);
return { transfer };
}
+4 -4
View File
@@ -19,8 +19,8 @@ export interface AuditLogEntry {
}
export interface AuditLogFilters {
companyCode?: string;
userId?: string;
company_code?: string;
user_id?: string;
resourceType?: string;
action?: string;
tableName?: string;
@@ -48,8 +48,8 @@ export async function getAuditLogs(
limit: number;
}> {
const params = new URLSearchParams();
if (filters.companyCode) params.append("companyCode", filters.companyCode);
if (filters.userId) params.append("userId", filters.userId);
if (filters.company_code) params.append("companyCode", filters.company_code);
if (filters.user_id) params.append("userId", filters.user_id);
if (filters.resourceType) params.append("resourceType", filters.resourceType);
if (filters.action) params.append("action", filters.action);
if (filters.tableName) params.append("tableName", filters.tableName);
+2 -2
View File
@@ -42,7 +42,7 @@ export const uploadFiles = async (params: {
autoLink?: boolean;
columnName?: string;
isVirtualFileColumn?: boolean;
companyCode?: string; // 🔒 멀티테넌시: 회사 코드
company_code?: string; // 🔒 멀티테넌시: 회사 코드
isRecordMode?: boolean; // 🆕 레코드 모드 플래그
}): Promise<FileUploadResponse> => {
const formData = new FormData();
@@ -66,7 +66,7 @@ export const uploadFiles = async (params: {
if (params.autoLink !== undefined) formData.append("auto_link", params.autoLink.toString());
if (params.columnName) formData.append("column_name", params.columnName);
if (params.isVirtualFileColumn !== undefined) formData.append("is_virtual_file_column", params.isVirtualFileColumn.toString());
if (params.companyCode) formData.append("company_code", params.companyCode); // 🔒 멀티테넌시
if (params.company_code) formData.append("company_code", params.company_code); // 🔒 멀티테넌시
// 🆕 레코드 모드 플래그 추가 (백엔드에서 attachments 컬럼 자동 업데이트용)
if (params.isRecordMode !== undefined) formData.append("is_record_mode", params.isRecordMode.toString());
+2 -2
View File
@@ -79,7 +79,7 @@ export class GlobalFileManager {
static registerFiles(files: FileInfo[], context: {
uploadPage: string;
componentId: string;
screenId?: number;
screen_id?: number;
}): void {
files.forEach(file => {
const globalFileInfo: GlobalFileInfo = {
@@ -87,7 +87,7 @@ export class GlobalFileManager {
uploadPage: context.uploadPage,
uploadTime: new Date().toISOString(),
componentId: context.componentId,
screen_id: context.screenId,
screen_id: context.screen_id,
accessible: true,
};
this.registerFile(globalFileInfo);
+1 -1
View File
@@ -64,7 +64,7 @@ export async function autoDetectMultiTableConfig(
): Promise<{ success: boolean; data?: TableChainConfig; message?: string }> {
try {
const params: Record<string, any> = { rootTable };
if (screenId) params.screenId = screenId;
if (screenId) params.screen_id = screenId;
const response = await apiClient.get("/data/multi-table/auto-detect", {
params,
+2 -2
View File
@@ -177,14 +177,14 @@ export async function getLanguages(): Promise<ApiResponse<Language[]>> {
*
*/
export async function getLangKeys(params?: {
companyCode?: string;
company_code?: string;
menuCode?: string;
categoryId?: number;
searchText?: string;
}): Promise<ApiResponse<LangKey[]>> {
try {
const queryParams = new URLSearchParams();
if (params?.companyCode) queryParams.append("companyCode", params.companyCode);
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);
+2 -2
View File
@@ -13,12 +13,12 @@ import {
function mapRawScreen(raw: any): ScreenDefinition {
const createdRaw = raw.createdDate ?? raw.created_date;
const updatedRaw = raw.updatedDate ?? raw.updated_date;
const screenId = raw.screenId ?? raw.screen_id;
const screenId = raw.screen_id ?? raw["screenId"];
const screenName = raw.screenName ?? raw.screen_name;
const screenCode = raw.screenCode ?? raw.screen_code;
const tableName = raw.tableName ?? raw.table_name;
const tableLabel = raw.tableLabel ?? raw.table_label;
const companyCode = raw.companyCode ?? raw.company_code;
const companyCode = raw.company_code ?? raw["companyCode"];
const isActive = raw.isActive ?? raw.is_active;
const createdDate = createdRaw ? new Date(createdRaw) : undefined;
const updatedDate = updatedRaw ? new Date(updatedRaw) : undefined;
+2 -2
View File
@@ -257,8 +257,8 @@ export interface SummaryReport {
}
export interface DriverStat {
userId: string;
userName: string;
user_id: string;
user_name: string;
tripCount: number;
completedCount: number;
totalDistance: number;
+1 -1
View File
@@ -56,7 +56,7 @@ function getTokenSummary(): string {
const remainHour = Math.floor(remainMin / 60);
const min = remainMin % 60;
return `유효(${remainHour}h${min}m 남음, user:${payload.userId})`;
return `유효(${remainHour}h${min}m 남음, user:${payload.user_id})`;
} catch {
return "파싱실패";
}
@@ -81,7 +81,7 @@ export function ConditionalSectionViewer({
setComponents(layout.components || []);
setScreenInfo({
id: screenId,
tableName: screen.tableName,
tableName: screen.table_name,
});
setScreenResolution(layout.screenResolution || null);
} catch (error) {
@@ -196,7 +196,7 @@ export function ConditionalSectionViewer({
tableName={screenInfo?.tableName}
userId={userId}
userName={userName}
companyCode={user?.companyCode}
companyCode={user?.company_code}
formData={enhancedFormData}
onFormDataChange={onFormDataChange}
groupedData={groupedData}
@@ -150,13 +150,13 @@ export const MailRecipientSelectorComponent: React.FC<
// 수신자 추가 (내부 사용자)
const addInternalRecipient = useCallback(
(user: InternalUser, type: "to" | "cc") => {
const email = user.email || `${user.userId}@company.com`;
const email = user.email || `${user.user_id}@company.com`;
const newRecipient: Recipient = {
id: `internal-${user.userId}`,
id: `internal-${user.user_id}`,
email,
name: user.userName,
name: user.user_name,
type: "internal",
userId: user.userId,
userId: user.user_id,
};
if (type === "to") {
@@ -254,7 +254,7 @@ export const MailRecipientSelectorComponent: React.FC<
const isUserSelected = useCallback(
(user: InternalUser, type: "to" | "cc") => {
const recipients = type === "to" ? toRecipients : ccRecipients;
const userEmail = user.email || `${user.userId}@company.com`;
const userEmail = user.email || `${user.user_id}@company.com`;
return recipients.some((r) => r.email === userEmail);
},
[toRecipients, ccRecipients]
@@ -317,13 +317,13 @@ export const MailRecipientSelectorComponent: React.FC<
<CommandEmpty> .</CommandEmpty>
<CommandGroup>
{internalUsers.map((user, index) => {
const userEmail = user.email || `${user.userId}@company.com`;
const userEmail = user.email || `${user.user_id}@company.com`;
const selected = isUserSelected(user, type);
const uniqueKey = `${user.userId}-${index}`;
const uniqueKey = `${user.user_id}-${index}`;
return (
<CommandItem
key={uniqueKey}
value={`${user.userId}-${user.userName}-${userEmail}`}
value={`${user.user_id}-${user.user_name}-${userEmail}`}
onSelect={() => {
if (!selected) {
addInternalRecipient(user, type);
@@ -336,7 +336,7 @@ export const MailRecipientSelectorComponent: React.FC<
>
<div className="flex flex-1 items-center justify-between">
<div className="flex flex-col">
<span className="font-medium">{user.userName}</span>
<span className="font-medium">{user.user_name}</span>
<span className="text-xs text-muted-foreground">
{userEmail}
{user.deptName && ` | ${user.deptName}`}
@@ -53,10 +53,10 @@ export interface MailRecipientSelectorProps {
isInteractive?: boolean;
}
// 내부 사용자 정보 (API 응답 - camelCase)
// 내부 사용자 정보 (API 응답 - snake_case)
export interface InternalUser {
userId: string;
userName: string;
user_id: string;
user_name: string;
email?: string;
deptName?: string;
positionName?: string;
@@ -40,9 +40,9 @@ const ScreenSelector: React.FC<ScreenSelectorProps> = ({ value, onChange, placeh
const response = await screenApi.getScreens({ size: 500 });
if (response.data) {
setScreens(response.data.map((s: any) => ({
screenId: s.screenId,
screenName: s.screenName || s.name || `화면 ${s.screenId}`,
tableName: s.tableName || s.table_name,
screenId: s.screen_id,
screenName: s.screen_name || s.name || `화면 ${s.screen_id}`,
tableName: s.table_name,
})));
}
} catch (error) {
@@ -132,7 +132,7 @@ export const RelatedDataButtonsConfigPanel: React.FC<RelatedDataButtonsConfigPan
const response = await tableManagementApi.getTableList();
if (response.success && response.data) {
setAllTables(response.data.map((t: any) => ({
tableName: t.tableName || t.table_name,
tableName: t.table_name,
displayName: t.tableLabel || t.table_label || t.displayName,
})));
}
@@ -196,8 +196,8 @@ export const RelatedDataButtonsConfigPanel: React.FC<RelatedDataButtonsConfigPan
}
try {
const screenInfo = await screenApi.getScreen(config.modalLink.targetScreenId);
if (screenInfo?.tableName) {
setTargetModalTableName(screenInfo.tableName);
if (screenInfo?.table_name) {
setTargetModalTableName(screenInfo.table_name);
}
} catch (error) {
console.error("대상 모달 화면 정보 로드 실패:", error);
@@ -88,14 +88,14 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
// 좌측 화면 정보 조회
const screenData = await screenApi.getScreen(localConfig.leftScreenId);
if (!screenData?.tableName) {
if (!screenData?.table_name) {
console.warn("좌측 화면에 테이블이 설정되지 않았습니다.");
setLeftScreenColumns([]);
return;
}
// 테이블 컬럼 조회
const columnsResponse = await getTableColumns(screenData.tableName);
const columnsResponse = await getTableColumns(screenData.table_name);
if (columnsResponse.success && columnsResponse.data?.columns) {
const columns = columnsResponse.data.columns.map((col: any) => ({
columnName: col.column_name || col.columnName,
@@ -131,12 +131,12 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
const screenData = await screenApi.getScreen(localConfig.rightScreenId);
// 1. 메인 화면의 테이블 (있는 경우)
if (screenData?.tableName) {
const columnsResponse = await getTableColumns(screenData.tableName);
if (screenData?.table_name) {
const columnsResponse = await getTableColumns(screenData.table_name);
if (columnsResponse.success && columnsResponse.data?.columns) {
tables.push({
tableName: screenData.tableName,
screenName: screenData.screenName || "메인 화면",
tableName: screenData.table_name,
screenName: screenData.screen_name || "메인 화면",
columns: columnsResponse.data.columns.map((col: any) => ({
columnName: col.column_name || col.columnName,
columnLabel: col.column_label || col.columnLabel || col.column_name || col.columnName,
@@ -192,20 +192,20 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
for (const embeddedScreenId of embeddedScreenIds) {
try {
const embeddedScreen = await screenApi.getScreen(embeddedScreenId);
if (embeddedScreen?.tableName) {
if (embeddedScreen?.table_name) {
// 이미 추가된 테이블인지 확인
if (!tables.find(t => t.tableName === embeddedScreen.tableName)) {
const columnsResponse = await getTableColumns(embeddedScreen.tableName);
if (!tables.find(t => t.tableName === embeddedScreen.table_name)) {
const columnsResponse = await getTableColumns(embeddedScreen.table_name);
if (columnsResponse.success && columnsResponse.data?.columns) {
tables.push({
tableName: embeddedScreen.tableName,
screenName: embeddedScreen.screenName || `화면 ${embeddedScreenId}`,
tableName: embeddedScreen.table_name,
screenName: embeddedScreen.screen_name || `화면 ${embeddedScreenId}`,
columns: columnsResponse.data.columns.map((col: any) => ({
columnName: col.column_name || col.columnName,
columnLabel: col.column_label || col.columnLabel || col.column_name || col.columnName,
})),
});
console.log("✅ 테이블 추가:", embeddedScreen.tableName);
console.log("✅ 테이블 추가:", embeddedScreen.table_name);
}
}
}
@@ -91,15 +91,15 @@ const GroupByColumnsSelector: React.FC<{
) : (
<div className="max-h-[200px] space-y-1 overflow-y-auto rounded-md border p-3">
{columns.map((col) => (
<div key={col.columnName} className="flex items-center gap-2">
<div key={col.column_name} className="flex items-center gap-2">
<Checkbox
id={`groupby-${col.columnName}`}
checked={selectedColumns.includes(col.columnName)}
onCheckedChange={() => toggleColumn(col.columnName)}
id={`groupby-${col.column_name}`}
checked={selectedColumns.includes(col.column_name)}
onCheckedChange={() => toggleColumn(col.column_name)}
/>
<label htmlFor={`groupby-${col.columnName}`} className="flex-1 cursor-pointer text-xs">
{col.columnLabel || col.columnName}
<span className="text-muted-foreground ml-1">({col.columnName})</span>
<label htmlFor={`groupby-${col.column_name}`} className="flex-1 cursor-pointer text-xs">
{col.display_name || col.column_name}
<span className="text-muted-foreground ml-1">({col.column_name})</span>
</label>
</div>
))}
@@ -218,9 +218,9 @@ export const ActionButtonConfigModal: React.FC<ActionButtonConfigModalProps> = (
}
const transformedScreens = screenList.map((s: any) => ({
screen_id: s.screenId ?? s.screen_id ?? s.id,
screen_name: s.screenName ?? s.screen_name ?? s.name ?? `화면 ${s.screenId || s.screen_id || s.id}`,
screen_code: s.screenCode ?? s.screen_code ?? s.code ?? "",
screen_id: s.screen_id,
screen_name: s.screen_name ?? `화면 ${s.screen_id}`,
screen_code: s.screen_code ?? "",
}));
setScreens(transformedScreens);
@@ -131,10 +131,9 @@ export const SplitPanelLayout2ConfigPanel: React.FC<SplitPanelLayout2ConfigPanel
console.log("[loadTables] 추출된 테이블 목록:", tableList);
if (tableList.length > 0) {
// 백엔드에서 카멜케이스(tableName)로 반환하므로 둘 다 처리
const transformedTables = tableList.map((t: any) => ({
table_name: t.tableName ?? t.table_name ?? t.name ?? "",
table_comment: t.displayName ?? t.table_comment ?? t.description ?? "",
table_name: t.table_name ?? t.name ?? "",
table_comment: t.display_name ?? t.table_comment ?? t.description ?? "",
}));
console.log("[loadTables] 변환된 테이블 목록:", transformedTables);
setTables(transformedTables);
@@ -171,11 +170,10 @@ export const SplitPanelLayout2ConfigPanel: React.FC<SplitPanelLayout2ConfigPanel
console.log("[loadScreens] 추출된 화면 목록:", screenList);
if (screenList.length > 0) {
// 백엔드에서 카멜케이스(screenId, screenName)로 반환하므로 둘 다 처리
const transformedScreens = screenList.map((s: any) => ({
screen_id: s.screenId ?? s.screen_id ?? s.id,
screen_name: s.screenName ?? s.screen_name ?? s.name ?? `화면 ${s.screenId || s.screen_id || s.id}`,
screen_code: s.screenCode ?? s.screen_code ?? s.code ?? "",
screen_id: s.screen_id,
screen_name: s.screen_name ?? `화면 ${s.screen_id}`,
screen_code: s.screen_code ?? "",
}));
console.log("[loadScreens] 변환된 화면 목록:", transformedScreens);
setScreens(transformedScreens);
@@ -213,11 +211,10 @@ export const SplitPanelLayout2ConfigPanel: React.FC<SplitPanelLayout2ConfigPanel
console.log(`[loadColumns] ${side} 추출된 컬럼 목록:`, columnList);
if (columnList.length > 0) {
// 백엔드에서 카멜케이스(columnName)로 반환하므로 둘 다 처리
const transformedColumns = columnList.map((c: any) => ({
column_name: c.columnName ?? c.column_name ?? c.name ?? "",
data_type: c.dataType ?? c.data_type ?? c.type ?? "",
column_comment: c.displayName ?? c.column_comment ?? c.label ?? "",
column_name: c.column_name ?? c.name ?? "",
data_type: c.data_type ?? c.type ?? "",
column_comment: c.display_name ?? c.column_comment ?? c.label ?? "",
}));
console.log(`[loadColumns] ${side} 변환된 컬럼 목록:`, transformedColumns);
@@ -279,9 +276,9 @@ export const SplitPanelLayout2ConfigPanel: React.FC<SplitPanelLayout2ConfigPanel
if (mainResponse.data?.success) {
const columnList = mainResponse.data.data?.columns || mainResponse.data.data || [];
mainColumns = columnList.map((c: any) => ({
column_name: c.columnName ?? c.column_name ?? c.name ?? "",
data_type: c.dataType ?? c.data_type ?? c.type ?? "",
column_comment: c.displayName ?? c.column_comment ?? c.label ?? "",
column_name: c.column_name ?? c.name ?? "",
data_type: c.data_type ?? c.type ?? "",
column_comment: c.display_name ?? c.column_comment ?? c.label ?? "",
}));
}
@@ -619,9 +616,9 @@ export const SplitPanelLayout2ConfigPanel: React.FC<SplitPanelLayout2ConfigPanel
}
const transformedColumns = columnList.map((c: any) => ({
column_name: c.columnName ?? c.column_name ?? c.name ?? "",
data_type: c.dataType ?? c.data_type ?? c.type ?? "",
column_comment: c.displayName ?? c.column_comment ?? c.label ?? "",
column_name: c.column_name ?? c.name ?? "",
data_type: c.data_type ?? c.type ?? "",
column_comment: c.display_name ?? c.column_comment ?? c.label ?? "",
}));
setJoinTableColumns(transformedColumns);
} catch (error) {
@@ -623,7 +623,7 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
}
// 🔒 현재 사용자의 회사 코드 가져오기 (멀티테넌시 격리)
const userCompanyCode = user?.companyCode || (window as any).__user__?.companyCode;
const userCompanyCode = user?.company_code || (window as any).__user__?.company_code;
// 🔑 레코드 모드일 때는 effectiveTableName을 우선 사용
const finalLinkedTable = effectiveIsRecordMode
@@ -24,20 +24,20 @@ import { ItemRoutingConfig, ProcessColumnDef } from "./types";
import { defaultConfig } from "./config";
interface TableInfo {
tableName: string;
displayName?: string;
table_name: string;
display_name?: string;
}
interface ColumnInfo {
columnName: string;
displayName?: string;
column_name: string;
display_name?: string;
dataType?: string;
}
interface ScreenInfo {
screenId: number;
screenName: string;
screenCode: string;
screen_id: number;
screen_name: string;
screen_code: string;
}
// 테이블 셀렉터 Combobox
@@ -53,7 +53,7 @@ function TableSelector({
loading: boolean;
}) {
const [open, setOpen] = useState(false);
const selected = tables.find((t) => t.tableName === value);
const selected = tables.find((t) => t.table_name === value);
return (
<Popover open={open} onOpenChange={setOpen}>
@@ -68,7 +68,7 @@ function TableSelector({
{loading
? "로딩 중..."
: selected
? selected.displayName || selected.tableName
? selected.display_name || selected.table_name
: "테이블 선택"}
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
</Button>
@@ -83,10 +83,10 @@ function TableSelector({
<CommandGroup className="max-h-[200px] overflow-auto">
{tables.map((t) => (
<CommandItem
key={t.tableName}
value={`${t.displayName || ""} ${t.tableName}`}
key={t.table_name}
value={`${t.display_name || ""} ${t.table_name}`}
onSelect={() => {
onChange(t.tableName);
onChange(t.table_name);
setOpen(false);
}}
className="text-xs"
@@ -94,16 +94,16 @@ function TableSelector({
<Check
className={cn(
"mr-2 h-3 w-3",
value === t.tableName ? "opacity-100" : "opacity-0"
value === t.table_name ? "opacity-100" : "opacity-0"
)}
/>
<div className="flex flex-col">
<span className="font-medium">
{t.displayName || t.tableName}
{t.display_name || t.table_name}
</span>
{t.displayName && (
{t.display_name && (
<span className="text-[10px] text-muted-foreground">
{t.tableName}
{t.table_name}
</span>
)}
</div>
@@ -157,7 +157,7 @@ function ColumnSelector({
load();
}, [tableName]);
const selected = columns.find((c) => c.columnName === value);
const selected = columns.find((c) => c.column_name === value);
return (
<Popover open={open} onOpenChange={setOpen}>
@@ -174,7 +174,7 @@ function ColumnSelector({
: !tableName
? "테이블 먼저 선택"
: selected
? selected.displayName || selected.columnName
? selected.display_name || selected.column_name
: label || "컬럼 선택"}
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
</Button>
@@ -189,10 +189,10 @@ function ColumnSelector({
<CommandGroup className="max-h-[200px] overflow-auto">
{columns.map((c) => (
<CommandItem
key={c.columnName}
value={`${c.displayName || ""} ${c.columnName}`}
key={c.column_name}
value={`${c.display_name || ""} ${c.column_name}`}
onSelect={() => {
onChange(c.columnName);
onChange(c.column_name);
setOpen(false);
}}
className="text-xs"
@@ -200,16 +200,16 @@ function ColumnSelector({
<Check
className={cn(
"mr-2 h-3 w-3",
value === c.columnName ? "opacity-100" : "opacity-0"
value === c.column_name ? "opacity-100" : "opacity-0"
)}
/>
<div className="flex flex-col">
<span className="font-medium">
{c.displayName || c.columnName}
{c.display_name || c.column_name}
</span>
{c.displayName && (
{c.display_name && (
<span className="text-[10px] text-muted-foreground">
{c.columnName}
{c.column_name}
</span>
)}
</div>
@@ -243,9 +243,9 @@ function ScreenSelector({
const res = await screenApi.getScreens({ page: 1, size: 1000 });
setScreens(
res.data.map((s: any) => ({
screenId: s.screenId,
screenName: s.screenName,
screenCode: s.screenCode,
screen_id: s.screen_id,
screen_name: s.screen_name,
screen_code: s.screen_code ?? "",
}))
);
} catch {
@@ -257,7 +257,7 @@ function ScreenSelector({
load();
}, []);
const selected = screens.find((s) => s.screenId === value);
const selected = screens.find((s) => s.screen_id === value);
return (
<Popover open={open} onOpenChange={setOpen}>
@@ -272,7 +272,7 @@ function ScreenSelector({
{loading
? "로딩 중..."
: selected
? `${selected.screenName} (${selected.screenId})`
? `${selected.screen_name} (${selected.screen_id})`
: "화면 선택"}
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
</Button>
@@ -287,10 +287,10 @@ function ScreenSelector({
<CommandGroup className="max-h-[300px] overflow-auto">
{screens.map((s) => (
<CommandItem
key={s.screenId}
value={`${s.screenName.toLowerCase()} ${s.screenCode.toLowerCase()} ${s.screenId}`}
key={s.screen_id}
value={`${s.screen_name.toLowerCase()} ${s.screen_code.toLowerCase()} ${s.screen_id}`}
onSelect={() => {
onChange(s.screenId === value ? undefined : s.screenId);
onChange(s.screen_id === value ? undefined : s.screen_id);
setOpen(false);
}}
className="text-xs"
@@ -298,13 +298,13 @@ function ScreenSelector({
<Check
className={cn(
"mr-2 h-3 w-3",
value === s.screenId ? "opacity-100" : "opacity-0"
value === s.screen_id ? "opacity-100" : "opacity-0"
)}
/>
<div className="flex flex-col">
<span className="font-medium">{s.screenName}</span>
<span className="font-medium">{s.screen_name}</span>
<span className="text-[10px] text-muted-foreground">
{s.screenCode} (ID: {s.screenId})
{s.screen_code} (ID: {s.screen_id})
</span>
</div>
</CommandItem>
@@ -352,8 +352,8 @@ function ProcessColumnSelector({
cols.push(
...res2.data.columns.map((c: any) => ({
...c,
columnName: c.columnName,
displayName: `[${processTable}] ${c.displayName || c.columnName}`,
column_name: c.column_name,
display_name: `[${processTable}] ${c.display_name || c.column_name}`,
}))
);
}
@@ -368,7 +368,7 @@ function ProcessColumnSelector({
loadAll();
}, [tableName, processTable]);
const selected = columns.find((c) => c.columnName === value);
const selected = columns.find((c) => c.column_name === value);
return (
<Popover open={open} onOpenChange={setOpen}>
@@ -379,7 +379,7 @@ function ProcessColumnSelector({
className="h-7 w-24 justify-between text-[10px]"
disabled={loading}
>
{selected ? selected.displayName || selected.columnName : value || "선택"}
{selected ? selected.display_name || selected.column_name : value || "선택"}
<ChevronsUpDown className="ml-1 h-2.5 w-2.5 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
@@ -393,10 +393,10 @@ function ProcessColumnSelector({
<CommandGroup className="max-h-[200px] overflow-auto">
{columns.map((c) => (
<CommandItem
key={c.columnName}
value={`${c.displayName || ""} ${c.columnName}`}
key={c.column_name}
value={`${c.display_name || ""} ${c.column_name}`}
onSelect={() => {
onChange(c.columnName);
onChange(c.column_name);
setOpen(false);
}}
className="text-xs"
@@ -404,10 +404,10 @@ function ProcessColumnSelector({
<Check
className={cn(
"mr-1 h-3 w-3",
value === c.columnName ? "opacity-100" : "opacity-0"
value === c.column_name ? "opacity-100" : "opacity-0"
)}
/>
{c.displayName || c.columnName}
{c.display_name || c.column_name}
</CommandItem>
))}
</CommandGroup>
@@ -237,7 +237,7 @@ export function PopWorkDetailComponent({
...(isPassed !== null
? [{ type: "data-update", targetTable: "process_work_result", targetColumn: "is_passed", value: isPassed, items: [{ id: rowId }] }]
: []),
{ type: "data-update", targetTable: "process_work_result", targetColumn: "recorded_by", value: user?.userId ?? "", items: [{ id: rowId }] },
{ type: "data-update", targetTable: "process_work_result", targetColumn: "recorded_by", value: user?.user_id ?? "", items: [{ id: rowId }] },
{ type: "data-update", targetTable: "process_work_result", targetColumn: "recorded_at", value: new Date().toISOString(), items: [{ id: rowId }] },
],
data: { items: [{ id: rowId }], fieldValues: {} },
@@ -251,7 +251,7 @@ export function PopWorkDetailComponent({
result_value: resultValue,
status: newStatus,
is_passed: isPassed,
recorded_by: user?.userId ?? null,
recorded_by: user?.user_id ?? null,
recorded_at: new Date().toISOString(),
}
: r
@@ -267,7 +267,7 @@ export function PopWorkDetailComponent({
});
}
},
[user?.userId]
[user?.user_id]
);
// ========================================
+2 -2
View File
@@ -70,7 +70,7 @@ export class EnhancedFormService {
console.log("🚀 향상된 폼 저장 시작:", {
tableName,
screenId: screenInfo.screenId,
screenId: screenInfo.screen_id ?? screenInfo.screenId,
dataKeys: Object.keys(formData),
componentsCount: components.length,
});
@@ -105,7 +105,7 @@ export class EnhancedFormService {
// 3. 서버 저장 수행
const saveStart = performance.now();
const saveResult = await this.performServerSave(screenInfo.screenId, tableName, processedData, options);
const saveResult = await this.performServerSave(screenInfo.screen_id ?? screenInfo.screenId, tableName, processedData, options);
saveTime = performance.now() - saveStart;
if (!saveResult.success) {
+1 -1
View File
@@ -3358,7 +3358,7 @@ export class ButtonActionExecutor {
}
// 3. 동적 모달 제목 생성
let finalTitle = config.modalTitle || screenInfo?.screenName || "데이터 등록";
let finalTitle = config.modalTitle || (screenInfo?.screen_name ?? screenInfo?.screenName) || "데이터 등록";
// 블록 기반 제목 처리
if (config.modalTitleBlocks?.length) {
+1 -1
View File
@@ -3,7 +3,7 @@
*/
export interface LoginFormData {
userId: string;
user_id: string;
password: string;
}
+11 -11
View File
@@ -2,23 +2,23 @@
export interface UserHistory {
no?: number; // 순번 (프론트엔드에서 추가)
rowNum?: number; // 행 번호 (쿼리에서 생성)
row_num?: number; // 행 번호 (쿼리에서 생성)
// USER_INFO_HISTORY 테이블 컬럼들 (camelCase)
// USER_INFO_HISTORY 테이블 컬럼들 (snake_case)
sabun?: string; // 사번
userId: string; // 사용자 ID
userName?: string; // 사용자 이름
deptCode?: string; // 부서 코드
deptName?: string; // 부서명
userTypeName?: string; // 사용자 타입명 (회사명)
historyType?: string; // 이력유형
user_id: string; // 사용자 ID
user_name?: string; // 사용자 이름
dept_code?: string; // 부서 코드
dept_name?: string; // 부서명
user_type_name?: string; // 사용자 타입명 (회사명)
history_type?: string; // 이력유형
writer?: string; // 작성자 ID
regDate?: string; // 등록일시
reg_date?: string; // 등록일시
status?: string; // 상태
// 조인된 컬럼들
writerName?: string; // 작성자명 (JOIN으로 가져옴)
regDateTitle?: string; // 작성일 (YYYY-MM-DD 형식)
writer_name?: string; // 작성자명 (JOIN으로 가져옴)
reg_date_title?: string; // 작성일 (YYYY-MM-DD 형식)
}
// API 응답 타입
+3 -3
View File
@@ -175,14 +175,14 @@ export type DataTransferTrigger =
export interface ScreenDataTransferConfig {
// 소스 (데이터를 보내는 쪽)
source: {
screenId?: number;
screen_id?: number;
componentId?: string;
fields: string[]; // 전달할 필드들
};
// 타겟 (데이터를 받는 쪽)
target: {
screenId?: number;
screen_id?: number;
componentId?: string;
mappings: FieldMapping[];
};