diff --git a/tksupport/api/controllers/companyHolidayController.js b/tksupport/api/controllers/companyHolidayController.js new file mode 100644 index 0000000..1415805 --- /dev/null +++ b/tksupport/api/controllers/companyHolidayController.js @@ -0,0 +1,76 @@ +const companyHolidayModel = require('../models/companyHolidayModel'); + +const companyHolidayController = { + async getHolidays(req, res) { + try { + const year = parseInt(req.query.year) || new Date().getFullYear(); + const holidays = await companyHolidayModel.getByYear(year); + res.json({ success: true, data: holidays }); + } catch (error) { + console.error('전사 휴가 조회 오류:', error); + res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); + } + }, + + async createHoliday(req, res) { + try { + const { holiday_date, holiday_name, holiday_type, description } = req.body; + if (!holiday_date || !holiday_name || !holiday_type) { + return res.status(400).json({ success: false, error: '필수 필드가 누락되었습니다' }); + } + if (!['PAID', 'ANNUAL_DEDUCT'].includes(holiday_type)) { + return res.status(400).json({ success: false, error: '유효하지 않은 휴가 유형입니다' }); + } + + const created_by = req.user.user_id || req.user.id; + const result = await companyHolidayModel.create({ holiday_date, holiday_name, holiday_type, description, created_by }); + res.status(201).json({ success: true, message: '전사 휴가가 등록되었습니다', data: { id: result.insertId } }); + } catch (error) { + if (error.code === 'ER_DUP_ENTRY') { + return res.status(400).json({ success: false, error: '해당 날짜에 이미 전사 휴가가 등록되어 있습니다' }); + } + console.error('전사 휴가 등록 오류:', error); + res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); + } + }, + + async deleteHoliday(req, res) { + try { + const { id } = req.params; + const holiday = await companyHolidayModel.getById(id); + if (!holiday) { + return res.status(404).json({ success: false, error: '해당 전사 휴가를 찾을 수 없습니다' }); + } + if (holiday.deduction_applied_at) { + return res.status(400).json({ success: false, error: '차감이 실행된 휴가는 삭제할 수 없습니다' }); + } + + await companyHolidayModel.delete(id); + res.json({ success: true, message: '전사 휴가가 삭제되었습니다' }); + } catch (error) { + console.error('전사 휴가 삭제 오류:', error); + res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); + } + }, + + async applyDeduction(req, res) { + try { + const { id } = req.params; + const result = await companyHolidayModel.applyAnnualDeduction(id); + const response = { + success: true, + message: `연차 차감이 완료되었습니다 (${result.affected_count}명 적용)`, + data: result + }; + if (result.missing_balance_count > 0) { + response.warning = `${result.missing_balance_count}명의 사원은 연차 잔여일 데이터가 없어 차감되지 않았습니다`; + } + res.json(response); + } catch (error) { + console.error('연차 차감 오류:', error); + res.status(400).json({ success: false, error: error.message }); + } + } +}; + +module.exports = companyHolidayController; diff --git a/tksupport/api/controllers/vacationController.js b/tksupport/api/controllers/vacationController.js index e4386a8..23bf2c8 100644 --- a/tksupport/api/controllers/vacationController.js +++ b/tksupport/api/controllers/vacationController.js @@ -1,5 +1,6 @@ const vacationRequestModel = require('../models/vacationRequestModel'); const vacationBalanceModel = require('../models/vacationBalanceModel'); +const companyHolidayModel = require('../models/companyHolidayModel'); const { getPool } = require('../middleware/auth'); const vacationController = { @@ -228,6 +229,29 @@ const vacationController = { } }, + // ─── 내 휴가 현황 ─── + + async getMyStatus(req, res) { + try { + const userId = req.user.user_id || req.user.id; + const year = parseInt(req.query.year) || new Date().getFullYear(); + const startOfYear = `${year}-01-01`; + const endOfYear = `${year}-12-31`; + const [balances, requests, holidays] = await Promise.all([ + vacationBalanceModel.getByUserAndYear(userId, year), + vacationRequestModel.getAll({ user_id: userId, start_date: startOfYear, end_date: endOfYear }), + companyHolidayModel.getByYear(year) + ]); + res.json({ + success: true, + data: { balances, requests, company_holidays: holidays, overtime_hours: null } + }); + } catch (error) { + console.error('내 휴가 현황 조회 오류:', error); + res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); + } + }, + // ─── 잔여일 ─── async getMyBalance(req, res) { diff --git a/tksupport/api/controllers/vacationDashboardController.js b/tksupport/api/controllers/vacationDashboardController.js new file mode 100644 index 0000000..b1e262c --- /dev/null +++ b/tksupport/api/controllers/vacationDashboardController.js @@ -0,0 +1,52 @@ +const vacationDashboardModel = require('../models/vacationDashboardModel'); + +const vacationDashboardController = { + async getDashboard(req, res) { + try { + const year = parseInt(req.query.year) || new Date().getFullYear(); + const filters = { + department_id: req.query.department_id || null, + search_name: req.query.search_name || null + }; + + const [summary, rows] = await Promise.all([ + vacationDashboardModel.getSummary(year), + vacationDashboardModel.getEmployeeList(year, filters) + ]); + + // user_id별 그룹핑 + const employeeMap = {}; + rows.forEach(row => { + if (!employeeMap[row.user_id]) { + employeeMap[row.user_id] = { + user_id: row.user_id, + name: row.name, + username: row.username, + department_name: row.department_name, + hire_date: row.hire_date, + balances: [] + }; + } + if (row.id) { + employeeMap[row.user_id].balances.push({ + balance_type: row.balance_type, + type_name: row.type_name, + type_code: row.type_code, + total_days: row.total_days, + used_days: row.used_days, + remaining_days: row.remaining_days, + expires_at: row.expires_at + }); + } + }); + const employees = Object.values(employeeMap); + + res.json({ success: true, data: { summary, employees } }); + } catch (error) { + console.error('휴가 대시보드 조회 오류:', error); + res.status(500).json({ success: false, error: '서버 오류가 발생했습니다' }); + } + } +}; + +module.exports = vacationDashboardController; diff --git a/tksupport/api/db/migrations/002_section_c_additions.sql b/tksupport/api/db/migrations/002_section_c_additions.sql new file mode 100644 index 0000000..410851a --- /dev/null +++ b/tksupport/api/db/migrations/002_section_c_additions.sql @@ -0,0 +1,25 @@ +-- Section A 동시 개발 대비: 필요 테이블/컬럼 IF NOT EXISTS로 안전 생성 + +CREATE TABLE IF NOT EXISTS company_holidays ( + id INT AUTO_INCREMENT PRIMARY KEY, + holiday_date DATE NOT NULL, + holiday_name VARCHAR(100) NOT NULL, + holiday_type ENUM('PAID', 'ANNUAL_DEDUCT') NOT NULL, + description TEXT, + created_by INT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (created_by) REFERENCES sso_users(user_id), + UNIQUE KEY uq_holiday_date (holiday_date) +); + +-- Section C 전용: 중복 차감 방지 +ALTER TABLE company_holidays + ADD COLUMN IF NOT EXISTS deduction_applied_at TIMESTAMP NULL + COMMENT '연차차감 실행 시각 (NULL=미실행)'; + +-- sp_vacation_balances 컬럼 추가 (Section A 대비) +ALTER TABLE sp_vacation_balances + ADD COLUMN IF NOT EXISTS balance_type ENUM('AUTO','MANUAL','CARRY_OVER','LONG_SERVICE','COMPANY_GRANT') + DEFAULT 'AUTO'; +ALTER TABLE sp_vacation_balances + ADD COLUMN IF NOT EXISTS expires_at DATE NULL; diff --git a/tksupport/api/index.js b/tksupport/api/index.js index 511fe58..44c20fb 100644 --- a/tksupport/api/index.js +++ b/tksupport/api/index.js @@ -1,6 +1,8 @@ const express = require('express'); const cors = require('cors'); const vacationRoutes = require('./routes/vacationRoutes'); +const companyHolidayRoutes = require('./routes/companyHolidayRoutes'); +const vacationDashboardRoutes = require('./routes/vacationDashboardRoutes'); const vacationRequestModel = require('./models/vacationRequestModel'); const { requireAuth } = require('./middleware/auth'); @@ -34,6 +36,8 @@ app.get('/health', (req, res) => { }); // Routes +app.use('/api/vacation/company', companyHolidayRoutes); +app.use('/api/vacation/dashboard', vacationDashboardRoutes); app.use('/api/vacation', vacationRoutes); // 404 diff --git a/tksupport/api/middleware/auth.js b/tksupport/api/middleware/auth.js index 5869260..0cf54fc 100644 --- a/tksupport/api/middleware/auth.js +++ b/tksupport/api/middleware/auth.js @@ -75,4 +75,21 @@ function requirePage(pageName) { }; } -module.exports = { getPool, extractToken, requireAuth, requireAdmin, requirePage }; +function requireSupportTeam(req, res, next) { + const token = extractToken(req); + if (!token) { + return res.status(401).json({ success: false, error: '인증이 필요합니다' }); + } + try { + const decoded = jwt.verify(token, JWT_SECRET); + if (!['support_team', 'admin', 'system'].includes((decoded.role || '').toLowerCase())) { + return res.status(403).json({ success: false, error: '지원팀 이상 권한이 필요합니다' }); + } + req.user = decoded; + next(); + } catch { + return res.status(401).json({ success: false, error: '유효하지 않은 토큰입니다' }); + } +} + +module.exports = { getPool, extractToken, requireAuth, requireAdmin, requireSupportTeam, requirePage }; diff --git a/tksupport/api/models/companyHolidayModel.js b/tksupport/api/models/companyHolidayModel.js new file mode 100644 index 0000000..1d93cf9 --- /dev/null +++ b/tksupport/api/models/companyHolidayModel.js @@ -0,0 +1,93 @@ +const { getPool } = require('../middleware/auth'); + +const companyHolidayModel = { + async getByYear(year) { + const db = getPool(); + const [rows] = await db.query( + 'SELECT * FROM company_holidays WHERE YEAR(holiday_date) = ? ORDER BY holiday_date', + [year] + ); + return rows; + }, + + async getById(id) { + const db = getPool(); + const [rows] = await db.query('SELECT * FROM company_holidays WHERE id = ?', [id]); + return rows.length > 0 ? rows[0] : null; + }, + + async create(data) { + const db = getPool(); + const [result] = await db.query( + 'INSERT INTO company_holidays (holiday_date, holiday_name, holiday_type, description, created_by) VALUES (?, ?, ?, ?, ?)', + [data.holiday_date, data.holiday_name, data.holiday_type, data.description || null, data.created_by] + ); + return result; + }, + + async delete(id) { + const db = getPool(); + const [result] = await db.query('DELETE FROM company_holidays WHERE id = ?', [id]); + return result; + }, + + async applyAnnualDeduction(holidayId) { + const db = getPool(); + const conn = await db.getConnection(); + try { + await conn.beginTransaction(); + + // 1. SELECT FOR UPDATE — 동시 실행 방지 + const [holidays] = await conn.query( + 'SELECT * FROM company_holidays WHERE id = ? FOR UPDATE', [holidayId] + ); + if (holidays.length === 0) { + throw new Error('해당 전사 휴가를 찾을 수 없습니다'); + } + const holiday = holidays[0]; + if (holiday.holiday_type !== 'ANNUAL_DEDUCT') { + throw new Error('연차차감 유형의 휴가만 차감할 수 있습니다'); + } + if (holiday.deduction_applied_at) { + throw new Error('이미 차감이 실행된 휴가입니다'); + } + + // 2. vacation_types에서 ANNUAL_FULL id 조회 + const [types] = await conn.query( + "SELECT id FROM vacation_types WHERE type_code = 'ANNUAL_FULL'" + ); + if (types.length === 0) { + throw new Error('연차(ANNUAL_FULL) 유형이 존재하지 않습니다'); + } + const typeId = types[0].id; + + // 3. 전 활성 사원 연차 차감 (잔여일 무관 일괄 적용) + const [result] = await conn.query(` + UPDATE sp_vacation_balances SET used_days = used_days + 1, updated_at = NOW() + WHERE vacation_type_id = ? AND year = YEAR(?) AND balance_type = 'AUTO' + AND user_id IN (SELECT user_id FROM sso_users WHERE is_active = 1) + `, [typeId, holiday.holiday_date]); + + // 4. 차감 완료 표시 + await conn.query( + 'UPDATE company_holidays SET deduction_applied_at = NOW() WHERE id = ?', [holidayId] + ); + + // 5. balance 없는 사원 수 체크 (경고용) + const [activeUsers] = await conn.query( + 'SELECT COUNT(*) as cnt FROM sso_users WHERE is_active = 1' + ); + const missing = activeUsers[0].cnt - result.affectedRows; + + await conn.commit(); + return { affected_count: result.affectedRows, missing_balance_count: missing }; + } catch (err) { + await conn.rollback(); + throw err; + } finally { + conn.release(); + } + } +}; + +module.exports = companyHolidayModel; diff --git a/tksupport/api/models/vacationBalanceModel.js b/tksupport/api/models/vacationBalanceModel.js index 9236a23..d7a0a66 100644 --- a/tksupport/api/models/vacationBalanceModel.js +++ b/tksupport/api/models/vacationBalanceModel.js @@ -6,6 +6,8 @@ const vacationBalanceModel = { const [rows] = await db.query(` SELECT vb.*, + vb.balance_type, + vb.expires_at, vt.type_name, vt.type_code, vt.priority, @@ -13,7 +15,7 @@ const vacationBalanceModel = { (vb.total_days - vb.used_days) as remaining_days FROM sp_vacation_balances vb INNER JOIN vacation_types vt ON vb.vacation_type_id = vt.id - WHERE vb.user_id = ? AND vb.year = ? + WHERE vb.user_id = ? AND (vb.year = ? OR (vb.balance_type = 'LONG_SERVICE' AND vb.expires_at IS NULL)) ORDER BY vt.priority ASC, vt.type_name ASC `, [userId, year]); return rows; @@ -24,9 +26,12 @@ const vacationBalanceModel = { const [rows] = await db.query(` SELECT vb.*, + vb.balance_type, su.name as user_name, su.username, su.hire_date, + su.department_id, + COALESCE(d.department_name, '미배정') as department_name, vt.type_name, vt.type_code, vt.priority, @@ -34,7 +39,8 @@ const vacationBalanceModel = { FROM sp_vacation_balances vb INNER JOIN sso_users su ON vb.user_id = su.user_id INNER JOIN vacation_types vt ON vb.vacation_type_id = vt.id - WHERE vb.year = ? AND su.is_active = 1 + LEFT JOIN departments d ON su.department_id = d.department_id + WHERE (vb.year = ? OR (vb.balance_type = 'LONG_SERVICE' AND vb.expires_at IS NULL)) AND su.is_active = 1 ORDER BY su.name ASC, vt.priority ASC `, [year]); return rows; @@ -42,14 +48,16 @@ const vacationBalanceModel = { async allocate(data) { const db = getPool(); + const balanceType = data.balance_type || 'AUTO'; + const expiresAt = data.expires_at || null; const [result] = await db.query(` - INSERT INTO sp_vacation_balances (user_id, vacation_type_id, year, total_days, used_days, notes, created_by) - VALUES (?, ?, ?, ?, 0, ?, ?) + INSERT INTO sp_vacation_balances (user_id, vacation_type_id, year, total_days, used_days, notes, created_by, balance_type, expires_at) + VALUES (?, ?, ?, ?, 0, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE total_days = VALUES(total_days), notes = VALUES(notes), updated_at = NOW() - `, [data.user_id, data.vacation_type_id, data.year, data.total_days, data.notes || null, data.created_by]); + `, [data.user_id, data.vacation_type_id, data.year, data.total_days, data.notes || null, data.created_by, balanceType, expiresAt]); return result; }, diff --git a/tksupport/api/models/vacationDashboardModel.js b/tksupport/api/models/vacationDashboardModel.js new file mode 100644 index 0000000..f42c98f --- /dev/null +++ b/tksupport/api/models/vacationDashboardModel.js @@ -0,0 +1,55 @@ +const { getPool } = require('../middleware/auth'); + +const vacationDashboardModel = { + async getSummary(year) { + const db = getPool(); + const [rows] = await db.query(` + SELECT + d.department_id, COALESCE(d.department_name, '미배정') as department_name, + COUNT(DISTINCT su.user_id) as employee_count, + AVG(vb.total_days - vb.used_days) as avg_remaining, + SUM(CASE WHEN (vb.total_days - vb.used_days) <= 2 THEN 1 ELSE 0 END) as low_balance_count + FROM sso_users su + LEFT JOIN departments d ON su.department_id = d.department_id + LEFT JOIN sp_vacation_balances vb ON su.user_id = vb.user_id + AND vb.year = ? AND vb.balance_type = 'AUTO' + WHERE su.is_active = 1 + GROUP BY d.department_id + `, [year]); + return rows; + }, + + async getEmployeeList(year, filters = {}) { + const db = getPool(); + let query = ` + SELECT su.user_id, su.name, su.username, su.hire_date, su.department_id, + COALESCE(d.department_name, '미배정') as department_name, + vb.id, vb.balance_type, vb.total_days, vb.used_days, + (vb.total_days - vb.used_days) as remaining_days, + vb.expires_at, vt.type_name, vt.type_code + FROM sso_users su + LEFT JOIN departments d ON su.department_id = d.department_id + LEFT JOIN sp_vacation_balances vb ON su.user_id = vb.user_id + AND (vb.year = ? OR (vb.balance_type = 'LONG_SERVICE' AND vb.expires_at IS NULL)) + LEFT JOIN vacation_types vt ON vb.vacation_type_id = vt.id + WHERE su.is_active = 1 + `; + const params = [year]; + + if (filters.department_id) { + query += ' AND su.department_id = ?'; + params.push(filters.department_id); + } + if (filters.search_name) { + query += ' AND su.name LIKE ?'; + params.push(`%${filters.search_name}%`); + } + + query += ' ORDER BY su.name ASC'; + + const [rows] = await db.query(query, params); + return rows; + } +}; + +module.exports = vacationDashboardModel; diff --git a/tksupport/api/models/vacationRequestModel.js b/tksupport/api/models/vacationRequestModel.js index a33f612..71c388e 100644 --- a/tksupport/api/models/vacationRequestModel.js +++ b/tksupport/api/models/vacationRequestModel.js @@ -14,11 +14,13 @@ const vacationRequestModel = { vr.*, su.name as user_name, su.username, + COALESCE(d.department_name, '미배정') as department_name, vt.type_name as vacation_type_name, vt.type_code, reviewer.name as reviewer_name FROM sp_vacation_requests vr INNER JOIN sso_users su ON vr.user_id = su.user_id + LEFT JOIN departments d ON su.department_id = d.department_id INNER JOIN vacation_types vt ON vr.vacation_type_id = vt.id LEFT JOIN sso_users reviewer ON vr.reviewed_by = reviewer.user_id WHERE 1=1 @@ -45,6 +47,10 @@ const vacationRequestModel = { query += ' AND vr.vacation_type_id = ?'; params.push(filters.vacation_type_id); } + if (filters.department_id) { + query += ' AND su.department_id = ?'; + params.push(filters.department_id); + } query += ' ORDER BY vr.created_at DESC'; @@ -59,11 +65,13 @@ const vacationRequestModel = { vr.*, su.name as user_name, su.username, + COALESCE(d.department_name, '미배정') as department_name, vt.type_name as vacation_type_name, vt.type_code, reviewer.name as reviewer_name FROM sp_vacation_requests vr INNER JOIN sso_users su ON vr.user_id = su.user_id + LEFT JOIN departments d ON su.department_id = d.department_id INNER JOIN vacation_types vt ON vr.vacation_type_id = vt.id LEFT JOIN sso_users reviewer ON vr.reviewed_by = reviewer.user_id WHERE vr.request_id = ? @@ -113,10 +121,12 @@ const vacationRequestModel = { vr.*, su.name as user_name, su.username, + COALESCE(d.department_name, '미배정') as department_name, vt.type_name as vacation_type_name, vt.type_code FROM sp_vacation_requests vr INNER JOIN sso_users su ON vr.user_id = su.user_id + LEFT JOIN departments d ON su.department_id = d.department_id INNER JOIN vacation_types vt ON vr.vacation_type_id = vt.id WHERE vr.status = 'pending' ORDER BY vr.created_at ASC @@ -128,17 +138,21 @@ const vacationRequestModel = { const db = getPool(); const fs = require('fs'); const path = require('path'); - const sqlFile = path.join(__dirname, '..', 'db', 'migrations', '001_create_sp_tables.sql'); - const sql = fs.readFileSync(sqlFile, 'utf8'); - const statements = sql.split(';').map(s => s.trim()).filter(s => s.length > 0); - for (const stmt of statements) { - try { - await db.query(stmt); - } catch (err) { - if (err.code === 'ER_DUP_FIELDNAME' || err.code === 'ER_TABLE_EXISTS_ERROR') { - // Already migrated - } else { - throw err; + const migrationFiles = ['001_create_sp_tables.sql', '002_section_c_additions.sql']; + for (const file of migrationFiles) { + const sqlFile = path.join(__dirname, '..', 'db', 'migrations', file); + if (!fs.existsSync(sqlFile)) continue; + const sql = fs.readFileSync(sqlFile, 'utf8'); + const statements = sql.split(';').map(s => s.trim()).filter(s => s.length > 0); + for (const stmt of statements) { + try { + await db.query(stmt); + } catch (err) { + if (err.code === 'ER_DUP_FIELDNAME' || err.code === 'ER_TABLE_EXISTS_ERROR') { + // Already migrated + } else { + throw err; + } } } } diff --git a/tksupport/api/routes/companyHolidayRoutes.js b/tksupport/api/routes/companyHolidayRoutes.js new file mode 100644 index 0000000..6e5bb8a --- /dev/null +++ b/tksupport/api/routes/companyHolidayRoutes.js @@ -0,0 +1,13 @@ +const express = require('express'); +const router = express.Router(); +const { requireAuth, requireSupportTeam } = require('../middleware/auth'); +const ctrl = require('../controllers/companyHolidayController'); + +router.use(requireAuth); + +router.get('/holidays', ctrl.getHolidays); +router.post('/holidays', requireSupportTeam, ctrl.createHoliday); +router.delete('/holidays/:id', requireSupportTeam, ctrl.deleteHoliday); +router.post('/holidays/:id/apply-deduction', requireSupportTeam, ctrl.applyDeduction); + +module.exports = router; diff --git a/tksupport/api/routes/vacationDashboardRoutes.js b/tksupport/api/routes/vacationDashboardRoutes.js new file mode 100644 index 0000000..6dfc382 --- /dev/null +++ b/tksupport/api/routes/vacationDashboardRoutes.js @@ -0,0 +1,10 @@ +const express = require('express'); +const router = express.Router(); +const { requireAuth, requireSupportTeam } = require('../middleware/auth'); +const ctrl = require('../controllers/vacationDashboardController'); + +router.use(requireAuth); + +router.get('/', requireSupportTeam, ctrl.getDashboard); + +module.exports = router; diff --git a/tksupport/api/routes/vacationRoutes.js b/tksupport/api/routes/vacationRoutes.js index 46488ff..194136c 100644 --- a/tksupport/api/routes/vacationRoutes.js +++ b/tksupport/api/routes/vacationRoutes.js @@ -20,6 +20,9 @@ router.get('/pending', requireAdmin, ctrl.getPending); router.patch('/requests/:id/approve', requireAdmin, ctrl.approveRequest); router.patch('/requests/:id/reject', requireAdmin, ctrl.rejectRequest); +// 내 휴가 현황 +router.get('/my-status', ctrl.getMyStatus); + // 잔여일 router.get('/balance', ctrl.getMyBalance); router.get('/balance/all', requireAdmin, ctrl.getAllBalances); diff --git a/tksupport/web/company-holidays.html b/tksupport/web/company-holidays.html new file mode 100644 index 0000000..6d704b9 --- /dev/null +++ b/tksupport/web/company-holidays.html @@ -0,0 +1,229 @@ + + + + + + 전사 휴가 관리 - TK 행정지원 + + + + + +
+
+
+
+ + +

TK 행정지원

+
+
+ +
-
+ +
+
+
+
+ +
+
+ + +
+
+
+

전사 휴가 관리

+
+ + +
+
+ +
+ + + + + + + + + + + + + + +
날짜휴가명유형설명상태관리
로딩 중...
+
+
+
+
+
+ + + + + + + + diff --git a/tksupport/web/index.html b/tksupport/web/index.html index 3cef246..b7130c8 100644 --- a/tksupport/web/index.html +++ b/tksupport/web/index.html @@ -6,7 +6,7 @@ 대시보드 - TK 행정지원 - + @@ -123,7 +123,7 @@ - + - +
@@ -54,6 +54,7 @@ 신청자 + 부서 유형 기간 일수 @@ -63,7 +64,7 @@ - 로딩 중... + 로딩 중... @@ -83,6 +84,12 @@ +
+ + +
@@ -92,6 +99,7 @@ 신청자 + 부서 유형 기간 일수 @@ -101,7 +109,7 @@ - 조회 버튼을 클릭하세요 + 조회 버튼을 클릭하세요 @@ -195,7 +203,7 @@ - + + + + + +
+
+
+
+ + +

TK 행정지원

+
+
+ +
-
+ +
+
+
+
+ +
+
+ + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+ + +
+
+ + +
+
+
-
+
전체 직원
+
+
+
-
+
평균 잔여일
+
+
+
-
+
소진 임박
+
+
+ + +
+

부서별 현황

+
+
로딩 중...
+
+
+ + +
+

직원별 상세

+
+ + + + + + + + + + + + + + +
이름부서기본연차이월장기근속총 잔여
로딩 중...
+
+
+
+
+
+ + + + + diff --git a/tksupport/web/vacation-status.html b/tksupport/web/vacation-status.html index faf497e..00487a1 100644 --- a/tksupport/web/vacation-status.html +++ b/tksupport/web/vacation-status.html @@ -6,7 +6,7 @@ 내 휴가 현황 - TK 행정지원 - +
@@ -33,19 +33,46 @@
- + +
+

내 휴가 현황

+ +
+ +
-

잔여일 현황

-
+

잔여일 현황

+
로딩 중...
+
- +
+

연장근로

+
+
--시간
+
(추후 업데이트 예정)
+
+
+ + +
+

전사 휴가일

+
+
로딩 중...
+
+
+ + +
-
- +

휴가 사용 이력

+