[agent-pipeline] pipe-20260329071056-9n90 round-1

This commit is contained in:
DDD1542
2026-03-29 16:18:59 +09:00
parent e9fdfd4348
commit 882f63e744
15 changed files with 206 additions and 244 deletions
@@ -77,7 +77,7 @@ function SentTab() {
// 현재 로그인 사용자 ID를 기반으로 내가 올린 결재만 조회
const userRes = await getCurrentUser();
if (userRes.success && userRes.data) {
const res = await getApprovalRequests({ requester_id: userRes.data.userId });
const res = await getApprovalRequests({ requester_id: userRes.data.user_id });
if (res.success && res.data) setRequests(res.data);
} else {
// 사용자 정보 없으면 빈 목록 표시
@@ -49,7 +49,7 @@ export function UserStatusConfirmDialog({
<div className="flex items-center gap-2">
<span className="text-muted-foreground w-16 text-sm">:</span>
<span className="font-medium">
{user.userName} ({user.userId})
{user.user_name} ({user.user_id})
</span>
</div>
<div className="flex items-center gap-2">
+20 -20
View File
@@ -88,8 +88,8 @@ export function UserTable({
const handleOpenHistoryModal = (user: User) => {
setHistoryModal({
isOpen: true,
userId: user.userId,
userName: user.userName || user.userId,
userId: user.user_id,
userName: user.user_name || user.user_id,
});
};
@@ -120,35 +120,35 @@ export function UserTable({
render: (value) => <span className="font-mono">{value || "-"}</span>,
},
{
key: "companyCode",
key: "company_code",
label: "회사",
width: "120px",
hideOnMobile: true,
render: (value) => <span className="font-medium">{value || "-"}</span>,
},
{
key: "deptName",
key: "dept_name",
label: "부서명",
width: "120px",
hideOnMobile: true,
render: (value) => <span className="font-medium">{value || "-"}</span>,
},
{
key: "positionName",
key: "position_name",
label: "직책",
width: "100px",
hideOnMobile: true,
render: (value) => <span className="font-medium">{value || "-"}</span>,
},
{
key: "userId",
key: "user_id",
label: "사용자 ID",
width: "120px",
hideOnMobile: true,
render: (value) => <span className="font-mono">{value}</span>,
},
{
key: "userName",
key: "user_name",
label: "사용자명",
width: "100px",
hideOnMobile: true,
@@ -159,7 +159,7 @@ export function UserTable({
label: "전화번호",
width: "120px",
hideOnMobile: true,
render: (_value, row) => <span>{row.tel || row.cellPhone || "-"}</span>,
render: (_value, row) => <span>{row.tel || row.cell_phone || "-"}</span>,
},
{
key: "email",
@@ -172,7 +172,7 @@ export function UserTable({
),
},
{
key: "regDate",
key: "reg_date",
label: "등록일",
width: "100px",
hideOnMobile: true,
@@ -188,7 +188,7 @@ export function UserTable({
<Switch
checked={row.status === "active"}
onCheckedChange={(checked) => handleStatusToggle(row, checked)}
aria-label={`${row.userName} 상태 토글`}
aria-label={`${row.user_name} 상태 토글`}
/>
</div>
),
@@ -204,22 +204,22 @@ export function UserTable({
},
{
label: "회사",
render: (user) => <span className="font-medium">{user.companyCode || ""}</span>,
render: (user) => <span className="font-medium">{user.company_code || ""}</span>,
hideEmpty: true,
},
{
label: "부서",
render: (user) => <span className="font-medium">{user.deptName || ""}</span>,
render: (user) => <span className="font-medium">{user.dept_name || ""}</span>,
hideEmpty: true,
},
{
label: "직책",
render: (user) => <span className="font-medium">{user.positionName || ""}</span>,
render: (user) => <span className="font-medium">{user.position_name || ""}</span>,
hideEmpty: true,
},
{
label: "연락처",
render: (user) => <span>{user.tel || user.cellPhone || ""}</span>,
render: (user) => <span>{user.tel || user.cell_phone || ""}</span>,
hideEmpty: true,
},
{
@@ -229,7 +229,7 @@ export function UserTable({
},
{
label: "등록일",
render: (user) => <span>{formatDate(user.regDate || "")}</span>,
render: (user) => <span>{formatDate(user.reg_date || "")}</span>,
},
];
@@ -238,17 +238,17 @@ export function UserTable({
<ResponsiveDataView<User>
data={users}
columns={columns}
keyExtractor={(u) => u.userId}
keyExtractor={(u) => u.user_id}
isLoading={isLoading}
emptyMessage="등록된 사용자가 없습니다."
skeletonCount={10}
cardTitle={(u) => u.userName || ""}
cardSubtitle={(u) => <span className="font-mono">{u.userId}</span>}
cardTitle={(u) => u.user_name || ""}
cardSubtitle={(u) => <span className="font-mono">{u.user_id}</span>}
cardHeaderRight={(u) => (
<Switch
checked={u.status === "active"}
onCheckedChange={(checked) => handleStatusToggle(u, checked)}
aria-label={`${u.userName} 상태 토글`}
aria-label={`${u.user_name} 상태 토글`}
/>
)}
cardFields={cardFields}
@@ -268,7 +268,7 @@ export function UserTable({
<Button
variant="ghost"
size="icon"
onClick={() => onPasswordReset(user.userId, user.userName || user.userId)}
onClick={() => onPasswordReset(user.user_id, user.user_name || user.user_id)}
className="h-8 w-8"
title="비밀번호 초기화"
>
+25 -25
View File
@@ -32,11 +32,11 @@ export function UserToolbar({
// 통합 검색 시 고급 검색 필드들 클리어
searchType: undefined,
search_sabun: undefined,
search_companyName: undefined,
search_deptName: undefined,
search_positionName: undefined,
search_userId: undefined,
search_userName: undefined,
search_company_name: undefined,
search_dept_name: undefined,
search_position_name: undefined,
search_user_id: undefined,
search_user_name: undefined,
search_tel: undefined,
search_email: undefined,
});
@@ -54,11 +54,11 @@ export function UserToolbar({
// 고급 검색 모드인지 확인
const isAdvancedSearchMode = !!(
searchFilter.search_sabun ||
searchFilter.search_companyName ||
searchFilter.search_deptName ||
searchFilter.search_positionName ||
searchFilter.search_userId ||
searchFilter.search_userName ||
searchFilter.search_company_name ||
searchFilter.search_dept_name ||
searchFilter.search_position_name ||
searchFilter.search_user_id ||
searchFilter.search_user_name ||
searchFilter.search_tel ||
searchFilter.search_email
);
@@ -133,36 +133,36 @@ export function UserToolbar({
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<Input
placeholder="회사명 검색"
value={searchFilter.search_companyName || ""}
onChange={(e) => handleAdvancedSearchChange("search_companyName", e.target.value)}
value={searchFilter.search_company_name || ""}
onChange={(e) => handleAdvancedSearchChange("search_company_name", e.target.value)}
className="h-10 text-sm"
/>
<Input
placeholder="부서명 검색"
value={searchFilter.search_deptName || ""}
onChange={(e) => handleAdvancedSearchChange("search_deptName", e.target.value)}
value={searchFilter.search_dept_name || ""}
onChange={(e) => handleAdvancedSearchChange("search_dept_name", e.target.value)}
className="h-10 text-sm"
/>
<Input
placeholder="직책 검색"
value={searchFilter.search_positionName || ""}
onChange={(e) => handleAdvancedSearchChange("search_positionName", e.target.value)}
value={searchFilter.search_position_name || ""}
onChange={(e) => handleAdvancedSearchChange("search_position_name", e.target.value)}
className="h-10 text-sm"
/>
<Input
placeholder="사용자 ID 검색"
value={searchFilter.search_userId || ""}
onChange={(e) => handleAdvancedSearchChange("search_userId", e.target.value)}
value={searchFilter.search_user_id || ""}
onChange={(e) => handleAdvancedSearchChange("search_user_id", e.target.value)}
className="h-10 text-sm"
/>
<Input
placeholder="사용자명 검색"
value={searchFilter.search_userName || ""}
onChange={(e) => handleAdvancedSearchChange("search_userName", e.target.value)}
value={searchFilter.search_user_name || ""}
onChange={(e) => handleAdvancedSearchChange("search_user_name", e.target.value)}
className="h-10 text-sm"
/>
@@ -190,11 +190,11 @@ export function UserToolbar({
onClick={() =>
onSearchChange({
search_sabun: undefined,
search_companyName: undefined,
search_deptName: undefined,
search_positionName: undefined,
search_userId: undefined,
search_userName: undefined,
search_company_name: undefined,
search_dept_name: undefined,
search_position_name: undefined,
search_user_id: undefined,
search_user_name: undefined,
search_tel: undefined,
search_email: undefined,
})
+4 -4
View File
@@ -53,10 +53,10 @@ export function MainSidebar({ menuList, expandedMenus, onMenuClick, className =
*/
const renderMenuItem = (menu: MenuItem, level: number = 0) => {
const hasChildren = menu.children && menu.children.length > 0;
const isExpanded = expandedMenus.has(String(menu.OBJID));
const isExpanded = expandedMenus.has(String(menu.objid));
return (
<div key={String(menu.OBJID)}>
<div key={String(menu.objid)}>
<button
onClick={() => onMenuClick(menu)}
className={cn(
@@ -66,8 +66,8 @@ export function MainSidebar({ menuList, expandedMenus, onMenuClick, className =
)}
>
<div className="flex items-center gap-2">
{getMenuIcon(menu.MENU_NAME_KOR || menu.menuNameKor || "", menu.MENU_ICON || menu.menu_icon)}
<span>{menu.MENU_NAME_KOR || menu.menuNameKor || "메뉴"}</span>
{getMenuIcon(menu.menu_name_kor || "", menu.menu_icon)}
<span>{menu.menu_name_kor || "메뉴"}</span>
</div>
{hasChildren && (isExpanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />)}
</button>
+31 -31
View File
@@ -28,12 +28,12 @@ export default function MailAccountModal({
const [formData, setFormData] = useState<CreateMailAccountDto>({
name: '',
email: '',
smtpHost: '',
smtpPort: 587,
smtpSecure: false,
smtpUsername: '',
smtpPassword: '',
dailyLimit: 1000,
smtp_host: '',
smtp_port: 587,
smtp_secure: false,
smtp_username: '',
smtp_password: '',
daily_limit: 1000,
});
const [isSubmitting, setIsSubmitting] = useState(false);
@@ -49,24 +49,24 @@ export default function MailAccountModal({
setFormData({
name: account.name,
email: account.email,
smtpHost: account.smtpHost,
smtpPort: account.smtpPort,
smtpSecure: account.smtpSecure,
smtpUsername: account.smtpUsername,
smtpPassword: '', // 비밀번호는 비워둠 (보안)
dailyLimit: account.dailyLimit,
smtp_host: account.smtp_host,
smtp_port: account.smtp_port,
smtp_secure: account.smtp_secure,
smtp_username: account.smtp_username,
smtp_password: '', // 비밀번호는 비워둠 (보안)
daily_limit: account.daily_limit,
});
} else {
// 생성 모드일 때 초기화
setFormData({
name: '',
email: '',
smtpHost: '',
smtpPort: 587,
smtpSecure: false,
smtpUsername: '',
smtpPassword: '',
dailyLimit: 1000,
smtp_host: '',
smtp_port: 587,
smtp_secure: false,
smtp_username: '',
smtp_password: '',
daily_limit: 1000,
});
}
setTestResult(null);
@@ -204,8 +204,8 @@ export default function MailAccountModal({
</label>
<input
type="text"
name="smtpHost"
value={formData.smtpHost}
name="smtp_host"
value={formData.smtp_host}
onChange={handleChange}
required
className="w-full px-4 py-2 border border rounded-lg focus:ring-2 focus:ring-primary focus:border-primary"
@@ -222,8 +222,8 @@ export default function MailAccountModal({
</label>
<input
type="number"
name="smtpPort"
value={formData.smtpPort}
name="smtp_port"
value={formData.smtp_port}
onChange={handleChange}
required
className="w-full px-4 py-2 border border rounded-lg focus:ring-2 focus:ring-primary focus:border-primary"
@@ -239,12 +239,12 @@ export default function MailAccountModal({
</label>
<select
name="smtpSecure"
value={formData.smtpSecure ? 'true' : 'false'}
name="smtp_secure"
value={formData.smtp_secure ? 'true' : 'false'}
onChange={(e) =>
setFormData((prev) => ({
...prev,
smtpSecure: e.target.value === 'true',
smtp_secure: e.target.value === 'true',
}))
}
className="w-full px-4 py-2 border border rounded-lg focus:ring-2 focus:ring-primary focus:border-primary"
@@ -269,8 +269,8 @@ export default function MailAccountModal({
</label>
<input
type="text"
name="smtpUsername"
value={formData.smtpUsername}
name="smtp_username"
value={formData.smtp_username}
onChange={handleChange}
required
className="w-full px-4 py-2 border border rounded-lg focus:ring-2 focus:ring-primary focus:border-primary"
@@ -287,8 +287,8 @@ export default function MailAccountModal({
</label>
<input
type="password"
name="smtpPassword"
value={formData.smtpPassword}
name="smtp_password"
value={formData.smtp_password}
onChange={handleChange}
required={mode === 'create'}
className="w-full px-4 py-2 border border rounded-lg focus:ring-2 focus:ring-primary focus:border-primary"
@@ -313,8 +313,8 @@ export default function MailAccountModal({
</label>
<input
type="number"
name="dailyLimit"
value={formData.dailyLimit}
name="daily_limit"
value={formData.daily_limit}
onChange={handleChange}
className="w-full px-4 py-2 border border rounded-lg focus:ring-2 focus:ring-primary focus:border-primary"
placeholder="1000"
@@ -70,9 +70,9 @@ export function MenuSelectModal({
// 처음 2레벨까지 자동 확장
const initialExpanded = new Set<string>();
response.data.forEach((menu) => {
const level = menu.lev ?? menu.LEV ?? 1;
const level = menu.lev ?? 1;
if (level <= 2) {
initialExpanded.add(menu.objid ?? menu.OBJID ?? "");
initialExpanded.add(menu.objid ?? "");
}
});
setExpandedIds(initialExpanded);
@@ -91,11 +91,11 @@ export function MenuSelectModal({
// 모든 메뉴를 노드로 변환
menus.forEach((menu) => {
const objid = menu.objid ?? menu.OBJID ?? "";
const parentObjId = menu.parent_obj_id ?? menu.PARENT_OBJ_ID ?? "";
const menuNameKor = menu.menu_name_kor ?? menu.MENU_NAME_KOR ?? menu.translated_name ?? menu.TRANSLATED_NAME ?? "";
const menuUrl = menu.menu_url ?? menu.MENU_URL ?? "";
const level = menu.lev ?? menu.LEV ?? 1;
const objid = menu.objid ?? "";
const parentObjId = menu.parent_obj_id ?? "";
const menuNameKor = menu.menu_name_kor ?? menu.translated_name ?? "";
const menuUrl = menu.menu_url ?? "";
const level = menu.lev ?? 1;
menuMap.set(objid, {
objid,
@@ -109,8 +109,8 @@ export function MenuSelectModal({
// 부모-자식 관계 설정
menus.forEach((menu) => {
const objid = menu.objid ?? menu.OBJID ?? "";
const parentObjId = menu.parent_obj_id ?? menu.PARENT_OBJ_ID ?? "";
const objid = menu.objid ?? "";
const parentObjId = menu.parent_obj_id ?? "";
const node = menuMap.get(objid);
if (!node) return;
@@ -1166,7 +1166,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
case "current_time":
return localTime;
case "current_user":
return currentUser?.userName || currentUser?.userId || "unknown_user";
return currentUser?.user_name || currentUser?.user_id || "unknown_user";
case "uuid":
return crypto.randomUUID();
case "sequence":
@@ -226,7 +226,7 @@ export const OptimizedButtonComponent: React.FC<OptimizedButtonProps> = ({
buttonId: component.id,
screenId: component.screenId,
companyCode,
userId: contextData.userId,
userId: formData.userId ?? formData.user_id,
formData,
selectedRows: selectedRows || [],
selectedRowsData: selectedRowsData || [],
+4
View File
@@ -5,7 +5,9 @@ import { AuthLogger } from "@/lib/authLogger";
interface UserInfo {
userId: string;
user_id?: string;
userName: string;
user_name?: string;
userNameEng?: string;
userNameCn?: string;
deptCode?: string;
@@ -129,6 +131,8 @@ export const useAuth = () => {
...data,
user_type: data.user_type ?? data.userType,
company_code: data.company_code ?? data.companyCode,
user_id: data.user_id ?? data.userId,
user_name: data.user_name ?? data.userName,
};
}
+13 -28
View File
@@ -20,20 +20,6 @@ export const useMenu = (user: any, authLoading: boolean) => {
isLoading: true,
});
/**
* 데이터 키를 대문자로 변환하는 함수
*/
const convertToUpperCaseKeys = useCallback((data: Record<string, unknown>[]): MenuItem[] => {
return data.map((item) => {
const converted: Record<string, unknown> = {};
Object.keys(item).forEach((key) => {
const upperKey = key.toUpperCase();
converted[upperKey] = item[key];
});
return converted as unknown as MenuItem;
});
}, []);
/**
* 메뉴 트리 구조 생성
*/
@@ -42,14 +28,14 @@ export const useMenu = (user: any, authLoading: boolean) => {
const rootMenus: MenuItem[] = [];
menuItems.forEach((menu) => {
const objId = String(menu.OBJID);
const parentId = String(menu.PARENT_OBJ_ID);
menuMap.set(objId, { ...menu, OBJID: objId, PARENT_OBJ_ID: parentId, children: [] });
const objId = String(menu.objid);
const parentId = String(menu.parent_obj_id);
menuMap.set(objId, { ...menu, objid: objId, parent_obj_id: parentId, children: [] });
});
menuItems.forEach((menu) => {
const objId = String(menu.OBJID);
const parentId = String(menu.PARENT_OBJ_ID);
const objId = String(menu.objid);
const parentId = String(menu.parent_obj_id);
const menuItem = menuMap.get(objId)!;
if (parentId !== "-395553955") {
@@ -63,7 +49,7 @@ export const useMenu = (user: any, authLoading: boolean) => {
}
});
return rootMenus.sort((a, b) => (a.SEQ || 0) - (b.SEQ || 0));
return rootMenus.sort((a, b) => (a.seq || 0) - (b.seq || 0));
}, []);
/**
@@ -76,10 +62,9 @@ export const useMenu = (user: any, authLoading: boolean) => {
const response = await apiClient.get("/admin/user-menus");
if (response.data?.success && response.data?.data) {
const convertedMenuData = convertToUpperCaseKeys(response.data.data || []);
setMenuState((prev: MenuState) => ({
...prev,
menuList: buildMenuTree(convertedMenuData),
menuList: buildMenuTree(response.data.data || []),
isLoading: false,
}));
} else {
@@ -89,7 +74,7 @@ export const useMenu = (user: any, authLoading: boolean) => {
AuthLogger.log("MENU_LOAD_FAIL", `메뉴 로드 실패: ${err?.response?.status || err?.message || "unknown"}`);
setMenuState((prev: MenuState) => ({ ...prev, isLoading: false }));
}
}, [convertToUpperCaseKeys, buildMenuTree]);
}, [buildMenuTree]);
/**
* 메뉴 토글
@@ -115,15 +100,15 @@ export const useMenu = (user: any, authLoading: boolean) => {
const handleMenuClick = useCallback(
async (menu: MenuItem) => {
if (menu.children && menu.children.length > 0) {
toggleMenu(String(menu.OBJID));
toggleMenu(String(menu.objid));
} else {
const menuName = menu.MENU_NAME_KOR || menu.menuNameKor || menu.TRANSLATED_NAME || "메뉴";
const menuName = menu.menu_name_kor || menu.translated_name || "메뉴";
if (typeof window !== "undefined") {
localStorage.setItem("currentMenuName", menuName);
}
try {
const menuObjid = menu.OBJID || menu.objid;
const menuObjid = menu.objid;
if (menuObjid) {
const { menuScreenApi } = await import("@/lib/api/screen");
const assignedScreens = await menuScreenApi.getScreensByMenu(parseInt(menuObjid.toString()));
@@ -138,8 +123,8 @@ export const useMenu = (user: any, authLoading: boolean) => {
console.warn("할당된 화면 조회 실패:", error);
}
if (menu.MENU_URL) {
router.push(menu.MENU_URL);
if (menu.menu_url) {
router.push(menu.menu_url);
} else {
console.warn("메뉴에 URL이나 할당된 화면이 없습니다:", menu);
const { toast } = await import("sonner");
+45 -45
View File
@@ -20,11 +20,11 @@ export const useUserManagement = () => {
// 고급 검색 필드들 디바운싱
const debouncedSabun = useDebounce(searchFilter.search_sabun || "", 500);
const debouncedCompanyName = useDebounce(searchFilter.search_companyName || "", 500);
const debouncedDeptName = useDebounce(searchFilter.search_deptName || "", 500);
const debouncedPositionName = useDebounce(searchFilter.search_positionName || "", 500);
const debouncedUserId = useDebounce(searchFilter.search_userId || "", 500);
const debouncedUserName = useDebounce(searchFilter.search_userName || "", 500);
const debouncedCompanyName = useDebounce(searchFilter.search_company_name || "", 500);
const debouncedDeptName = useDebounce(searchFilter.search_dept_name || "", 500);
const debouncedPositionName = useDebounce(searchFilter.search_position_name || "", 500);
const debouncedUserId = useDebounce(searchFilter.search_user_id || "", 500);
const debouncedUserName = useDebounce(searchFilter.search_user_name || "", 500);
const debouncedTel = useDebounce(searchFilter.search_tel || "", 500);
const debouncedEmail = useDebounce(searchFilter.search_email || "", 500);
@@ -36,11 +36,11 @@ export const useUserManagement = () => {
// 고급 검색
search_sabun: debouncedSabun,
search_companyName: debouncedCompanyName,
search_deptName: debouncedDeptName,
search_positionName: debouncedPositionName,
search_userId: debouncedUserId,
search_userName: debouncedUserName,
search_company_name: debouncedCompanyName,
search_dept_name: debouncedDeptName,
search_position_name: debouncedPositionName,
search_user_id: debouncedUserId,
search_user_name: debouncedUserName,
search_tel: debouncedTel,
search_email: debouncedEmail,
@@ -66,11 +66,11 @@ export const useUserManagement = () => {
return (
(searchFilter.searchValue || "") !== debouncedSearchValue ||
(searchFilter.search_sabun || "") !== debouncedSabun ||
(searchFilter.search_companyName || "") !== debouncedCompanyName ||
(searchFilter.search_deptName || "") !== debouncedDeptName ||
(searchFilter.search_positionName || "") !== debouncedPositionName ||
(searchFilter.search_userId || "") !== debouncedUserId ||
(searchFilter.search_userName || "") !== debouncedUserName ||
(searchFilter.search_company_name || "") !== debouncedCompanyName ||
(searchFilter.search_dept_name || "") !== debouncedDeptName ||
(searchFilter.search_position_name || "") !== debouncedPositionName ||
(searchFilter.search_user_id || "") !== debouncedUserId ||
(searchFilter.search_user_name || "") !== debouncedUserName ||
(searchFilter.search_tel || "") !== debouncedTel ||
(searchFilter.search_email || "") !== debouncedEmail
);
@@ -79,15 +79,15 @@ export const useUserManagement = () => {
debouncedSearchValue,
searchFilter.search_sabun,
debouncedSabun,
searchFilter.search_companyName,
searchFilter.search_company_name,
debouncedCompanyName,
searchFilter.search_deptName,
searchFilter.search_dept_name,
debouncedDeptName,
searchFilter.search_positionName,
searchFilter.search_position_name,
debouncedPositionName,
searchFilter.search_userId,
searchFilter.search_user_id,
debouncedUserId,
searchFilter.search_userName,
searchFilter.search_user_name,
debouncedUserName,
searchFilter.search_tel,
debouncedTel,
@@ -128,20 +128,20 @@ export const useUserManagement = () => {
if (filter.search_sabun && filter.search_sabun.trim()) {
searchParams.search_sabun = filter.search_sabun.trim();
}
if (filter.search_companyName && filter.search_companyName.trim()) {
searchParams.search_companyName = filter.search_companyName.trim();
if (filter.search_company_name && filter.search_company_name.trim()) {
searchParams.search_company_name = filter.search_company_name.trim();
}
if (filter.search_deptName && filter.search_deptName.trim()) {
searchParams.search_deptName = filter.search_deptName.trim();
if (filter.search_dept_name && filter.search_dept_name.trim()) {
searchParams.search_dept_name = filter.search_dept_name.trim();
}
if (filter.search_positionName && filter.search_positionName.trim()) {
searchParams.search_positionName = filter.search_positionName.trim();
if (filter.search_position_name && filter.search_position_name.trim()) {
searchParams.search_position_name = filter.search_position_name.trim();
}
if (filter.search_userId && filter.search_userId.trim()) {
searchParams.search_userId = filter.search_userId.trim();
if (filter.search_user_id && filter.search_user_id.trim()) {
searchParams.search_user_id = filter.search_user_id.trim();
}
if (filter.search_userName && filter.search_userName.trim()) {
searchParams.search_userName = filter.search_userName.trim();
if (filter.search_user_name && filter.search_user_name.trim()) {
searchParams.search_user_name = filter.search_user_name.trim();
}
if (filter.search_tel && filter.search_tel.trim()) {
searchParams.search_tel = filter.search_tel.trim();
@@ -199,11 +199,11 @@ export const useUserManagement = () => {
}, [
debouncedSearchFilter.searchValue,
debouncedSearchFilter.search_sabun,
debouncedSearchFilter.search_companyName,
debouncedSearchFilter.search_deptName,
debouncedSearchFilter.search_positionName,
debouncedSearchFilter.search_userId,
debouncedSearchFilter.search_userName,
debouncedSearchFilter.search_company_name,
debouncedSearchFilter.search_dept_name,
debouncedSearchFilter.search_position_name,
debouncedSearchFilter.search_user_id,
debouncedSearchFilter.search_user_name,
debouncedSearchFilter.search_tel,
debouncedSearchFilter.search_email,
loadUsers,
@@ -217,11 +217,11 @@ export const useUserManagement = () => {
const hasSearchChange = !!(
newFilter.searchValue !== undefined ||
newFilter.search_sabun !== undefined ||
newFilter.search_companyName !== undefined ||
newFilter.search_deptName !== undefined ||
newFilter.search_positionName !== undefined ||
newFilter.search_userId !== undefined ||
newFilter.search_userName !== undefined ||
newFilter.search_company_name !== undefined ||
newFilter.search_dept_name !== undefined ||
newFilter.search_position_name !== undefined ||
newFilter.search_user_id !== undefined ||
newFilter.search_user_name !== undefined ||
newFilter.search_tel !== undefined ||
newFilter.search_email !== undefined ||
newFilter.searchType !== undefined
@@ -260,10 +260,10 @@ export const useUserManagement = () => {
// 사용자 상태 토글 핸들러
const handleStatusToggle = useCallback(async (user: User, newStatus: string) => {
try {
console.log(`🎛️ 상태 변경: ${user.userName} (${user.userId}) → ${newStatus}`);
console.log(`🎛️ 상태 변경: ${user.user_name} (${user.user_id}) → ${newStatus}`);
// 백엔드 API 호출
const response = await userAPI.updateStatus(user.userId, newStatus);
const response = await userAPI.updateStatus(user.user_id, newStatus);
// 백엔드 응답 구조: { result: boolean, msg: string }
if (response && typeof response === "object" && "result" in response) {
@@ -273,7 +273,7 @@ export const useUserManagement = () => {
console.log("✅ 상태 변경 성공:", apiResponse.msg);
// 전체 목록 새로고침 대신 개별 사용자 상태만 업데이트
setUsers((prevUsers) => prevUsers.map((u) => (u.userId === user.userId ? { ...u, status: newStatus } : u)));
setUsers((prevUsers) => prevUsers.map((u) => (u.user_id === user.user_id ? { ...u, status: newStatus } : u)));
} else {
console.error("❌ 상태 변경 실패:", apiResponse.msg);
alert(apiResponse.msg || "상태 변경에 실패했습니다.");
@@ -290,8 +290,8 @@ export const useUserManagement = () => {
// 데이터 새로고침 (비밀번호 초기화 후 목록 갱신용)
const refreshData = useCallback(() => {
loadUsers(debouncedSearchFilter.searchValue, debouncedSearchFilter.searchType);
}, [loadUsers, debouncedSearchFilter.searchValue, debouncedSearchFilter.searchType]);
loadUsers(debouncedSearchFilter);
}, [loadUsers, debouncedSearchFilter]);
// 사용자 등록 핸들러
const handleCreate = useCallback(() => {
+7 -7
View File
@@ -477,16 +477,16 @@ export interface ApiResponse<T = unknown> {
}
export interface UserInfo {
userId: string;
userName: string;
deptName?: string;
companyCode?: string;
userType?: string;
userTypeName?: string;
user_id: string;
user_name: string;
dept_name?: string;
company_code?: string;
user_type?: string;
user_type_name?: string;
email?: string;
photo?: string;
locale?: string;
isAdmin?: boolean;
is_admin?: boolean;
}
export const getCurrentUser = async (): Promise<ApiResponse<UserInfo>> => {
+14 -41
View File
@@ -4,17 +4,17 @@
export interface MenuItem {
objid: string;
parentObjId: string;
menuNameKor: string;
menuNameEng: string;
menuUrl: string;
menuDesc: string;
parent_obj_id: string;
menu_name_kor: string;
menu_name_eng: string;
menu_url: string;
menu_desc: string;
status: "ACTIVE" | "INACTIVE";
statusTitle: string;
status_title: string;
seq: number;
lev: number;
menuType: "admin" | "user";
lpadMenuNameKor: string;
menu_type: "admin" | "user";
lpad_menu_name_kor: string;
writer?: string;
regdate?: string;
company_code?: string;
@@ -31,33 +31,6 @@ export interface MenuItem {
translated_desc?: string;
lang_key?: string;
lang_key_desc?: string;
// 백엔드에서 오는 대문자 키들 (fallback)
OBJID?: string;
PARENT_OBJ_ID?: string;
MENU_NAME_KOR?: string;
MENU_NAME_ENG?: string;
MENU_URL?: string;
MENU_DESC?: string;
STATUS?: string;
STATUS_TITLE?: string;
SEQ?: number;
LEV?: number;
MENU_TYPE?: string;
LPAD_MENU_NAME_KOR?: string;
WRITER?: string;
REGDATE?: string;
COMPANY_CODE?: string;
COMPANY_NAME?: string;
// 아이콘 대문자 키
MENU_ICON?: string;
// 번역 관련 대문자 키들
TRANSLATED_NAME?: string;
TRANSLATED_DESC?: string;
LANG_KEY?: string;
LANG_KEY_DESC?: string;
}
export interface MenuState {
@@ -67,14 +40,14 @@ export interface MenuState {
}
export interface MenuFormData {
menuNameKor: string;
menuNameEng: string;
menuUrl: string;
menuDesc: string;
menu_name_kor: string;
menu_name_eng: string;
menu_url: string;
menu_desc: string;
status: "ACTIVE" | "INACTIVE";
seq: number;
parentObjId: string;
menuType: "admin" | "user";
parent_obj_id: string;
menu_type: "admin" | "user";
}
export interface MenuApiResponse {
+30 -30
View File
@@ -2,28 +2,28 @@
* 사용자 관리 관련 타입 정의
*/
// 사용자 정보 인터페이스 (백엔드 API 응답과 일치하는 camelCase)
// 사용자 정보 인터페이스 (백엔드 API 응답과 일치하는 snake_case)
export interface User {
sabun?: string; // 사번
userId: string; // 사용자 ID
userName: string; // 사용자명
userNameEng?: string; // 영문명
userNameCn?: string; // 중문명
companyCode?: string; // 회사 코드
companyName?: string; // 회사명
deptCode?: string; // 부서 코드
deptName?: string; // 부서명
positionCode?: string; // 직책 코드
positionName?: string; // 직책
user_id: string; // 사용자 ID
user_name: string; // 사용자명
user_name_eng?: string; // 영문명
user_name_cn?: string; // 중문명
company_code?: string; // 회사 코드
company_name?: string; // 회사명
dept_code?: string; // 부서 코드
dept_name?: string; // 부서명
position_code?: string; // 직책 코드
position_name?: string; // 직책
email?: string; // 이메일
tel?: string; // 전화번호
cellPhone?: string; // 휴대폰
userType?: string; // 사용자 유형 코드
userTypeName?: string; // 사용자 유형명
regDate?: string; // 등록일 (YYYY-MM-DD)
cell_phone?: string; // 휴대폰
user_type?: string; // 사용자 유형 코드
user_type_name?: string; // 사용자 유형명
reg_date?: string; // 등록일 (YYYY-MM-DD)
status: string; // 상태 (active, inactive)
dataType?: string; // 데이터 타입
endDate?: string; // 퇴사일
data_type?: string; // 데이터 타입
end_date?: string; // 퇴사일
locale?: string; // 로케일
rnum?: number; // 행 번호
}
@@ -34,15 +34,15 @@ export interface UserSearchFilter {
searchValue?: string; // 통합 검색어 (모든 필드 대상)
// 단일 필드 검색 (중간 우선순위)
searchType?: "all" | "sabun" | "companyCode" | "deptName" | "positionName" | "userId" | "userName" | "tel" | "email"; // 검색 대상 (하위 호환성)
searchType?: "all" | "sabun" | "company_code" | "dept_name" | "position_name" | "user_id" | "user_name" | "tel" | "email"; // 검색 대상 (하위 호환성)
// 고급 검색 (개별 필드별 AND 조건)
search_sabun?: string; // 사번 검색
search_companyName?: string; // 회사명 검색
search_deptName?: string; // 부서명 검색
search_positionName?: string; // 직책 검색
search_userId?: string; // 사용자 ID 검색
search_userName?: string; // 사용자명 검색
search_company_name?: string; // 회사명 검색
search_dept_name?: string; // 부서명 검색
search_position_name?: string; // 직책 검색
search_user_id?: string; // 사용자 ID 검색
search_user_name?: string; // 사용자명 검색
search_tel?: string; // 전화번호 검색 (TEL + CELL_PHONE)
search_email?: string; // 이메일 검색
}
@@ -57,15 +57,15 @@ export interface UserTableColumn {
// 사용자 등록/수정 폼 데이터
export interface UserFormData {
userId: string;
userName: string;
deptCode: string;
deptName: string;
positionName: string;
user_id: string;
user_name: string;
dept_code: string;
dept_name: string;
position_name: string;
email: string;
tel: string;
cellPhone: string;
userTypeName: string;
cell_phone: string;
user_type_name: string;
status: string;
}