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