fix: 사용자 관리 시스템 백엔드 API 통합

- 사용자 목록 로드를 localStorage에서 AuthAPI.getUsers()로 변경
- 비밀번호 초기화를 localStorage 조작에서 AuthAPI.resetPassword()로 변경
- 사용자 삭제 기능 백엔드 API 연동 확인
- 사용자 추가/목록/삭제 모든 기능이 백엔드 DB와 동기화됨
- localStorage 하드코딩 문제 해결로 일관된 데이터 관리 구현

Fixes:
- 사용자 추가 후 목록에 표시되지 않던 문제
- 비밀번호 초기화가 실제 DB에 반영되지 않던 문제
- 백엔드 API와 localStorage 간 데이터 불일치 문제
This commit is contained in:
hyungi
2025-10-24 10:27:45 +09:00
parent b024a178d0
commit 0e57cb99e8
4 changed files with 315 additions and 9 deletions

View File

@@ -305,10 +305,14 @@
// 사용자 목록 로드
async function loadUsers() {
try {
// 백엔드 API에서 사용자 목록 로드
users = await AuthAPI.getUsers();
displayUsers();
} catch (error) {
console.error('사용자 목록 로드 실패:', error);
// API 실패 시 빈 배열로 초기화
users = [];
displayUsers();
}
}
@@ -339,18 +343,53 @@
</span>
</div>
</div>
${user.username !== 'hyungi' ? `
<div class="flex gap-2">
<button
onclick="deleteUser('${user.username}')"
class="px-3 py-1 bg-red-500 text-white rounded hover:bg-red-600 transition-colors text-sm"
onclick="resetPassword('${user.username}')"
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>
` : ''}
${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>
`).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) {
if (!confirm(`정말 ${username} 사용자를 삭제하시겠습니까?`)) {

View File

@@ -214,9 +214,9 @@
<a href="project-management.html" class="nav-link" style="display:none;" id="projectBtn">
<i class="fas fa-folder-open mr-2"></i>프로젝트 관리
</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>관리
</a>
</button>
</div>
</div>
</nav>
@@ -500,6 +500,17 @@
}
}
// 관리 버튼 클릭 처리
function handleAdminClick() {
if (currentUser.role === 'admin') {
// 관리자: 사용자 관리 페이지로 이동
window.location.href = 'admin.html';
} else {
// 일반 사용자: 비밀번호 변경 모달 표시
showPasswordChangeModal();
}
}
// 섹션 전환
// URL 해시 처리
function handleUrlHash() {
@@ -1414,6 +1425,125 @@
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') {
const toast = document.createElement('div');

View File

@@ -106,9 +106,9 @@
<a href="project-management.html" class="nav-link" style="display:none;" id="projectBtn">
<i class="fas fa-folder-open mr-2"></i>프로젝트 관리
</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>사용자 관리
</a>
</button>
</div>
</div>
</nav>
@@ -599,6 +599,136 @@
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() {
localStorage.removeItem('currentUser');

View File

@@ -125,6 +125,13 @@ const AuthAPI = {
current_password: currentPassword,
new_password: newPassword
})
}),
resetPassword: (userId, newPassword = '000000') => apiRequest(`/auth/users/${userId}`, {
method: 'PUT',
body: JSON.stringify({
password: newPassword
})
})
};