feat: 대시보드 작업장 현황 지도 구현
- 실시간 작업장 현황을 지도로 시각화 - 작업장 관리 페이지에서 정의한 구역 정보 활용 - TBM 작업자 및 방문자 현황 표시 주요 변경사항: - dashboard.html: 작업장 현황 섹션 추가 (기존 작업 현황 테이블 제거) - workplace-status.js: 지도 렌더링 및 데이터 통합 로직 구현 - modern-dashboard.js: 삭제된 DOM 요소 조건부 체크 추가 시각화 방식: - 인원 없음: 회색 테두리 + 작업장 이름 - 내부 작업자: 파란색 영역 + 인원 수 - 외부 방문자: 보라색 영역 + 인원 수 - 둘 다: 초록색 영역 + 총 인원 수 기술 구현: - Canvas API 기반 사각형 영역 렌더링 - map-regions API를 통한 데이터 일관성 보장 - 클릭 이벤트로 상세 정보 모달 표시 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -218,16 +218,16 @@ const updateUser = asyncHandler(async (req, res) => {
|
||||
checkAdminPermission(req.user);
|
||||
|
||||
const { id } = req.params;
|
||||
const { username, name, email, phone, role, password } = req.body;
|
||||
const { username, name, email, phone, role, role_id, password } = req.body;
|
||||
|
||||
if (!id || isNaN(id)) {
|
||||
throw new ValidationError('유효하지 않은 사용자 ID입니다');
|
||||
}
|
||||
|
||||
logger.info('사용자 수정 요청', { userId: id });
|
||||
logger.info('사용자 수정 요청', { userId: id, body: req.body });
|
||||
|
||||
// 최소 하나의 수정 필드가 필요
|
||||
if (!username && !name && email === undefined && phone === undefined && !role && !password) {
|
||||
if (!username && !name && email === undefined && phone === undefined && !role && !role_id && !password) {
|
||||
throw new ValidationError('수정할 필드가 없습니다');
|
||||
}
|
||||
|
||||
@@ -283,13 +283,35 @@ const updateUser = asyncHandler(async (req, res) => {
|
||||
values.push(phone || null);
|
||||
}
|
||||
|
||||
if (role) {
|
||||
const validRoles = ['admin', 'group_leader', 'worker'];
|
||||
if (!validRoles.includes(role)) {
|
||||
throw new ValidationError('유효하지 않은 권한입니다');
|
||||
// role_id 또는 role 문자열 처리
|
||||
if (role_id) {
|
||||
// role_id가 유효한지 확인
|
||||
const [roleCheck] = await db.execute('SELECT id, name FROM roles WHERE id = ?', [role_id]);
|
||||
if (roleCheck.length === 0) {
|
||||
throw new ValidationError('유효하지 않은 역할 ID입니다');
|
||||
}
|
||||
updates.push('role = ?, access_level = ?');
|
||||
values.push(role, role);
|
||||
updates.push('role_id = ?');
|
||||
values.push(role_id);
|
||||
logger.info('role_id로 역할 변경', { userId: id, role_id, role_name: roleCheck[0].name });
|
||||
} else if (role) {
|
||||
// role 문자열을 role_id로 변환 (하위 호환성)
|
||||
const roleNameMap = {
|
||||
'admin': 'Admin',
|
||||
'system': 'System Admin',
|
||||
'user': 'User',
|
||||
'guest': 'Guest',
|
||||
'group_leader': 'User', // 임시 매핑
|
||||
'worker': 'User' // 임시 매핑
|
||||
};
|
||||
const roleName = roleNameMap[role.toLowerCase()] || role;
|
||||
const [roleCheck] = await db.execute('SELECT id FROM roles WHERE name = ?', [roleName]);
|
||||
|
||||
if (roleCheck.length === 0) {
|
||||
throw new ValidationError(`유효하지 않은 권한입니다: ${role}`);
|
||||
}
|
||||
updates.push('role_id = ?');
|
||||
values.push(roleCheck[0].id);
|
||||
logger.info('role 문자열로 역할 변경', { userId: id, role, role_id: roleCheck[0].id });
|
||||
}
|
||||
|
||||
if (password) {
|
||||
@@ -297,7 +319,7 @@ const updateUser = asyncHandler(async (req, res) => {
|
||||
throw new ValidationError('비밀번호는 최소 6자 이상이어야 합니다');
|
||||
}
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
updates.push('password_hash = ?');
|
||||
updates.push('password = ?');
|
||||
values.push(hashedPassword);
|
||||
}
|
||||
|
||||
@@ -306,6 +328,7 @@ const updateUser = asyncHandler(async (req, res) => {
|
||||
|
||||
const updateQuery = `UPDATE users SET ${updates.join(', ')} WHERE user_id = ?`;
|
||||
|
||||
logger.info('실행할 UPDATE 쿼리', { query: updateQuery, values });
|
||||
await db.execute(updateQuery, values);
|
||||
|
||||
logger.info('사용자 수정 성공', {
|
||||
@@ -324,7 +347,7 @@ const updateUser = asyncHandler(async (req, res) => {
|
||||
if (error instanceof NotFoundError || error instanceof ValidationError || error instanceof ConflictError) {
|
||||
throw error;
|
||||
}
|
||||
logger.error('사용자 수정 실패', { userId: id, error: error.message });
|
||||
logger.error('사용자 수정 실패', { userId: id, error: error.message, stack: error.stack });
|
||||
throw new DatabaseError('사용자 정보를 수정하는데 실패했습니다');
|
||||
}
|
||||
});
|
||||
@@ -458,11 +481,127 @@ const deleteUser = asyncHandler(async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 사용자의 페이지 접근 권한 조회
|
||||
*/
|
||||
const getUserPageAccess = asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
if (!id || isNaN(id)) {
|
||||
throw new ValidationError('유효하지 않은 사용자 ID입니다');
|
||||
}
|
||||
|
||||
logger.info('사용자 페이지 권한 조회 요청', { userId: id });
|
||||
|
||||
const { getDb } = require('../dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
try {
|
||||
const query = `
|
||||
SELECT
|
||||
p.id as page_id,
|
||||
p.page_key,
|
||||
p.page_name,
|
||||
p.page_path,
|
||||
p.category,
|
||||
COALESCE(upa.can_access, 0) as can_access
|
||||
FROM pages p
|
||||
LEFT JOIN user_page_access upa ON p.id = upa.page_id AND upa.user_id = ?
|
||||
WHERE p.is_active = 1
|
||||
ORDER BY p.category, p.display_order
|
||||
`;
|
||||
|
||||
const [pageAccess] = await db.execute(query, [id]);
|
||||
|
||||
logger.info('사용자 페이지 권한 조회 성공', { userId: id, pageCount: pageAccess.length });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
pageAccess
|
||||
},
|
||||
message: '페이지 권한 조회 성공'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('사용자 페이지 권한 조회 실패', { userId: id, error: error.message });
|
||||
throw new DatabaseError('페이지 권한을 조회하는데 실패했습니다');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 사용자의 페이지 접근 권한 업데이트
|
||||
*/
|
||||
const updateUserPageAccess = asyncHandler(async (req, res) => {
|
||||
checkAdminPermission(req.user);
|
||||
|
||||
const { id } = req.params;
|
||||
const { pageAccess } = req.body;
|
||||
|
||||
if (!id || isNaN(id)) {
|
||||
throw new ValidationError('유효하지 않은 사용자 ID입니다');
|
||||
}
|
||||
|
||||
if (!Array.isArray(pageAccess)) {
|
||||
throw new ValidationError('pageAccess는 배열이어야 합니다');
|
||||
}
|
||||
|
||||
logger.info('사용자 페이지 권한 업데이트 요청', {
|
||||
userId: id,
|
||||
pageCount: pageAccess.length,
|
||||
updatedBy: req.user.username
|
||||
});
|
||||
|
||||
const { getDb } = require('../dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
try {
|
||||
// 트랜잭션 시작
|
||||
await db.query('START TRANSACTION');
|
||||
|
||||
// 기존 권한 삭제
|
||||
await db.execute('DELETE FROM user_page_access WHERE user_id = ?', [id]);
|
||||
|
||||
// 새 권한 삽입
|
||||
if (pageAccess.length > 0) {
|
||||
const values = pageAccess.map(p => [id, p.page_id, p.can_access]);
|
||||
const placeholders = values.map(() => '(?, ?, ?)').join(', ');
|
||||
const flatValues = values.flat();
|
||||
|
||||
await db.execute(
|
||||
`INSERT INTO user_page_access (user_id, page_id, can_access) VALUES ${placeholders}`,
|
||||
flatValues
|
||||
);
|
||||
}
|
||||
|
||||
// 커밋
|
||||
await db.query('COMMIT');
|
||||
|
||||
logger.info('사용자 페이지 권한 업데이트 성공', {
|
||||
userId: id,
|
||||
pageCount: pageAccess.length,
|
||||
updatedBy: req.user.username
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { user_id: id },
|
||||
message: '페이지 권한이 성공적으로 업데이트되었습니다'
|
||||
});
|
||||
} catch (error) {
|
||||
// 롤백
|
||||
await db.query('ROLLBACK');
|
||||
logger.error('사용자 페이지 권한 업데이트 실패', { userId: id, error: error.message });
|
||||
throw new DatabaseError('페이지 권한을 업데이트하는데 실패했습니다');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
getAllUsers,
|
||||
getUserById,
|
||||
createUser,
|
||||
updateUser,
|
||||
updateUserStatus,
|
||||
deleteUser
|
||||
deleteUser,
|
||||
getUserPageAccess,
|
||||
updateUserPageAccess
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user