refactor: 네비게이션 헤더 최신 디자인으로 전면 개편 및 로그인 버그 수정
- fix: 로그인 API에서 user.role_name 필드 올바르게 사용 (auth.service.js) - refactor: navbar 컴포넌트를 최신 dashboard-header 스타일로 전환 - refactor: 구버전 work-report-header 제거 (6개 페이지) - refactor: load-navbar.js를 최신 헤더 구조에 맞게 업데이트 - style: 파란색 그라데이션 헤더, 실시간 시계, 향상된 프로필 메뉴 - docs: 2026-01-20 개발 로그 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -35,19 +35,20 @@ const getAllUsers = asyncHandler(async (req, res) => {
|
||||
try {
|
||||
const query = `
|
||||
SELECT
|
||||
user_id,
|
||||
username,
|
||||
name,
|
||||
email,
|
||||
phone,
|
||||
role,
|
||||
access_level,
|
||||
is_active,
|
||||
created_at,
|
||||
updated_at,
|
||||
last_login
|
||||
FROM users
|
||||
ORDER BY created_at DESC
|
||||
u.user_id,
|
||||
u.username,
|
||||
u.name,
|
||||
u.email,
|
||||
u.role_id,
|
||||
r.name as role,
|
||||
u._access_level_old as access_level,
|
||||
u.is_active,
|
||||
u.created_at,
|
||||
u.updated_at,
|
||||
u.last_login_at as last_login
|
||||
FROM users u
|
||||
LEFT JOIN roles r ON u.role_id = r.id
|
||||
ORDER BY u.created_at DESC
|
||||
`;
|
||||
|
||||
const [users] = await db.execute(query);
|
||||
|
||||
@@ -23,13 +23,13 @@ exports.up = async function(knex) {
|
||||
|
||||
// 2. 사용자별 페이지 접근 권한 테이블 생성
|
||||
await knex.schema.createTable('user_page_access', function(table) {
|
||||
table.integer('user_id').unsigned().notNullable()
|
||||
table.integer('user_id').notNullable()
|
||||
.references('user_id').inTable('users').onDelete('CASCADE');
|
||||
table.integer('page_id').unsigned().notNullable()
|
||||
.references('id').inTable('pages').onDelete('CASCADE');
|
||||
table.boolean('can_access').defaultTo(true); // 접근 가능 여부
|
||||
table.timestamp('granted_at').defaultTo(knex.fn.now());
|
||||
table.integer('granted_by').unsigned() // 권한을 부여한 Admin의 user_id
|
||||
table.integer('granted_by') // 권한을 부여한 Admin의 user_id
|
||||
.references('user_id').inTable('users').onDelete('SET NULL');
|
||||
table.primary(['user_id', 'page_id']);
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* 2. 각 작업자에 대해 users 테이블에 계정 생성
|
||||
* 3. username은 이름 기반으로 자동 생성 (예: 홍길동 → hong.gildong)
|
||||
* 4. 초기 비밀번호는 '1234'로 통일 (첫 로그인 시 변경 권장)
|
||||
* 5. 현재 연도 연차 잔액 초기화
|
||||
* 5. 현재 연도 연차 잔액 초기화 (workers.annual_leave 사용)
|
||||
*/
|
||||
|
||||
const bcrypt = require('bcrypt');
|
||||
@@ -25,7 +25,7 @@ exports.up = async function(knex) {
|
||||
'workers.worker_name',
|
||||
'workers.email',
|
||||
'workers.status',
|
||||
'workers.base_annual_leave'
|
||||
'workers.annual_leave'
|
||||
);
|
||||
|
||||
console.log(`📊 계정이 없는 작업자: ${workersWithoutAccount.length}명`);
|
||||
@@ -41,7 +41,7 @@ exports.up = async function(knex) {
|
||||
|
||||
// User 역할 ID 조회
|
||||
const userRole = await knex('roles')
|
||||
.where('role_name', 'User')
|
||||
.where('name', 'User')
|
||||
.first();
|
||||
|
||||
if (!userRole) {
|
||||
@@ -77,7 +77,7 @@ exports.up = async function(knex) {
|
||||
await knex('worker_vacation_balance').insert({
|
||||
worker_id: worker.worker_id,
|
||||
year: currentYear,
|
||||
total_annual_leave: worker.base_annual_leave || 15,
|
||||
total_annual_leave: worker.annual_leave || 15,
|
||||
used_annual_leave: 0,
|
||||
created_at: knex.fn.now(),
|
||||
updated_at: knex.fn.now()
|
||||
|
||||
@@ -12,8 +12,8 @@ exports.up = async function(knex) {
|
||||
|
||||
// 1. Guest 역할 추가
|
||||
const [guestRoleId] = await knex('roles').insert({
|
||||
role_name: 'Guest',
|
||||
role_description: '게스트 (계정 없이 특정 기능 접근 가능)',
|
||||
name: 'Guest',
|
||||
description: '게스트 (계정 없이 특정 기능 접근 가능)',
|
||||
created_at: knex.fn.now(),
|
||||
updated_at: knex.fn.now()
|
||||
});
|
||||
@@ -44,7 +44,7 @@ exports.down = async function(knex) {
|
||||
|
||||
// 역할 제거
|
||||
await knex('roles')
|
||||
.where('role_name', 'Guest')
|
||||
.where('name', 'Guest')
|
||||
.delete();
|
||||
|
||||
console.log('✅ 게스트 역할 제거 완료');
|
||||
|
||||
0
api.hyungi.net/deploy.sh
Normal file → Executable file
0
api.hyungi.net/deploy.sh
Normal file → Executable file
@@ -129,8 +129,10 @@ const requireRole = (...roles) => {
|
||||
}
|
||||
|
||||
const userRole = req.user.role;
|
||||
const userRoleLower = userRole ? userRole.toLowerCase() : '';
|
||||
const rolesLower = roles.map(r => r.toLowerCase());
|
||||
|
||||
if (!roles.includes(userRole)) {
|
||||
if (!rolesLower.includes(userRoleLower)) {
|
||||
logger.warn('권한 체크 실패: 역할 불일치', {
|
||||
user_id: req.user.user_id || req.user.id,
|
||||
username: req.user.username,
|
||||
|
||||
@@ -5,7 +5,7 @@ class AttendanceModel {
|
||||
static async getDailyAttendanceRecords(date, workerId = null) {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT
|
||||
SELECT
|
||||
dar.*,
|
||||
w.worker_name,
|
||||
w.job_type,
|
||||
@@ -34,6 +34,39 @@ class AttendanceModel {
|
||||
return rows;
|
||||
}
|
||||
|
||||
// 기간별 근태 기록 조회 (월별 조회용)
|
||||
static async getDailyRecords(startDate, endDate, workerId = null) {
|
||||
const db = await getDb();
|
||||
let query = `
|
||||
SELECT
|
||||
dar.*,
|
||||
w.worker_name,
|
||||
w.job_type,
|
||||
wat.type_name as attendance_type_name,
|
||||
wat.type_code as attendance_type_code,
|
||||
vt.type_name as vacation_type_name,
|
||||
vt.type_code as vacation_type_code,
|
||||
vt.deduct_days as vacation_days
|
||||
FROM daily_attendance_records dar
|
||||
LEFT JOIN workers w ON dar.worker_id = w.worker_id
|
||||
LEFT JOIN work_attendance_types wat ON dar.attendance_type_id = wat.id
|
||||
LEFT JOIN vacation_types vt ON dar.vacation_type_id = vt.id
|
||||
WHERE dar.record_date BETWEEN ? AND ?
|
||||
`;
|
||||
|
||||
const params = [startDate, endDate];
|
||||
|
||||
if (workerId) {
|
||||
query += ' AND dar.worker_id = ?';
|
||||
params.push(workerId);
|
||||
}
|
||||
|
||||
query += ' ORDER BY dar.record_date ASC';
|
||||
|
||||
const [rows] = await db.execute(query, params);
|
||||
return rows;
|
||||
}
|
||||
|
||||
// 작업 보고서와 근태 기록 동기화 (시간 합산 및 상태 업데이트)
|
||||
static async syncWithWorkReports(workerId, date) {
|
||||
const db = await getDb();
|
||||
|
||||
@@ -5,7 +5,14 @@ const findByUsername = async (username) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
'SELECT user_id, username, password, name, email, role, access_level, worker_id, is_active, last_login_at, password_changed_at, failed_login_attempts, locked_until, created_at, updated_at FROM users WHERE username = ?', [username]
|
||||
`SELECT u.user_id, u.username, u.password, u.name, u.email,
|
||||
u.role_id, r.name as role_name,
|
||||
u._access_level_old as access_level, u.worker_id, u.is_active,
|
||||
u.last_login_at, u.password_changed_at, u.failed_login_attempts,
|
||||
u.locked_until, u.created_at, u.updated_at
|
||||
FROM users u
|
||||
LEFT JOIN roles r ON u.role_id = r.id
|
||||
WHERE u.username = ?`, [username]
|
||||
);
|
||||
return rows[0];
|
||||
} catch (err) {
|
||||
|
||||
@@ -22,7 +22,8 @@ router.use(verifyToken);
|
||||
* 관리자 권한 확인 미들웨어
|
||||
*/
|
||||
const adminOnly = (req, res, next) => {
|
||||
if (req.user && (req.user.role === 'admin' || req.user.role === 'system')) {
|
||||
const userRole = req.user?.role?.toLowerCase();
|
||||
if (req.user && (userRole === 'admin' || userRole === 'system' || userRole === 'system admin')) {
|
||||
next();
|
||||
} else {
|
||||
logger.warn('관리자 권한 없는 접근 시도', {
|
||||
@@ -87,8 +88,9 @@ router.get('/me/work-reports', async (req, res) => {
|
||||
router.get('/me/monthly-stats', async (req, res) => {
|
||||
try {
|
||||
const { year, month } = req.query;
|
||||
const db = require('../config/database');
|
||||
const stats = await db.query(
|
||||
const { getDb } = require('../dbPool');
|
||||
const db = await getDb();
|
||||
const [stats] = await db.execute(
|
||||
`SELECT
|
||||
SUM(total_work_hours) as month_hours,
|
||||
COUNT(DISTINCT record_date) as work_days
|
||||
|
||||
@@ -57,7 +57,7 @@ const loginService = async (username, password, ipAddress, userAgent) => {
|
||||
|
||||
|
||||
const token = jwt.sign(
|
||||
{ user_id: user.user_id, username: user.username, role: user.role, access_level: user.access_level, worker_id: user.worker_id, name: user.name || user.username },
|
||||
{ user_id: user.user_id, username: user.username, role: user.role_name, role_id: user.role_id, access_level: user.access_level, worker_id: user.worker_id, name: user.name || user.username },
|
||||
process.env.JWT_SECRET || 'your-secret-key',
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
|
||||
);
|
||||
@@ -80,7 +80,7 @@ const loginService = async (username, password, ipAddress, userAgent) => {
|
||||
user_id: user.user_id,
|
||||
username: user.username,
|
||||
name: user.name || user.username,
|
||||
role: user.role,
|
||||
role: user.role_name,
|
||||
access_level: user.access_level,
|
||||
worker_id: user.worker_id
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user