사용자 계정 관리 API 연동 및 기능 개선
- 사용자 비밀번호 변경 API 추가 - 메뉴 권한 관리 API 추가 (조회/저장) - 인증 방법 수정 API 추가 - 사용자 권한 업데이트 API 추가 - 계정 관리 UI 컴포넌트 개선 - Docker 및 Makefile 설정 업데이트 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,44 @@
|
||||
import { useState } from 'react';
|
||||
import { PATHS } from '@/shared/constants/paths';
|
||||
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
|
||||
import { HeaderType } from '@/entities/common/model/types';
|
||||
import {
|
||||
import {
|
||||
useSetHeaderTitle,
|
||||
useSetHeaderType,
|
||||
useSetFooterMode,
|
||||
useSetOnBack
|
||||
} from '@/widgets/sub-layout/use-sub-layout';
|
||||
import { useUserChangePasswordMutation } from '@/entities/user/api/use-user-change-password-mutation';
|
||||
import { snackBar } from '@/shared/lib/toast';
|
||||
|
||||
export const PasswordModifyLoginPasswordPage = () => {
|
||||
const { navigate } = useNavigate();
|
||||
const [mid, setMid] = useState<string>('nictest00m');
|
||||
const [currentPassword, setCurrentPassword] = useState<string>('');
|
||||
const [newPassword, setNewPassword] = useState<string>('');
|
||||
const [confirmPassword, setConfirmPassword] = useState<string>('');
|
||||
const [usrid] = useState<string>(''); // TODO: Get actual user ID from context/session
|
||||
|
||||
const changePasswordMutation = useUserChangePasswordMutation({
|
||||
onSuccess: () => {
|
||||
snackBar('비밀번호가 성공적으로 변경되었습니다.');
|
||||
// Clear form
|
||||
setCurrentPassword('');
|
||||
setNewPassword('');
|
||||
setConfirmPassword('');
|
||||
// Navigate back
|
||||
navigate(PATHS.account.password.manage);
|
||||
},
|
||||
onError: (error) => {
|
||||
snackBar(error?.response?.data?.message || '비밀번호 변경에 실패했습니다.');
|
||||
}
|
||||
});
|
||||
|
||||
const midList = [
|
||||
{ value: 'nictest00m', label: 'nictest00m' },
|
||||
{ value: 'nictest01m', label: 'nictest01m' },
|
||||
{ value: 'nictest02m', label: 'nictest02m' },
|
||||
];
|
||||
|
||||
useSetHeaderTitle('로그인 비밀번호 변경');
|
||||
useSetHeaderType(HeaderType.LeftArrow);
|
||||
@@ -17,6 +46,29 @@ export const PasswordModifyLoginPasswordPage = () => {
|
||||
useSetOnBack(() => {
|
||||
navigate(PATHS.account.password.manage);
|
||||
});
|
||||
|
||||
// 저장 버튼 활성화 조건 체크
|
||||
const isFormValid = () => {
|
||||
return (
|
||||
currentPassword.length >= 8 &&
|
||||
newPassword.length >= 8 &&
|
||||
confirmPassword.length >= 8 &&
|
||||
newPassword === confirmPassword
|
||||
);
|
||||
};
|
||||
|
||||
// 저장 버튼 클릭 핸들러
|
||||
const handleSave = () => {
|
||||
if (!isFormValid()) return;
|
||||
|
||||
// TODO: Validate current password before submitting
|
||||
changePasswordMutation.mutate({
|
||||
mid,
|
||||
usrid: usrid || 'test', // TODO: Get actual user ID
|
||||
password: newPassword
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<main>
|
||||
@@ -26,42 +78,54 @@ export const PasswordModifyLoginPasswordPage = () => {
|
||||
<div className="user-add">
|
||||
<div className="ua-row">
|
||||
<div className="ua-label">가맹점 <span className="red">*</span></div>
|
||||
<select className="wid-100">
|
||||
<option>nictest01g</option>
|
||||
<select className="wid-100" value={mid} onChange={(e) => setMid(e.target.value)}>
|
||||
{midList.map((item) => (
|
||||
<option key={item.value} value={item.value}>{item.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="ua-row">
|
||||
<div className="ua-label">기존 비밀번호 <span className="red">*</span></div>
|
||||
<input
|
||||
<input
|
||||
className="wid-100"
|
||||
type="password"
|
||||
placeholder=""
|
||||
value={currentPassword}
|
||||
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="ua-row">
|
||||
<div className="ua-label">변경 비밀번호 <span className="red">*</span></div>
|
||||
<input
|
||||
className="wid-100 error"
|
||||
<input
|
||||
className={`wid-100 ${confirmPassword && newPassword !== confirmPassword ? 'error' : ''}`}
|
||||
type="password"
|
||||
placeholder=""
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="ua-help error">입력 정보 불일치</div>
|
||||
<div className="ua-row">
|
||||
<div className="ua-label">변경 비밀번호 재입력 <span className="red">*</span></div>
|
||||
<input
|
||||
className="wid-100 error"
|
||||
<input
|
||||
className={`wid-100 ${confirmPassword && newPassword !== confirmPassword ? 'error' : ''}`}
|
||||
type="password"
|
||||
placeholder=""
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{confirmPassword && newPassword !== confirmPassword && (
|
||||
<div className="ua-help error">입력 정보 불일치</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="apply-row">
|
||||
<button
|
||||
<button
|
||||
className="btn-50 btn-blue flex-1"
|
||||
type="button"
|
||||
>저장</button>
|
||||
disabled={!isFormValid() || changePasswordMutation.isPending}
|
||||
onClick={handleSave}
|
||||
>{changePasswordMutation.isPending ? '처리중...' : '저장'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,9 +16,9 @@ import {
|
||||
export const UserAccountAuthPage = () => {
|
||||
const { navigate } = useNavigate();
|
||||
const location = useLocation();
|
||||
const { tid, mid, usrid } = location.state || {};
|
||||
const { tid, mid, usrid, idCl, status } = location.state || {};
|
||||
|
||||
const [activeTab, setActiveTab] = useState<AccountUserTabKeys>(AccountUserTabKeys.AccountAuth);
|
||||
const [activeTab, ] = useState<AccountUserTabKeys>(AccountUserTabKeys.AccountAuth);
|
||||
useSetHeaderTitle('사용자 설정');
|
||||
useSetHeaderType(HeaderType.LeftArrow);
|
||||
useSetFooterMode(true);
|
||||
@@ -37,12 +37,16 @@ export const UserAccountAuthPage = () => {
|
||||
<div className="tab-pane pt-46 active">
|
||||
<AccountUserTab
|
||||
activeTab={ activeTab }
|
||||
tid={ tid || '' }
|
||||
mid={ mid || '' }
|
||||
usrid={ usrid || '' }
|
||||
idCl={ idCl || '' }
|
||||
status={ status || '' }
|
||||
></AccountUserTab>
|
||||
<UserAccountAuthWrap
|
||||
tid={ tid || '' }
|
||||
mid={ mid || '' }
|
||||
usrid={ usrid || '' }
|
||||
idCl={ idCl || '' }
|
||||
status={ status || '' }
|
||||
></UserAccountAuthWrap>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,8 +11,12 @@ import { useState, useEffect, useRef } from 'react';
|
||||
import { VerificationItem } from '@/entities/account/model/types';
|
||||
import { useUserCreateMutation } from '@/entities/user/api/use-user-create-mutation';
|
||||
import { useUserExistsUseridQuery } from '@/entities/user/api/use-user-exists-userid-query';
|
||||
import { useLocation } from 'react-router';
|
||||
|
||||
export const UserAddAccountPage = () => {
|
||||
const { navigate } = useNavigate();
|
||||
const location = useLocation();
|
||||
const { mid } = location.state || {};
|
||||
const { mutateAsync: userCreate, isPending } = useUserCreateMutation();
|
||||
|
||||
// 폼 상태 관리
|
||||
@@ -143,6 +147,16 @@ export const UserAddAccountPage = () => {
|
||||
setNewPhones(updated);
|
||||
};
|
||||
|
||||
// 비밀번호 검증 함수
|
||||
const validatePassword = (password: string) => {
|
||||
if (!password.trim()) {
|
||||
return '비밀번호를 입력해 주세요';
|
||||
} else if (password.length < 8) {
|
||||
return '8자리 이상 입력해 주세요';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
// 폼 입력 핸들러
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
@@ -174,6 +188,14 @@ export const UserAddAccountPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 비밀번호 blur 핸들러
|
||||
const handlePasswordBlur = () => {
|
||||
const passwordError = validatePassword(formData.password);
|
||||
if (passwordError) {
|
||||
setErrors(prev => ({ ...prev, password: passwordError }));
|
||||
}
|
||||
};
|
||||
|
||||
// 컴포넌트 언마운트 시 타이머 정리
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -241,6 +263,44 @@ export const UserAddAccountPage = () => {
|
||||
return totalCount > 1;
|
||||
};
|
||||
|
||||
// 저장 버튼 활성화 조건 체크
|
||||
const isSaveButtonEnabled = () => {
|
||||
// 1. 사용자 ID가 입력되고 중복되지 않아야 함
|
||||
if (!formData.usrid.trim()) return false;
|
||||
if (errors.usrid) return false;
|
||||
if (isCheckingUsrid || isUserExistsLoading) return false;
|
||||
|
||||
// 2. 비밀번호가 8자리 이상
|
||||
if (!formData.password.trim() || formData.password.length < 8) return false;
|
||||
|
||||
// 3. 입력된 모든 이메일과 휴대폰 번호가 유효한 형식이어야 함
|
||||
const nonEmptyEmails = newEmails.filter(email => email.trim());
|
||||
const nonEmptyPhones = newPhones.filter(phone => phone.trim());
|
||||
|
||||
// 입력된 이메일이 있으면 모두 유효한 형식이어야 함
|
||||
if (nonEmptyEmails.length > 0) {
|
||||
const invalidEmails = nonEmptyEmails.filter(email => !isValidEmail(email));
|
||||
if (invalidEmails.length > 0) return false;
|
||||
}
|
||||
|
||||
// 입력된 휴대폰 번호가 있으면 모두 유효한 형식이어야 함
|
||||
if (nonEmptyPhones.length > 0) {
|
||||
const invalidPhones = nonEmptyPhones.filter(phone => !isValidPhone(phone));
|
||||
if (invalidPhones.length > 0) return false;
|
||||
}
|
||||
|
||||
// 4. 유효한 이메일 또는 휴대폰 번호가 최소 1개 이상
|
||||
const validEmails = nonEmptyEmails.filter(email => isValidEmail(email));
|
||||
const validPhones = nonEmptyPhones.filter(phone => isValidPhone(phone));
|
||||
|
||||
if (validEmails.length === 0 && validPhones.length === 0) return false;
|
||||
|
||||
// 5. 중복이 없어야 함
|
||||
if (hasDuplicateEmail() || hasDuplicatePhone()) return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// 폼 검증
|
||||
const validateForm = () => {
|
||||
const newErrors = { usrid: '', password: '' };
|
||||
@@ -302,10 +362,11 @@ export const UserAddAccountPage = () => {
|
||||
});
|
||||
|
||||
const request = {
|
||||
userId: formData.usrid,
|
||||
mid: mid,
|
||||
usrid: formData.usrid,
|
||||
password: formData.password,
|
||||
loginRange: formData.loginRange,
|
||||
verification: verifications
|
||||
verifications: verifications
|
||||
};
|
||||
|
||||
const response = await userCreate(request);
|
||||
@@ -365,7 +426,7 @@ export const UserAddAccountPage = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{errors.usrid && <div className="ua-help error">{errors.usrid}</div>}
|
||||
{errors.usrid && <div className="ua-help error pt-10">{errors.usrid}</div>}
|
||||
{!errors.usrid && formData.usrid && userExistsData && !userExistsData.exists && (
|
||||
<div className="ua-help" style={{ color: '#78D197' }}>사용 가능한 ID입니다.</div>
|
||||
)}
|
||||
@@ -378,9 +439,10 @@ export const UserAddAccountPage = () => {
|
||||
placeholder="8자리 이상 입력해 주세요"
|
||||
value={formData.password}
|
||||
onChange={(e) => handleInputChange('password', e.target.value)}
|
||||
onBlur={handlePasswordBlur}
|
||||
/>
|
||||
</div>
|
||||
{errors.password && <div className="ua-help error">{errors.password}</div>}
|
||||
{errors.password && <div className="ua-help error pt-10">{errors.password}</div>}
|
||||
|
||||
<div className="ua-row">
|
||||
<div className="ua-label">로그인 범위</div>
|
||||
@@ -390,7 +452,7 @@ export const UserAddAccountPage = () => {
|
||||
onChange={(e) => handleInputChange('loginRange', e.target.value)}
|
||||
>
|
||||
<option value="MID">MID</option>
|
||||
<option value="MID + GID">MID + GID</option>
|
||||
<option value="GID">MID + GID</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -471,7 +533,7 @@ export const UserAddAccountPage = () => {
|
||||
className="btn-50 btn-blue flex-1"
|
||||
type="button"
|
||||
onClick={handleSave}
|
||||
disabled={isPending}
|
||||
disabled={!isSaveButtonEnabled() || isPending}
|
||||
>
|
||||
{isPending ? '저장 중...' : '저장'}
|
||||
</button>
|
||||
|
||||
@@ -15,10 +15,10 @@ import {
|
||||
|
||||
export const UserLoginAuthInfoPage = () => {
|
||||
const location = useLocation();
|
||||
const { mid, usrid, tid } = location.state || {};
|
||||
const { mid, usrid, idCl, status } = location.state || {};
|
||||
const { navigate } = useNavigate();
|
||||
|
||||
const [activeTab, setActiveTab] = useState<AccountUserTabKeys>(AccountUserTabKeys.LoginAuthInfo);
|
||||
const [activeTab, ] = useState<AccountUserTabKeys>(AccountUserTabKeys.LoginAuthInfo);
|
||||
useSetHeaderTitle('사용자 설정');
|
||||
useSetHeaderType(HeaderType.LeftArrow);
|
||||
useSetFooterMode(true);
|
||||
@@ -26,22 +26,6 @@ export const UserLoginAuthInfoPage = () => {
|
||||
navigate(PATHS.account.user.manage);
|
||||
});
|
||||
|
||||
// const { mutateAsync: userFindAuthMethod } = useUserFindAuthMethodMutation();
|
||||
|
||||
// const callUserFindAuthMethod = (mid: string, usrid: string) => {
|
||||
// let parms: UserFindAuthMethodParams = {
|
||||
// mid: mid,
|
||||
// usrid: usrid
|
||||
// }
|
||||
// userFindAuthMethod(params).then((rs: UserFindAuthMethodResponse) => {
|
||||
// console.log("User Find Auth Method: ", rs)
|
||||
// });
|
||||
// }
|
||||
|
||||
// useEffect(() => {
|
||||
// callUserFindAuthMethod(mid, usrid);
|
||||
// }, [mid, usrid]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<main>
|
||||
@@ -49,13 +33,16 @@ export const UserLoginAuthInfoPage = () => {
|
||||
<div className="tab-pane pt-46 active">
|
||||
<AccountUserTab
|
||||
activeTab={ activeTab }
|
||||
tid={tid || ''}
|
||||
mid={mid || ''}
|
||||
usrid={usrid || ''}
|
||||
idCl={idCl || ''}
|
||||
status={status || ''}
|
||||
></AccountUserTab>
|
||||
<UserLoginAuthInfoWrap
|
||||
mid={mid || ''}
|
||||
usrid={usrid || ''}
|
||||
idCl={idCl || ''}
|
||||
status={status || ''}
|
||||
></UserLoginAuthInfoWrap>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
export const UserManagePage = () => {
|
||||
const { navigate } = useNavigate();
|
||||
|
||||
const [activeTab, setActiveTab] = useState<AccountTabKeys>(AccountTabKeys.UserManage);
|
||||
const [activeTab, ] = useState<AccountTabKeys>(AccountTabKeys.UserManage);
|
||||
useSetHeaderTitle('계정 관리');
|
||||
useSetHeaderType(HeaderType.LeftArrow);
|
||||
useSetFooterMode(true);
|
||||
|
||||
@@ -1,28 +1,169 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router';
|
||||
import { PATHS } from '@/shared/constants/paths';
|
||||
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
|
||||
import { HeaderType } from '@/entities/common/model/types';
|
||||
import {
|
||||
import {
|
||||
useSetHeaderTitle,
|
||||
useSetHeaderType,
|
||||
useSetFooterMode,
|
||||
useSetOnBack
|
||||
} from '@/widgets/sub-layout/use-sub-layout';
|
||||
import { useLocation } from 'react-router';
|
||||
import { useUserMenuPermissionsSaveMutation } from '@/entities/user/api/use-user-menu-permission-save-mutation';
|
||||
// import { useUserMenuPermissionsMutation } from '@/entities/user/api/use-user-menu-permission-mutation';
|
||||
import { UserMenuPermissionData } from '@/entities/user/model/types';
|
||||
|
||||
// 권한 비트 플래그 (실제 API 데이터 기준)
|
||||
const PERMISSION = {
|
||||
READ: 1, // 조회
|
||||
SAVE: 2, // 저장
|
||||
EXECUTE: 4, // 실행
|
||||
DOWNLOAD: 8 // 다운로드
|
||||
};
|
||||
|
||||
export const UserMenuAuthPage = () => {
|
||||
const { navigate } = useNavigate();
|
||||
const location = useLocation();
|
||||
const { mid, usrid, menuName, subMenu, menuGrants } = location.state || {};
|
||||
|
||||
useSetHeaderTitle('사용자 설정');
|
||||
// 메뉴별 권한 상태 관리
|
||||
const [permissions, setPermissions] = useState<Record<number, number>>({});
|
||||
const [initialPermissions, setInitialPermissions] = useState<Record<number, number>>({});
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const savePermissionsMutation = useUserMenuPermissionsSaveMutation();
|
||||
// const getPermissionsMutation = useUserMenuPermissionsMutation();
|
||||
|
||||
useSetHeaderTitle(menuName);
|
||||
useSetHeaderType(HeaderType.LeftArrow);
|
||||
useSetFooterMode(true);
|
||||
useSetOnBack(() => {
|
||||
navigate(PATHS.account.user.accountAuth);
|
||||
navigate(PATHS.account.user.accountAuth, {
|
||||
state: {
|
||||
mid,
|
||||
usrid,
|
||||
idCl: location.state?.idCl,
|
||||
status: location.state?.status
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
console.log('menuGrants : ', menuGrants);
|
||||
}, [menuGrants]);
|
||||
|
||||
});
|
||||
// // 메뉴 권한 조회 함수
|
||||
// const loadPermissions = useCallback(() => {
|
||||
// if (mid && usrid) {
|
||||
// getPermissionsMutation.mutate(
|
||||
// { mid, usrid },
|
||||
// {
|
||||
// onSuccess: (response) => {
|
||||
// if (response.data) {
|
||||
// const perms: Record<number, number> = {};
|
||||
// response.data.forEach((item: UserMenuPermissionData) => {
|
||||
// perms[item.menuId] = item.grant;
|
||||
// });
|
||||
// setPermissions(perms);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// }, [mid, usrid]);
|
||||
|
||||
// menuGrants를 권한 상태로 초기화 또는 API에서 권한 조회
|
||||
useEffect(() => {
|
||||
if (menuGrants && Array.isArray(menuGrants) && menuGrants.length > 0) {
|
||||
// menuGrants 데이터가 있으면 사용
|
||||
const initial: Record<number, number> = {};
|
||||
menuGrants.forEach((grant: { menuId: number; grant: number }) => {
|
||||
initial[grant.menuId] = grant.grant || 0;
|
||||
});
|
||||
setPermissions(initial);
|
||||
setInitialPermissions(initial);
|
||||
} else {
|
||||
// menuGrants가 없거나 빈 배열이면 API에서 권한 조회
|
||||
// loadPermissions();
|
||||
}
|
||||
}, [menuGrants]);
|
||||
|
||||
// 권한 변경 감지
|
||||
useEffect(() => {
|
||||
const hasAnyChange = Object.keys(permissions).some(key => {
|
||||
const menuId = Number(key);
|
||||
return permissions[menuId] !== (initialPermissions[menuId] || 0);
|
||||
}) || Object.keys(initialPermissions).some(key => {
|
||||
const menuId = Number(key);
|
||||
return (permissions[menuId] || 0) !== initialPermissions[menuId];
|
||||
});
|
||||
setHasChanges(hasAnyChange);
|
||||
}, [permissions, initialPermissions]);
|
||||
|
||||
// 특정 메뉴의 권한 체크
|
||||
const hasPermission = (menuId: number, flag: number): boolean => {
|
||||
const grant = permissions[menuId] || 0;
|
||||
return (grant & flag) === flag;
|
||||
};
|
||||
|
||||
// 권한 토글 처리
|
||||
const togglePermission = (menuId: number, flag: number) => {
|
||||
setPermissions(prev => {
|
||||
const currentGrant = prev[menuId] || 0;
|
||||
const newGrant = currentGrant ^ flag;
|
||||
return {
|
||||
...prev,
|
||||
[menuId]: newGrant
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 메인 토글 처리 (VIEW 권한)
|
||||
const toggleMainPermission = (menuId: number) => {
|
||||
setPermissions(prev => {
|
||||
const currentGrant = prev[menuId] || 0;
|
||||
if (currentGrant > 0) {
|
||||
// 권한이 있으면 모두 제거
|
||||
return {
|
||||
...prev,
|
||||
[menuId]: 0
|
||||
};
|
||||
} else {
|
||||
// 권한이 없으면 VIEW 권한만 부여
|
||||
return {
|
||||
...prev,
|
||||
[menuId]: PERMISSION.READ
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 권한 저장
|
||||
const handleSave = () => {
|
||||
const namsUserMenuAccess: UserMenuPermissionData[] = Object.entries(permissions).map(
|
||||
([menuId, grant]) => ({
|
||||
menuId: Number(menuId),
|
||||
usrid: usrid,
|
||||
grant: grant
|
||||
})
|
||||
);
|
||||
|
||||
savePermissionsMutation.mutate(
|
||||
{ mid, namsUserMenuAccess },
|
||||
{
|
||||
onSuccess: () => {
|
||||
alert('권한이 저장되었습니다.');
|
||||
// 저장 성공 후 초기값 업데이트
|
||||
setInitialPermissions({...permissions});
|
||||
setHasChanges(false);
|
||||
},
|
||||
onError: (error) => {
|
||||
alert('권한 저장에 실패했습니다.');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -33,89 +174,83 @@ export const UserMenuAuthPage = () => {
|
||||
<div className="desc service-tip">메뉴별 사용 권한을 설정해 주세요.</div>
|
||||
<div className="desc service-tip">선택한 권한에 따라 기능 이용이 제한됩니다.</div>
|
||||
|
||||
<div className="settings-section nopadding">
|
||||
<div className="settings-row">
|
||||
<div className="settings-row-title bd-style">거래내역 조회</div>
|
||||
<label className="settings-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked
|
||||
/>
|
||||
<span className="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="set-divider"></div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-row-title bd-sub dot">등록</span>
|
||||
<label className="settings-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked
|
||||
/>
|
||||
<span className="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-row-title bd-sub dot">수정</span>
|
||||
<label className="settings-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked
|
||||
/>
|
||||
<span className="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-row-title bd-sub dot">삭제</span>
|
||||
<label className="settings-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked
|
||||
/>
|
||||
<span className="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-row-title bd-sub dot">다운로드</span>
|
||||
<label className="settings-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked
|
||||
/>
|
||||
<span className="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ht-20"></div>
|
||||
<div className="settings-section">
|
||||
<div className="settings-row">
|
||||
<div className="settings-row-title bd-style">현금영수증 발행</div>
|
||||
<label className="settings-switch">
|
||||
<input type="checkbox" />
|
||||
<span className="slider"></span>
|
||||
</label>
|
||||
{subMenu && subMenu.map((menu: { menuId: number; menuName: string }) => {
|
||||
const menuGrant = permissions[menu.menuId] || 0;
|
||||
const hasAccess = menuGrant > 0;
|
||||
|
||||
return (
|
||||
<div key={menu.menuId}>
|
||||
<div className="settings-section nopadding">
|
||||
<div className="settings-row">
|
||||
<div className="settings-row-title bd-style">{menu.menuName}</div>
|
||||
<label className="settings-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={hasAccess}
|
||||
onChange={() => toggleMainPermission(menu.menuId)}
|
||||
/>
|
||||
<span className="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="permission-details"
|
||||
style={{
|
||||
maxHeight: hasAccess ? '300px' : '0',
|
||||
opacity: hasAccess ? 1 : 0,
|
||||
overflow: 'hidden',
|
||||
transition: 'max-height 0.3s ease-in-out, opacity 0.3s ease-in-out'
|
||||
}}
|
||||
>
|
||||
<div className="set-divider"></div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-row-title bd-sub dot">저장</span>
|
||||
<label className="settings-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={hasPermission(menu.menuId, PERMISSION.SAVE)}
|
||||
onChange={() => togglePermission(menu.menuId, PERMISSION.SAVE)}
|
||||
/>
|
||||
<span className="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-row-title bd-sub dot">실행</span>
|
||||
<label className="settings-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={hasPermission(menu.menuId, PERMISSION.EXECUTE)}
|
||||
onChange={() => togglePermission(menu.menuId, PERMISSION.EXECUTE)}
|
||||
/>
|
||||
<span className="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="settings-row">
|
||||
<span className="settings-row-title bd-sub dot">다운로드</span>
|
||||
<label className="settings-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={hasPermission(menu.menuId, PERMISSION.DOWNLOAD)}
|
||||
onChange={() => togglePermission(menu.menuId, PERMISSION.DOWNLOAD)}
|
||||
/>
|
||||
<span className="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ht-20"></div>
|
||||
</div>
|
||||
<div className="settings-row">
|
||||
<div className="settings-row-title bd-style">에스크로</div>
|
||||
<label className="settings-switch">
|
||||
<input type="checkbox" />
|
||||
<span className="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="settings-row">
|
||||
<div className="settings-row-title bd-style">빌링</div>
|
||||
<label className="settings-switch">
|
||||
<input type="checkbox" />
|
||||
<span className="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<div className="apply-row">
|
||||
<button
|
||||
<button
|
||||
className="btn-50 btn-blue flex-1"
|
||||
type="button"
|
||||
>저장</button>
|
||||
type="button"
|
||||
onClick={handleSave}
|
||||
disabled={!hasChanges || savePermissionsMutation.isPending}
|
||||
>
|
||||
{savePermissionsMutation.isPending ? '저장 중...' : '저장'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user