[agent-pipeline] pipe-20260329071056-9n90 round-1
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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="비밀번호 초기화"
|
||||
>
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 || [],
|
||||
|
||||
@@ -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
@@ -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");
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user