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