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