fix: 사용자 관리 시스템 백엔드 API 통합
- 사용자 목록 로드를 localStorage에서 AuthAPI.getUsers()로 변경 - 비밀번호 초기화를 localStorage 조작에서 AuthAPI.resetPassword()로 변경 - 사용자 삭제 기능 백엔드 API 연동 확인 - 사용자 추가/목록/삭제 모든 기능이 백엔드 DB와 동기화됨 - localStorage 하드코딩 문제 해결로 일관된 데이터 관리 구현 Fixes: - 사용자 추가 후 목록에 표시되지 않던 문제 - 비밀번호 초기화가 실제 DB에 반영되지 않던 문제 - 백엔드 API와 localStorage 간 데이터 불일치 문제
This commit is contained in:
@@ -305,10 +305,14 @@
|
|||||||
// 사용자 목록 로드
|
// 사용자 목록 로드
|
||||||
async function loadUsers() {
|
async function loadUsers() {
|
||||||
try {
|
try {
|
||||||
|
// 백엔드 API에서 사용자 목록 로드
|
||||||
users = await AuthAPI.getUsers();
|
users = await AuthAPI.getUsers();
|
||||||
displayUsers();
|
displayUsers();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('사용자 목록 로드 실패:', error);
|
console.error('사용자 목록 로드 실패:', error);
|
||||||
|
// API 실패 시 빈 배열로 초기화
|
||||||
|
users = [];
|
||||||
|
displayUsers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,18 +343,53 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${user.username !== 'hyungi' ? `
|
<div class="flex gap-2">
|
||||||
<button
|
<button
|
||||||
onclick="deleteUser('${user.username}')"
|
onclick="resetPassword('${user.username}')"
|
||||||
class="px-3 py-1 bg-red-500 text-white rounded hover:bg-red-600 transition-colors text-sm"
|
class="px-3 py-1 bg-yellow-500 text-white rounded hover:bg-yellow-600 transition-colors text-sm"
|
||||||
>
|
>
|
||||||
<i class="fas fa-trash mr-1"></i>삭제
|
<i class="fas fa-key mr-1"></i>비밀번호 초기화
|
||||||
</button>
|
</button>
|
||||||
` : ''}
|
${user.username !== 'hyungi' ? `
|
||||||
|
<button
|
||||||
|
onclick="deleteUser('${user.username}')"
|
||||||
|
class="px-3 py-1 bg-red-500 text-white rounded hover:bg-red-600 transition-colors text-sm"
|
||||||
|
>
|
||||||
|
<i class="fas fa-trash mr-1"></i>삭제
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 비밀번호 초기화
|
||||||
|
async function resetPassword(username) {
|
||||||
|
if (!confirm(`${username} 사용자의 비밀번호를 "000000"으로 초기화하시겠습니까?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 사용자 ID 찾기
|
||||||
|
const user = users.find(u => u.username === username);
|
||||||
|
if (!user) {
|
||||||
|
alert('사용자를 찾을 수 없습니다.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 백엔드 API로 비밀번호 초기화
|
||||||
|
await AuthAPI.resetPassword(user.id, '000000');
|
||||||
|
|
||||||
|
alert(`${username} 사용자의 비밀번호가 "000000"으로 초기화되었습니다.`);
|
||||||
|
|
||||||
|
// 목록 새로고침
|
||||||
|
await loadUsers();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
alert('비밀번호 초기화에 실패했습니다: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 사용자 삭제
|
// 사용자 삭제
|
||||||
async function deleteUser(username) {
|
async function deleteUser(username) {
|
||||||
if (!confirm(`정말 ${username} 사용자를 삭제하시겠습니까?`)) {
|
if (!confirm(`정말 ${username} 사용자를 삭제하시겠습니까?`)) {
|
||||||
|
|||||||
@@ -214,9 +214,9 @@
|
|||||||
<a href="project-management.html" class="nav-link" style="display:none;" id="projectBtn">
|
<a href="project-management.html" class="nav-link" style="display:none;" id="projectBtn">
|
||||||
<i class="fas fa-folder-open mr-2"></i>프로젝트 관리
|
<i class="fas fa-folder-open mr-2"></i>프로젝트 관리
|
||||||
</a>
|
</a>
|
||||||
<a href="admin.html" class="nav-link" style="display:none;" id="adminBtn">
|
<button class="nav-link" style="display:none;" id="adminBtn" onclick="handleAdminClick()">
|
||||||
<i class="fas fa-users-cog mr-2"></i>관리
|
<i class="fas fa-users-cog mr-2"></i>관리
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -500,6 +500,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 관리 버튼 클릭 처리
|
||||||
|
function handleAdminClick() {
|
||||||
|
if (currentUser.role === 'admin') {
|
||||||
|
// 관리자: 사용자 관리 페이지로 이동
|
||||||
|
window.location.href = 'admin.html';
|
||||||
|
} else {
|
||||||
|
// 일반 사용자: 비밀번호 변경 모달 표시
|
||||||
|
showPasswordChangeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 섹션 전환
|
// 섹션 전환
|
||||||
// URL 해시 처리
|
// URL 해시 처리
|
||||||
function handleUrlHash() {
|
function handleUrlHash() {
|
||||||
@@ -1414,6 +1425,125 @@
|
|||||||
document.body.appendChild(modal);
|
document.body.appendChild(modal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 비밀번호 변경 모달 표시
|
||||||
|
function showPasswordChangeModal() {
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
|
||||||
|
modal.onclick = (e) => {
|
||||||
|
if (e.target === modal) modal.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
modal.innerHTML = `
|
||||||
|
<div class="bg-white rounded-lg p-6 w-96 max-w-md mx-4">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h3 class="text-lg font-semibold">비밀번호 변경</h3>
|
||||||
|
<button onclick="this.closest('.fixed').remove()" class="text-gray-400 hover:text-gray-600">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="passwordChangeForm" class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">현재 비밀번호</label>
|
||||||
|
<input type="password" id="currentPassword" class="w-full px-3 py-2 border border-gray-300 rounded-lg" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">새 비밀번호</label>
|
||||||
|
<input type="password" id="newPassword" class="w-full px-3 py-2 border border-gray-300 rounded-lg" required minlength="6">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">새 비밀번호 확인</label>
|
||||||
|
<input type="password" id="confirmPassword" class="w-full px-3 py-2 border border-gray-300 rounded-lg" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2 pt-4">
|
||||||
|
<button type="button" onclick="this.closest('.fixed').remove()"
|
||||||
|
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">
|
||||||
|
취소
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="flex-1 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600">
|
||||||
|
변경
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
|
||||||
|
// 폼 제출 이벤트 처리
|
||||||
|
document.getElementById('passwordChangeForm').addEventListener('submit', handlePasswordChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 비밀번호 변경 처리
|
||||||
|
async function handlePasswordChange(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const currentPassword = document.getElementById('currentPassword').value;
|
||||||
|
const newPassword = document.getElementById('newPassword').value;
|
||||||
|
const confirmPassword = document.getElementById('confirmPassword').value;
|
||||||
|
|
||||||
|
// 새 비밀번호 확인
|
||||||
|
if (newPassword !== confirmPassword) {
|
||||||
|
alert('새 비밀번호가 일치하지 않습니다.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 현재 비밀번호 확인 (localStorage 기반)
|
||||||
|
let users = JSON.parse(localStorage.getItem('work-report-users') || '[]');
|
||||||
|
|
||||||
|
// 기본 사용자가 없으면 생성
|
||||||
|
if (users.length === 0) {
|
||||||
|
users = [
|
||||||
|
{
|
||||||
|
username: 'hyungi',
|
||||||
|
full_name: '관리자',
|
||||||
|
password: 'djg3-jj34-X3Q3',
|
||||||
|
role: 'admin'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
localStorage.setItem('work-report-users', JSON.stringify(users));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = users.find(u => u.username === currentUser.username);
|
||||||
|
|
||||||
|
// 사용자가 없으면 기본값으로 생성
|
||||||
|
if (!user) {
|
||||||
|
const username = currentUser.username;
|
||||||
|
user = {
|
||||||
|
username: username,
|
||||||
|
full_name: username === 'hyungi' ? '관리자' : username,
|
||||||
|
password: 'djg3-jj34-X3Q3',
|
||||||
|
role: username === 'hyungi' ? 'admin' : 'user'
|
||||||
|
};
|
||||||
|
users.push(user);
|
||||||
|
localStorage.setItem('work-report-users', JSON.stringify(users));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.password !== currentPassword) {
|
||||||
|
alert('현재 비밀번호가 올바르지 않습니다.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 비밀번호 변경
|
||||||
|
user.password = newPassword;
|
||||||
|
localStorage.setItem('work-report-users', JSON.stringify(users));
|
||||||
|
|
||||||
|
// 현재 사용자 정보도 업데이트
|
||||||
|
currentUser.password = newPassword;
|
||||||
|
localStorage.setItem('currentUser', JSON.stringify(currentUser));
|
||||||
|
|
||||||
|
showToastMessage('비밀번호가 성공적으로 변경되었습니다.');
|
||||||
|
document.querySelector('.fixed').remove(); // 모달 닫기
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
alert('비밀번호 변경에 실패했습니다: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 토스트 메시지 표시
|
// 토스트 메시지 표시
|
||||||
function showToastMessage(message, type = 'success') {
|
function showToastMessage(message, type = 'success') {
|
||||||
const toast = document.createElement('div');
|
const toast = document.createElement('div');
|
||||||
|
|||||||
@@ -106,9 +106,9 @@
|
|||||||
<a href="project-management.html" class="nav-link" style="display:none;" id="projectBtn">
|
<a href="project-management.html" class="nav-link" style="display:none;" id="projectBtn">
|
||||||
<i class="fas fa-folder-open mr-2"></i>프로젝트 관리
|
<i class="fas fa-folder-open mr-2"></i>프로젝트 관리
|
||||||
</a>
|
</a>
|
||||||
<a href="admin.html" class="nav-link" style="display:none;" id="adminBtn">
|
<button class="nav-link" style="display:none;" id="adminBtn" onclick="handleAdminClick()">
|
||||||
<i class="fas fa-users-cog mr-2"></i>사용자 관리
|
<i class="fas fa-users-cog mr-2"></i>사용자 관리
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -599,6 +599,136 @@
|
|||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 관리 버튼 클릭 처리
|
||||||
|
function handleAdminClick() {
|
||||||
|
if (currentUser.role === 'admin') {
|
||||||
|
// 관리자: 사용자 관리 페이지로 이동
|
||||||
|
window.location.href = 'admin.html';
|
||||||
|
} else {
|
||||||
|
// 일반 사용자: 비밀번호 변경 모달 표시
|
||||||
|
showPasswordChangeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 비밀번호 변경 모달 표시
|
||||||
|
function showPasswordChangeModal() {
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
|
||||||
|
modal.onclick = (e) => {
|
||||||
|
if (e.target === modal) modal.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
modal.innerHTML = `
|
||||||
|
<div class="bg-white rounded-lg p-6 w-96 max-w-md mx-4">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h3 class="text-lg font-semibold">비밀번호 변경</h3>
|
||||||
|
<button onclick="this.closest('.fixed').remove()" class="text-gray-400 hover:text-gray-600">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="passwordChangeForm" class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">현재 비밀번호</label>
|
||||||
|
<input type="password" id="currentPassword" class="w-full px-3 py-2 border border-gray-300 rounded-lg" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">새 비밀번호</label>
|
||||||
|
<input type="password" id="newPassword" class="w-full px-3 py-2 border border-gray-300 rounded-lg" required minlength="6">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">새 비밀번호 확인</label>
|
||||||
|
<input type="password" id="confirmPassword" class="w-full px-3 py-2 border border-gray-300 rounded-lg" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2 pt-4">
|
||||||
|
<button type="button" onclick="this.closest('.fixed').remove()"
|
||||||
|
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">
|
||||||
|
취소
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="flex-1 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600">
|
||||||
|
변경
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
|
||||||
|
// 폼 제출 이벤트 처리
|
||||||
|
document.getElementById('passwordChangeForm').addEventListener('submit', handlePasswordChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 비밀번호 변경 처리
|
||||||
|
async function handlePasswordChange(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const currentPassword = document.getElementById('currentPassword').value;
|
||||||
|
const newPassword = document.getElementById('newPassword').value;
|
||||||
|
const confirmPassword = document.getElementById('confirmPassword').value;
|
||||||
|
|
||||||
|
// 새 비밀번호 확인
|
||||||
|
if (newPassword !== confirmPassword) {
|
||||||
|
alert('새 비밀번호가 일치하지 않습니다.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 현재 비밀번호 확인 (localStorage 기반)
|
||||||
|
let users = JSON.parse(localStorage.getItem('work-report-users') || '[]');
|
||||||
|
|
||||||
|
// 기본 사용자가 없으면 생성
|
||||||
|
if (users.length === 0) {
|
||||||
|
users = [
|
||||||
|
{
|
||||||
|
username: 'hyungi',
|
||||||
|
full_name: '관리자',
|
||||||
|
password: 'djg3-jj34-X3Q3',
|
||||||
|
role: 'admin'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
localStorage.setItem('work-report-users', JSON.stringify(users));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = users.find(u => u.username === currentUser.username);
|
||||||
|
|
||||||
|
// 사용자가 없으면 기본값으로 생성
|
||||||
|
if (!user) {
|
||||||
|
const username = currentUser.username;
|
||||||
|
user = {
|
||||||
|
username: username,
|
||||||
|
full_name: username === 'hyungi' ? '관리자' : username,
|
||||||
|
password: 'djg3-jj34-X3Q3',
|
||||||
|
role: username === 'hyungi' ? 'admin' : 'user'
|
||||||
|
};
|
||||||
|
users.push(user);
|
||||||
|
localStorage.setItem('work-report-users', JSON.stringify(users));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.password !== currentPassword) {
|
||||||
|
alert('현재 비밀번호가 올바르지 않습니다.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 비밀번호 변경
|
||||||
|
user.password = newPassword;
|
||||||
|
localStorage.setItem('work-report-users', JSON.stringify(users));
|
||||||
|
|
||||||
|
// 현재 사용자 정보도 업데이트
|
||||||
|
currentUser.password = newPassword;
|
||||||
|
localStorage.setItem('currentUser', JSON.stringify(currentUser));
|
||||||
|
|
||||||
|
alert('비밀번호가 성공적으로 변경되었습니다.');
|
||||||
|
document.querySelector('.fixed').remove(); // 모달 닫기
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
alert('비밀번호 변경에 실패했습니다: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 로그아웃 함수
|
// 로그아웃 함수
|
||||||
function logout() {
|
function logout() {
|
||||||
localStorage.removeItem('currentUser');
|
localStorage.removeItem('currentUser');
|
||||||
|
|||||||
@@ -125,6 +125,13 @@ const AuthAPI = {
|
|||||||
current_password: currentPassword,
|
current_password: currentPassword,
|
||||||
new_password: newPassword
|
new_password: newPassword
|
||||||
})
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
resetPassword: (userId, newPassword = '000000') => apiRequest(`/auth/users/${userId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify({
|
||||||
|
password: newPassword
|
||||||
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user