feat: 작업자-계정 통합 및 연차/출근 관리 시스템 구축

모든 작업자가 개인 계정으로 로그인하여 본인의 연차와 출근 기록을 확인할 수 있는 시스템을 구축했습니다.

주요 기능:
- 작업자-계정 1:1 통합 (기존 작업자 자동 계정 생성)
- 연차 관리 시스템 (연도별 잔액 관리)
- 출근 기록 시스템 (일일 근태 기록)
- 나의 대시보드 페이지 (개인 정보 조회)

데이터베이스:
- workers 테이블에 salary, base_annual_leave 컬럼 추가
- work_attendance_types, vacation_types 테이블 생성
- daily_attendance_records 테이블 생성
- worker_vacation_balance 테이블 생성
- 기존 작업자 자동 계정 생성 (username: 이름 기반)
- Guest 역할 추가

백엔드 API:
- 한글→영문 변환 유틸리티 (hangulToRoman.js)
- UserRoutes에 개인 정보 조회 API 추가
  - GET /api/users/me (내 정보)
  - GET /api/users/me/attendance-records (출근 기록)
  - GET /api/users/me/vacation-balance (연차 잔액)
  - GET /api/users/me/work-reports (작업 보고서)
  - GET /api/users/me/monthly-stats (월별 통계)

프론트엔드:
- 나의 대시보드 페이지 (my-dashboard.html)
- 연차 정보 위젯 (총/사용/잔여)
- 월별 출근 캘린더
- 근무 시간 통계
- 최근 작업 보고서 목록
- 네비게이션 바에 "나의 대시보드" 메뉴 추가

배포 시 주의사항:
- 마이그레이션 실행 필요
- 자동 생성된 계정 초기 비밀번호: 1234
- 작업자들에게 첫 로그인 후 비밀번호 변경 안내 필요

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-01-19 09:49:48 +09:00
parent 337cd14a15
commit 70630b380a
26 changed files with 2729 additions and 43 deletions

View File

@@ -598,37 +598,41 @@ router.get('/users', verifyToken, async (req, res) => {
try {
connection = await mysql.createConnection(dbConfig);
// 기본 쿼리
// 기본 쿼리 (role 테이블과 JOIN)
let query = `
SELECT
user_id,
username,
name,
email,
access_level,
worker_id,
is_active,
last_login_at,
created_at
FROM Users
SELECT
u.user_id,
u.username,
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.created_at
FROM users u
LEFT JOIN roles r ON u.role_id = r.id
WHERE 1=1
`;
const params = [];
// 필터링 옵션
if (req.query.active !== undefined) {
query += ' AND is_active = ?';
query += ' AND u.is_active = ?';
params.push(req.query.active === 'true');
}
// role_name으로 필터링 (access_level 대신)
if (req.query.access_level) {
query += ' AND access_level = ?';
params.push(req.query.access_level);
query += ' AND (u._access_level_old = ? OR r.name = ?)';
params.push(req.query.access_level, req.query.access_level);
}
query += ' ORDER BY created_at DESC';
query += ' ORDER BY u.created_at DESC';
const [rows] = await connection.execute(query, params);
const userList = rows.map(user => ({
@@ -636,7 +640,9 @@ router.get('/users', verifyToken, async (req, res) => {
username: user.username,
name: user.name || user.username,
email: user.email,
access_level: user.access_level,
role_id: user.role_id,
role_name: user.role_name,
access_level: user.access_level || user.role_name?.toLowerCase(), // 하위 호환성
worker_id: user.worker_id,
is_active: user.is_active,
last_login_at: user.last_login_at,