From 7aaac1e3348284251756ed45caffda15fbedfe44 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Mon, 30 Mar 2026 13:12:56 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Sprint=20002=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20+=20Sprint=20003=20=EB=8C=80=EC=8B=9C?= =?UTF-8?q?=EB=B3=B4=EB=93=9C=20API/UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sprint 002: - proxyInput created_by_name 누락 수정 - tbm-mobile 하단 네비에 현황 탭 추가 - proxy-input 저장 버튼 스피너 추가 Sprint 003: - GET /api/dashboard/my-summary API (연차/연장근로/페이지 통합) - 생산팀 대시보드 UI (프로필카드 + 아이콘 그리드) - dashboard-new.html 교체 (기존 .bak 백업) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../api/controllers/dashboardController.js | 89 +++++++++ .../api/controllers/proxyInputController.js | 3 +- system1-factory/api/models/dashboardModel.js | 150 +++++++++++++++ system1-factory/api/models/proxyInputModel.js | 6 +- system1-factory/api/routes.js | 1 + system1-factory/api/routes/dashboardRoutes.js | 13 ++ .../web/css/production-dashboard.css | 72 +++++++ .../web/js/production-dashboard.js | 177 ++++++++++++++++++ system1-factory/web/pages/dashboard-new.html | 151 +++------------ .../web/pages/work/proxy-input.html | 2 +- .../web/pages/work/tbm-mobile.html | 8 + 11 files changed, 539 insertions(+), 133 deletions(-) create mode 100644 system1-factory/api/controllers/dashboardController.js create mode 100644 system1-factory/api/models/dashboardModel.js create mode 100644 system1-factory/api/routes/dashboardRoutes.js create mode 100644 system1-factory/web/css/production-dashboard.css create mode 100644 system1-factory/web/js/production-dashboard.js diff --git a/system1-factory/api/controllers/dashboardController.js b/system1-factory/api/controllers/dashboardController.js new file mode 100644 index 0000000..67c7624 --- /dev/null +++ b/system1-factory/api/controllers/dashboardController.js @@ -0,0 +1,89 @@ +/** + * 대시보드 컨트롤러 + * Sprint 003 — 개인 요약 API + */ +const DashboardModel = require('../models/dashboardModel'); +const logger = require('../../shared/utils/logger'); + +const DashboardController = { + /** + * GET /api/dashboard/my-summary + * 연차 잔여 + 월간 연장근로 + 접근 가능 페이지 + */ + getMySummary: async (req, res) => { + try { + const userId = req.user.user_id || req.user.id; + const now = new Date(); + const year = now.getFullYear(); + const month = now.getMonth() + 1; + + // 1단계: 사용자 정보 먼저 조회 (worker_id 필요) + const userInfo = await DashboardModel.getUserInfo(userId); + if (!userInfo) { + return res.status(404).json({ success: false, message: '사용자 정보를 찾을 수 없습니다.' }); + } + + const workerId = userInfo.worker_id; + const departmentId = userInfo.department_id; + const role = userInfo.role; + + // 2단계: 나머지 3개 병렬 조회 + const [vacationRows, overtime, quickAccess] = await Promise.all([ + DashboardModel.getVacationBalance(workerId, year), + DashboardModel.getMonthlyOvertime(userId, year, month), + DashboardModel.getQuickAccess(userId, departmentId, role) + ]); + + // 연차 응답 가공 + const details = vacationRows.map(v => ({ + type_name: v.type_name, + type_code: v.type_code, + total: parseFloat(v.total_days) || 0, + used: parseFloat(v.used_days) || 0, + remaining: parseFloat(v.remaining_days) || 0 + })); + + const annualRow = vacationRows.find(v => v.type_code === 'ANNUAL'); + const totalDays = annualRow ? parseFloat(annualRow.total_days) || 0 : 0; + const usedDays = annualRow ? parseFloat(annualRow.used_days) || 0 : 0; + const remainingDays = annualRow ? parseFloat(annualRow.remaining_days) || 0 : 0; + + res.json({ + success: true, + data: { + user: { + user_id: userInfo.user_id, + name: userInfo.name, + worker_name: userInfo.worker_name || userInfo.name, + job_type: userInfo.job_type || '', + department_name: userInfo.department_name, + department_id: userInfo.department_id, + role: userInfo.role + }, + vacation: { + year, + total_days: totalDays, + used_days: usedDays, + remaining_days: remainingDays, + details + }, + overtime: { + year, + month, + total_overtime_hours: parseFloat(overtime.total_overtime_hours) || 0, + overtime_days: parseInt(overtime.overtime_days) || 0, + total_work_days: parseInt(overtime.total_work_days) || 0, + total_work_hours: parseFloat(overtime.total_work_hours) || 0, + avg_daily_hours: parseFloat(parseFloat(overtime.avg_daily_hours || 0).toFixed(1)) + }, + quick_access: quickAccess + } + }); + } catch (err) { + logger.error('대시보드 요약 조회 오류:', err); + res.status(500).json({ success: false, message: '대시보드 데이터 조회 중 오류가 발생했습니다.', error: err.message }); + } + } +}; + +module.exports = DashboardController; diff --git a/system1-factory/api/controllers/proxyInputController.js b/system1-factory/api/controllers/proxyInputController.js index 2ee1ff8..f5c2107 100644 --- a/system1-factory/api/controllers/proxyInputController.js +++ b/system1-factory/api/controllers/proxyInputController.js @@ -116,7 +116,8 @@ const ProxyInputController = { note: entry.note || '', tbm_session_id: sessionId, tbm_assignment_id: assignmentId, - created_by: userId + created_by: userId, + created_by_name: req.user.name || req.user.username || '' }); createdWorkers.push({ diff --git a/system1-factory/api/models/dashboardModel.js b/system1-factory/api/models/dashboardModel.js new file mode 100644 index 0000000..b0f8101 --- /dev/null +++ b/system1-factory/api/models/dashboardModel.js @@ -0,0 +1,150 @@ +/** + * 대시보드 개인 요약 모델 + * Sprint 003 — 연차/연장근로/접근 페이지 통합 조회 + */ +const { getDb } = require('../config/database'); + +const OVERTIME_THRESHOLD = 8; // 연장근로 기준 시간 + +const DashboardModel = { + /** + * 사용자 정보 조회 (쿼리 1 — 먼저 실행) + */ + getUserInfo: async (userId) => { + const db = await getDb(); + const [rows] = await db.execute(` + SELECT u.user_id, u.name, u.role, + w.worker_id, w.worker_name, w.job_type, w.department_id, + COALESCE(d.department_name, '미배정') AS department_name + FROM sso_users u + LEFT JOIN workers w ON u.user_id = w.user_id + LEFT JOIN departments d ON w.department_id = d.department_id + WHERE u.user_id = ? + `, [userId]); + return rows[0] || null; + }, + + /** + * 연차 현황 조회 (쿼리 2) + */ + getVacationBalance: async (workerId, year) => { + if (!workerId) return []; + const db = await getDb(); + const [rows] = await db.execute(` + SELECT vbd.vacation_type_id, vbd.total_days, vbd.used_days, vbd.remaining_days, + vt.type_name, vt.type_code + FROM vacation_balance_details vbd + JOIN vacation_types vt ON vbd.vacation_type_id = vt.id + WHERE vbd.worker_id = ? AND vbd.year = ? + ORDER BY vt.priority + `, [workerId, year]); + return rows; + }, + + /** + * 월간 연장근로 조회 (쿼리 3) + */ + getMonthlyOvertime: async (userId, year, month) => { + const db = await getDb(); + const [rows] = await db.execute(` + SELECT + COUNT(CASE WHEN dar.total_work_hours > ${OVERTIME_THRESHOLD} THEN 1 END) AS overtime_days, + COALESCE(SUM(CASE WHEN dar.total_work_hours > ${OVERTIME_THRESHOLD} THEN dar.total_work_hours - ${OVERTIME_THRESHOLD} ELSE 0 END), 0) AS total_overtime_hours, + COUNT(*) AS total_work_days, + COALESCE(SUM(dar.total_work_hours), 0) AS total_work_hours, + COALESCE(AVG(dar.total_work_hours), 0) AS avg_daily_hours + FROM daily_attendance_records dar + WHERE dar.user_id = ? AND YEAR(dar.record_date) = ? AND MONTH(dar.record_date) = ? + AND dar.total_work_hours > 0 + `, [userId, year, month]); + return rows[0] || { overtime_days: 0, total_overtime_hours: 0, total_work_days: 0, total_work_hours: 0, avg_daily_hours: 0 }; + }, + + /** + * 접근 가능 페이지 조회 (쿼리 4) + */ + getQuickAccess: async (userId, departmentId, role) => { + const db = await getDb(); + const isAdmin = ['admin', 'system'].includes((role || '').toLowerCase()); + + // 모든 페이지 조회 + const [allPages] = await db.execute(` + SELECT id, page_key, page_name, page_path, category, is_admin_only, + COALESCE(icon, '') AS icon + FROM pages + ORDER BY display_order, page_name + `); + + if (isAdmin) { + const adminPages = allPages.filter(p => p.is_admin_only); + const normalPages = allPages.filter(p => !p.is_admin_only); + return { + department_pages: normalPages.map(formatPage), + personal_pages: [], + admin_pages: adminPages.map(formatPage) + }; + } + + // 부서 권한 페이지 + let deptPageIds = new Set(); + if (departmentId) { + const [deptRows] = await db.execute(` + SELECT dpp.page_id + FROM department_page_permissions dpp + WHERE dpp.department_id = ? AND dpp.can_access = 1 + `, [departmentId]); + deptRows.forEach(r => deptPageIds.add(r.page_id)); + } + + // 개인 권한 페이지 + const [personalRows] = await db.execute(` + SELECT upa.page_id + FROM user_page_access upa + WHERE upa.user_id = ? AND upa.can_access = 1 + `, [userId]); + const personalPageIds = new Set(personalRows.map(r => r.page_id)); + + // 기본 접근 페이지 + const defaultPages = allPages.filter(p => !p.is_admin_only); + + // 분류 + const departmentPages = []; + const personalPages = []; + + for (const page of allPages) { + if (page.is_admin_only) continue; + + if (deptPageIds.has(page.id)) { + departmentPages.push(formatPage(page)); + } else if (personalPageIds.has(page.id)) { + personalPages.push(formatPage(page)); + } + } + + // 기본 접근 페이지 중 부서/개인에 없는 것 추가 + const addedIds = new Set([...departmentPages.map(p => p.page_key), ...personalPages.map(p => p.page_key)]); + for (const page of defaultPages) { + if (!addedIds.has(page.page_key) && !page.is_admin_only) { + departmentPages.push(formatPage(page)); + } + } + + return { + department_pages: departmentPages, + personal_pages: personalPages, + admin_pages: [] + }; + } +}; + +function formatPage(page) { + return { + page_key: page.page_key, + page_name: page.page_name, + page_path: page.page_path, + icon: page.icon || '', + category: page.category || '' + }; +} + +module.exports = DashboardModel; diff --git a/system1-factory/api/models/proxyInputModel.js b/system1-factory/api/models/proxyInputModel.js index 9e8b507..2580d9d 100644 --- a/system1-factory/api/models/proxyInputModel.js +++ b/system1-factory/api/models/proxyInputModel.js @@ -59,9 +59,9 @@ const ProxyInputModel = { */ createWorkReport: async (conn, data) => { const [result] = await conn.query(` - INSERT INTO daily_work_reports (report_date, user_id, project_id, work_type_id, task_id, work_status_id, work_hours, start_time, end_time, note, tbm_session_id, tbm_assignment_id, created_by, created_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()) - `, [data.report_date, data.user_id, data.project_id, data.work_type_id, data.task_id || null, data.work_status_id || 1, data.work_hours, data.start_time || null, data.end_time || null, data.note || '', data.tbm_session_id, data.tbm_assignment_id, data.created_by]); + INSERT INTO daily_work_reports (report_date, user_id, project_id, work_type_id, task_id, work_status_id, work_hours, start_time, end_time, note, tbm_session_id, tbm_assignment_id, created_by, created_by_name, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()) + `, [data.report_date, data.user_id, data.project_id, data.work_type_id, data.task_id || null, data.work_status_id || 1, data.work_hours, data.start_time || null, data.end_time || null, data.note || '', data.tbm_session_id, data.tbm_assignment_id, data.created_by, data.created_by_name || '']); return result; }, diff --git a/system1-factory/api/routes.js b/system1-factory/api/routes.js index b766976..6dd9ac2 100644 --- a/system1-factory/api/routes.js +++ b/system1-factory/api/routes.js @@ -154,6 +154,7 @@ function setupRoutes(app) { app.use('/api/vacation-balances', vacationBalanceRoutes); // 휴가 잔액 관리 app.use('/api/tbm', tbmRoutes); // TBM 시스템 app.use('/api/proxy-input', require('./routes/proxyInputRoutes')); // 대리입력 + 일별현황 + app.use('/api/dashboard', require('./routes/dashboardRoutes')); // 대시보드 개인 요약 app.use('/api/work-issues', workIssueRoutes); // 카테고리/아이템 + 신고 조회 (같은 MariaDB 공유) app.use('/api/departments', departmentRoutes); // 부서 관리 app.use('/api/patrol', patrolRoutes); // 일일순회점검 시스템 diff --git a/system1-factory/api/routes/dashboardRoutes.js b/system1-factory/api/routes/dashboardRoutes.js new file mode 100644 index 0000000..55c77ab --- /dev/null +++ b/system1-factory/api/routes/dashboardRoutes.js @@ -0,0 +1,13 @@ +/** + * 대시보드 라우터 + * Sprint 003 — 개인 요약 API + */ +const express = require('express'); +const router = express.Router(); +const dashboardController = require('../controllers/dashboardController'); +const { verifyToken } = require('../middlewares/auth'); + +// 모든 인증된 사용자 접근 가능 +router.get('/my-summary', verifyToken, dashboardController.getMySummary); + +module.exports = router; diff --git a/system1-factory/web/css/production-dashboard.css b/system1-factory/web/css/production-dashboard.css new file mode 100644 index 0000000..b9a8878 --- /dev/null +++ b/system1-factory/web/css/production-dashboard.css @@ -0,0 +1,72 @@ +/* 생산팀 대시보드 — Sprint 003 */ + +.pd-main { max-width: 640px; margin: 0 auto; padding: 16px 16px 80px; } + +/* 프로필 카드 */ +.pd-profile-card { + background: linear-gradient(135deg, #1e3a5f, #2563eb); + color: white; border-radius: 16px; padding: 20px; margin-bottom: 16px; +} +.pd-profile-header { display: flex; align-items: center; gap: 14px; margin-bottom: 16px; } +.pd-avatar { + width: 48px; height: 48px; border-radius: 50%; + background: rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; + font-size: 20px; font-weight: 700; flex-shrink: 0; +} +.pd-profile-name { font-size: 18px; font-weight: 700; } +.pd-profile-sub { font-size: 13px; opacity: 0.8; margin-top: 2px; } + +/* 현황 카드 */ +.pd-stats-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } +.pd-stat-card { + background: rgba(255,255,255,0.15); border-radius: 12px; padding: 14px; + backdrop-filter: blur(4px); +} +.pd-stat-label { font-size: 11px; opacity: 0.8; margin-bottom: 4px; display: flex; align-items: center; gap: 4px; } +.pd-stat-value { font-size: 22px; font-weight: 800; } +.pd-stat-sub { font-size: 11px; opacity: 0.7; margin-top: 2px; } +.pd-progress-bar { height: 5px; border-radius: 3px; background: rgba(255,255,255,0.2); margin-top: 8px; overflow: hidden; } +.pd-progress-fill { height: 100%; border-radius: 3px; transition: width 0.6s ease; } +.pd-progress-green { background: #4ade80; } +.pd-progress-yellow { background: #fbbf24; } +.pd-progress-red { background: #f87171; } + +/* 섹션 */ +.pd-section { margin-bottom: 20px; } +.pd-section-title { + font-size: 12px; font-weight: 700; color: #6b7280; + text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 12px; + padding-left: 2px; +} + +/* 아이콘 그리드 */ +.pd-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; } +.pd-grid-item { + display: flex; flex-direction: column; align-items: center; gap: 6px; + cursor: pointer; text-decoration: none; -webkit-tap-highlight-color: transparent; +} +.pd-grid-item:active .pd-grid-icon { transform: scale(0.93); } +.pd-grid-icon { + width: 52px; height: 52px; border-radius: 14px; + display: flex; align-items: center; justify-content: center; + color: white; font-size: 20px; transition: transform 0.15s; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); +} +.pd-grid-label { + font-size: 11px; text-align: center; color: #374151; line-height: 1.3; + max-width: 64px; overflow: hidden; display: -webkit-box; + -webkit-line-clamp: 2; -webkit-box-orient: vertical; +} + +/* 스켈레톤 */ +.pd-skeleton { background: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%); background-size: 200% 100%; animation: pd-shimmer 1.5s infinite; border-radius: 8px; } +@keyframes pd-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } + +/* 에러 */ +.pd-error { text-align: center; padding: 40px 20px; color: #6b7280; } +.pd-error i { font-size: 40px; margin-bottom: 12px; color: #d1d5db; } +.pd-error-btn { margin-top: 12px; padding: 8px 20px; background: #2563eb; color: white; border: none; border-radius: 8px; font-size: 14px; cursor: pointer; } + +/* 반응형 */ +@media (min-width: 640px) { .pd-grid { grid-template-columns: repeat(6, 1fr); } } +@media (min-width: 1024px) { .pd-main { max-width: 800px; } .pd-grid { grid-template-columns: repeat(8, 1fr); } } diff --git a/system1-factory/web/js/production-dashboard.js b/system1-factory/web/js/production-dashboard.js new file mode 100644 index 0000000..c59ad5b --- /dev/null +++ b/system1-factory/web/js/production-dashboard.js @@ -0,0 +1,177 @@ +/** + * 생산팀 대시보드 — Sprint 003 + */ + +const PAGE_ICONS = { + 'dashboard': 'fa-home', + 'work.tbm': 'fa-clipboard-list', + 'work.report_create': 'fa-file-alt', + 'work.analysis': 'fa-chart-bar', + 'work.nonconformity': 'fa-exclamation-triangle', + 'work.schedule': 'fa-calendar-alt', + 'work.meetings': 'fa-users', + 'work.daily_status': 'fa-chart-bar', + 'work.proxy_input': 'fa-user-edit', + 'factory.repair_management': 'fa-tools', + 'inspection.daily_patrol': 'fa-route', + 'inspection.checkin': 'fa-user-check', + 'inspection.work_status': 'fa-briefcase', + 'purchase.request': 'fa-shopping-cart', + 'purchase.analysis': 'fa-chart-line', + 'attendance.my_vacation_info': 'fa-info-circle', + 'attendance.monthly': 'fa-calendar', + 'attendance.vacation_request': 'fa-paper-plane', + 'attendance.vacation_management': 'fa-cog', + 'attendance.vacation_allocation': 'fa-plus-circle', + 'attendance.annual_overview': 'fa-chart-pie', + 'admin.user_management': 'fa-users-cog', + 'admin.projects': 'fa-project-diagram', + 'admin.tasks': 'fa-tasks', + 'admin.workplaces': 'fa-building', + 'admin.equipments': 'fa-cogs', + 'admin.departments': 'fa-sitemap', + 'admin.notifications': 'fa-bell', + 'admin.attendance_report': 'fa-clipboard-check', +}; + +const CATEGORY_COLORS = { + '작업 관리': '#3b82f6', + '공장 관리': '#f59e0b', + '소모품 관리': '#10b981', + '근태 관리': '#8b5cf6', + '시스템 관리': '#6b7280', +}; +const DEFAULT_COLOR = '#06b6d4'; + +function escHtml(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; } + +async function initDashboard() { + showSkeleton(); + try { + const token = localStorage.getItem('sso_token') || getCookie('sso_token'); + const resp = await fetch('/api/dashboard/my-summary', { + headers: { 'Authorization': 'Bearer ' + token } + }); + if (!resp.ok) throw new Error('API 오류: ' + resp.status); + const result = await resp.json(); + if (!result.success) throw new Error(result.message || '데이터 로드 실패'); + renderDashboard(result.data); + } catch (err) { + showError(err.message); + } +} + +function getCookie(name) { + const v = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)'); + return v ? v.pop() : ''; +} + +function renderDashboard(data) { + const { user, vacation, overtime, quick_access } = data; + + // 프로필 카드 + const card = document.getElementById('profileCard'); + const initial = (user.worker_name || user.name || '?').charAt(0); + const vacRemaining = vacation.remaining_days; + const vacTotal = vacation.total_days; + const vacUsed = vacation.used_days; + const vacPct = vacTotal > 0 ? Math.round((vacUsed / vacTotal) * 100) : 0; + const vacColor = vacRemaining >= 5 ? 'green' : vacRemaining >= 3 ? 'yellow' : 'red'; + + const otHours = overtime.total_overtime_hours; + const otDays = overtime.overtime_days; + + card.innerHTML = ` +
+
${escHtml(initial)}
+
+
${escHtml(user.worker_name || user.name)}
+
${escHtml(user.job_type || '')}${user.job_type ? ' · ' : ''}${escHtml(user.department_name)}
+
+
+
+
+
연차
+ ${vacTotal > 0 ? ` +
잔여 ${vacRemaining}일
+
${vacTotal}일 중 ${vacUsed}일 사용
+
+ ` : `
연차 정보 미등록
`} +
+
+
연장근로
+
${otHours.toFixed(1)}h
+
이번달 ${otDays}일
+
+
+ `; + + // 아이콘 그리드 + renderGrid('deptPagesGrid', 'deptPagesSection', quick_access.department_pages); + renderGrid('personalPagesGrid', 'personalPagesSection', quick_access.personal_pages); + renderGrid('adminPagesGrid', 'adminPagesSection', quick_access.admin_pages); +} + +function renderGrid(gridId, sectionId, pages) { + const grid = document.getElementById(gridId); + const section = document.getElementById(sectionId); + if (!pages || pages.length === 0) { + section.classList.add('hidden'); + return; + } + section.classList.remove('hidden'); + + // dashboard 자체 제외 + const filtered = pages.filter(p => p.page_key !== 'dashboard'); + if (filtered.length === 0) { section.classList.add('hidden'); return; } + + grid.innerHTML = filtered.map(p => { + const icon = PAGE_ICONS[p.page_key] || p.icon || 'fa-circle'; + const color = CATEGORY_COLORS[p.category] || DEFAULT_COLOR; + return ` +
+ +
+ ${escHtml(p.page_name)} +
`; + }).join(''); +} + +function showSkeleton() { + const card = document.getElementById('profileCard'); + card.innerHTML = ` +
+
+
+
+
+
+
+
+
+
+
+ `; + // 그리드 스켈레톤 + ['deptPagesGrid'].forEach(id => { + const g = document.getElementById(id); + if (g) g.innerHTML = Array(8).fill('
').join(''); + }); +} + +function showError(msg) { + document.getElementById('profileCard').innerHTML = ` +
+ +

${escHtml(msg || '정보를 불러올 수 없습니다.')}

+ +
+ `; +} + +// tkfb-core.js 인증 완료 후 실행 +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => setTimeout(initDashboard, 300)); +} else { + setTimeout(initDashboard, 300); +} diff --git a/system1-factory/web/pages/dashboard-new.html b/system1-factory/web/pages/dashboard-new.html index aaf4331..d8952b9 100644 --- a/system1-factory/web/pages/dashboard-new.html +++ b/system1-factory/web/pages/dashboard-new.html @@ -2,143 +2,38 @@ - + 대시보드 - TK 공장관리 - + + -
-
-
-
- - -

TK 공장관리

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

내 메뉴

+
+
-
- -
-
-

대시보드

-

-

-
- -
+ - -
-
-
-
-
금일 TBM
-
-
-
-
-
출근 인원
-
-
-
-
-
수리 요청
-
-
-
-
알림 관리
-
-
+ +
-
- -
-

- 금일 TBM -

-
-

로딩 중...

-
-
- - -
-

- 최근 알림 -

-
-

로딩 중...

-
-
- - -
-

- 수리 요청 현황 -

-
-

로딩 중...

-
-
- - - -
- - - - - - + + diff --git a/system1-factory/web/pages/work/proxy-input.html b/system1-factory/web/pages/work/proxy-input.html index 761a9ff..9c2f3ee 100644 --- a/system1-factory/web/pages/work/proxy-input.html +++ b/system1-factory/web/pages/work/proxy-input.html @@ -87,7 +87,7 @@
diff --git a/system1-factory/web/pages/work/tbm-mobile.html b/system1-factory/web/pages/work/tbm-mobile.html index 2d18084..33a3f8c 100644 --- a/system1-factory/web/pages/work/tbm-mobile.html +++ b/system1-factory/web/pages/work/tbm-mobile.html @@ -95,6 +95,14 @@ 작업보고 + + + + + + + 현황 +