- 페이지 폴더 재구성: safety/, attendance/ 폴더 신규 생성 - work/ → safety/: 이슈 신고, 출입 신청 관련 페이지 이동 - common/ → attendance/: 근태/휴가 관련 페이지 이동 - admin/ 정리: safety-* 파일들을 safety/로 이동 - 사이드바 네비게이션 메뉴 구현 - 카테고리별 메뉴: 작업관리, 안전관리, 근태관리, 시스템관리 - 접기/펼치기 기능 및 상태 저장 - 관리자 전용 메뉴 자동 표시/숨김 - 날씨 API 연동 (기상청 단기예보) - TBM 및 navbar에 현재 날씨 표시 - weatherService.js 추가 - 안전 체크리스트 확장 - 기본/날씨별/작업별 체크 유형 추가 - checklist-manage.html 페이지 추가 - 이슈 신고 시스템 구현 - workIssueController, workIssueModel, workIssueRoutes 추가 - DB 마이그레이션 파일 추가 (실행 대기) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
723 lines
16 KiB
HTML
723 lines
16 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>작업보고서 검토 | (주)테크니컬코리아</title>
|
|
<link rel="stylesheet" href="/css/main-layout.css">
|
|
<link rel="stylesheet" href="/css/admin.css">
|
|
<link rel="stylesheet" href="/css/work-report.css">
|
|
<link rel="icon" type="image/png" href="/img/favicon.png">
|
|
<script src="/js/auth-check.js" defer></script>
|
|
<style>
|
|
/* 검토 페이지 전용 스타일 */
|
|
.review-container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
display: grid;
|
|
grid-template-columns: 1fr 350px;
|
|
gap: 24px;
|
|
min-height: calc(100vh - 200px);
|
|
}
|
|
|
|
.main-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 24px;
|
|
}
|
|
|
|
/* 상단 대시보드 */
|
|
.dashboard-section {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 16px;
|
|
}
|
|
|
|
.dashboard-card {
|
|
background: white;
|
|
padding: 24px;
|
|
border-radius: 12px;
|
|
border: 1px solid #e1e5e9;
|
|
text-align: center;
|
|
transition: transform 0.2s ease;
|
|
}
|
|
|
|
.dashboard-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.dashboard-number {
|
|
font-size: 32px;
|
|
font-weight: 700;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.dashboard-label {
|
|
color: #666;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.dashboard-card.total .dashboard-number { color: #007bff; }
|
|
.dashboard-card.error .dashboard-number { color: #dc3545; }
|
|
.dashboard-card.warning .dashboard-number { color: #ffc107; }
|
|
.dashboard-card.missing .dashboard-number { color: #6c757d; }
|
|
|
|
/* 필터 섹션 */
|
|
.filter-section {
|
|
background: white;
|
|
padding: 24px;
|
|
border-radius: 12px;
|
|
border: 1px solid #e1e5e9;
|
|
}
|
|
|
|
.filter-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 16px;
|
|
align-items: end;
|
|
}
|
|
|
|
.filter-group label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
font-weight: 600;
|
|
color: #555;
|
|
}
|
|
|
|
.filter-input {
|
|
width: 100%;
|
|
padding: 12px;
|
|
border: 2px solid #e1e5e9;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.filter-input:focus {
|
|
outline: none;
|
|
border-color: #007bff;
|
|
}
|
|
|
|
.filter-btn {
|
|
padding: 12px 24px;
|
|
background: #007bff;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.filter-btn:hover {
|
|
background: #0056b3;
|
|
}
|
|
|
|
/* 알림 영역 */
|
|
.alerts-section {
|
|
background: white;
|
|
border-radius: 12px;
|
|
border: 1px solid #e1e5e9;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.alerts-header {
|
|
background: #f8f9fa;
|
|
padding: 16px 24px;
|
|
border-bottom: 1px solid #e1e5e9;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
.alert-item {
|
|
padding: 16px 24px;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.alert-item:hover {
|
|
background: #f8f9fa;
|
|
}
|
|
|
|
.alert-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.alert-type {
|
|
display: inline-block;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
margin-right: 12px;
|
|
}
|
|
|
|
.alert-type.error {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
}
|
|
|
|
.alert-type.warning {
|
|
background: #fff3cd;
|
|
color: #856404;
|
|
}
|
|
|
|
.alert-type.missing {
|
|
background: #d1ecf1;
|
|
color: #0c5460;
|
|
}
|
|
|
|
.alert-type.pending {
|
|
background: #e2e3e5;
|
|
color: #383d41;
|
|
}
|
|
|
|
.alert-content {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.alert-text {
|
|
flex: 1;
|
|
}
|
|
|
|
.alert-time {
|
|
color: #666;
|
|
font-size: 12px;
|
|
}
|
|
|
|
/* 메인 테이블 */
|
|
.table-section {
|
|
background: white;
|
|
border-radius: 12px;
|
|
border: 1px solid #e1e5e9;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.table-header {
|
|
background: #f8f9fa;
|
|
padding: 16px 24px;
|
|
border-bottom: 1px solid #e1e5e9;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.table-title {
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
.table-actions {
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
|
|
.action-btn {
|
|
padding: 8px 16px;
|
|
border: 1px solid #dee2e6;
|
|
background: white;
|
|
border-radius: 6px;
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.action-btn:hover {
|
|
border-color: #007bff;
|
|
color: #007bff;
|
|
}
|
|
|
|
.data-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.data-table th {
|
|
background: #f8f9fa;
|
|
padding: 16px;
|
|
text-align: left;
|
|
font-weight: 600;
|
|
color: #555;
|
|
border-bottom: 2px solid #e1e5e9;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.data-table td {
|
|
padding: 16px;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.data-table tr {
|
|
transition: background 0.2s;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.data-table tr:hover {
|
|
background: #f8f9fa;
|
|
}
|
|
|
|
.data-table tr.selected {
|
|
background: #e7f3ff;
|
|
border-left: 4px solid #007bff;
|
|
}
|
|
|
|
/* 상태 표시 */
|
|
.status-badge {
|
|
display: inline-block;
|
|
padding: 4px 8px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.status-badge.normal {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
}
|
|
|
|
.status-badge.error {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
}
|
|
|
|
.row-normal { background: #fff; }
|
|
.row-warning { background: #fffbf0; border-left: 4px solid #ffc107; }
|
|
.row-error { background: #fef5f5; border-left: 4px solid #dc3545; }
|
|
.row-missing { background: #f0f8ff; border-left: 4px solid #6c757d; }
|
|
.row-reviewed { background: #f0f9ff; border-left: 4px solid #28a745; }
|
|
|
|
/* 새로운 배지 스타일 */
|
|
.attendance-badge {
|
|
display: inline-block;
|
|
padding: 4px 8px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.attendance-badge.NORMAL {
|
|
background: #e3f2fd;
|
|
color: #1565c0;
|
|
}
|
|
|
|
.attendance-badge.HALF_DAY {
|
|
background: #fff3e0;
|
|
color: #ef6c00;
|
|
}
|
|
|
|
.attendance-badge.HALF_HALF_DAY {
|
|
background: #f3e5f5;
|
|
color: #7b1fa2;
|
|
}
|
|
|
|
.attendance-badge.EARLY_LEAVE {
|
|
background: #ffebee;
|
|
color: #c62828;
|
|
}
|
|
|
|
.hours-status-badge {
|
|
display: inline-block;
|
|
padding: 4px 8px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.hours-status-badge.NORMAL {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
}
|
|
|
|
.hours-status-badge.UNDER {
|
|
background: #fff3cd;
|
|
color: #856404;
|
|
}
|
|
|
|
.hours-status-badge.OVER {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
}
|
|
|
|
.review-badge {
|
|
display: inline-block;
|
|
padding: 4px 8px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.review-badge.reviewed {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
}
|
|
|
|
.review-badge.pending {
|
|
background: #ffeaa7;
|
|
color: #856404;
|
|
}
|
|
|
|
.review-complete-btn {
|
|
background: #28a745;
|
|
color: white;
|
|
border: none;
|
|
padding: 6px 12px;
|
|
border-radius: 6px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.review-complete-btn:hover {
|
|
background: #1e7e34;
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
/* 우측 수정 패널 */
|
|
.edit-panel {
|
|
background: white;
|
|
border-radius: 12px;
|
|
border: 1px solid #e1e5e9;
|
|
position: sticky;
|
|
top: 24px;
|
|
height: fit-content;
|
|
max-height: calc(100vh - 48px);
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.panel-header {
|
|
background: #f8f9fa;
|
|
padding: 20px;
|
|
border-bottom: 1px solid #e1e5e9;
|
|
}
|
|
|
|
.panel-title {
|
|
font-weight: 600;
|
|
color: #333;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.panel-subtitle {
|
|
color: #666;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.panel-content {
|
|
padding: 24px;
|
|
}
|
|
|
|
.panel-empty {
|
|
text-align: center;
|
|
color: #999;
|
|
padding: 60px 20px;
|
|
}
|
|
|
|
.panel-empty-icon {
|
|
font-size: 48px;
|
|
margin-bottom: 16px;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
font-weight: 600;
|
|
color: #555;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.form-input {
|
|
width: 100%;
|
|
padding: 12px;
|
|
border: 2px solid #e1e5e9;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.form-input:focus {
|
|
outline: none;
|
|
border-color: #007bff;
|
|
}
|
|
|
|
.panel-actions {
|
|
padding: 20px;
|
|
border-top: 1px solid #e1e5e9;
|
|
background: #f8f9fa;
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
|
|
.panel-btn {
|
|
flex: 1;
|
|
padding: 12px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.panel-btn.save {
|
|
background: #28a745;
|
|
color: white;
|
|
}
|
|
|
|
.panel-btn.save:hover {
|
|
background: #1e7e34;
|
|
}
|
|
|
|
.panel-btn.delete {
|
|
background: #dc3545;
|
|
color: white;
|
|
}
|
|
|
|
.panel-btn.delete:hover {
|
|
background: #c82333;
|
|
}
|
|
|
|
.panel-btn.cancel {
|
|
background: #6c757d;
|
|
color: white;
|
|
}
|
|
|
|
.panel-btn.cancel:hover {
|
|
background: #545b62;
|
|
}
|
|
|
|
/* 로딩 및 메시지 */
|
|
.loading-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0,0,0,0.5);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.loading-spinner {
|
|
background: white;
|
|
padding: 40px;
|
|
border-radius: 12px;
|
|
text-align: center;
|
|
}
|
|
|
|
.message {
|
|
padding: 16px 24px;
|
|
border-radius: 8px;
|
|
margin-bottom: 24px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.message.error {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
border: 1px solid #f5c6cb;
|
|
}
|
|
|
|
.message.success {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
border: 1px solid #c3e6cb;
|
|
}
|
|
|
|
.message.info {
|
|
background: #d1ecf1;
|
|
color: #0c5460;
|
|
border: 1px solid #bee5eb;
|
|
}
|
|
|
|
.message.warning {
|
|
background: #fff3cd;
|
|
color: #856404;
|
|
border: 1px solid #ffeaa7;
|
|
}
|
|
|
|
/* 반응형 */
|
|
@media (max-width: 1200px) {
|
|
.review-container {
|
|
grid-template-columns: 1fr;
|
|
gap: 16px;
|
|
}
|
|
|
|
.edit-panel {
|
|
position: relative;
|
|
top: 0;
|
|
max-height: none;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.dashboard-section {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
|
|
.filter-row {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.table-section {
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.data-table {
|
|
min-width: 800px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="main-layout">
|
|
<div id="navbar-container"></div>
|
|
|
|
<div class="content-wrapper">
|
|
<div id="sidebar-container"></div>
|
|
|
|
<div id="content-container">
|
|
<div class="page-header">
|
|
<h1>🔍 작업보고서 검토</h1>
|
|
<p class="subtitle">전체 현황을 파악하고 이상 사항을 빠르게 처리하세요.</p>
|
|
</div>
|
|
|
|
<!-- 메시지 영역 -->
|
|
<div id="message-container"></div>
|
|
|
|
<div class="review-container">
|
|
<!-- 메인 콘텐츠 -->
|
|
<div class="main-content">
|
|
<!-- 상단 대시보드 -->
|
|
<div class="dashboard-section">
|
|
<div class="dashboard-card total">
|
|
<div class="dashboard-number" id="totalReports">-</div>
|
|
<div class="dashboard-label">총 보고서</div>
|
|
</div>
|
|
<div class="dashboard-card error">
|
|
<div class="dashboard-number" id="errorReports">-</div>
|
|
<div class="dashboard-label">에러 발생</div>
|
|
</div>
|
|
<div class="dashboard-card warning">
|
|
<div class="dashboard-number" id="warningReports">-</div>
|
|
<div class="dashboard-label">주의 필요</div>
|
|
</div>
|
|
<div class="dashboard-card missing">
|
|
<div class="dashboard-number" id="missingReports">-</div>
|
|
<div class="dashboard-label">미검토</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 필터 섹션 -->
|
|
<div class="filter-section">
|
|
<div class="filter-row">
|
|
<div class="filter-group">
|
|
<label>시작 날짜</label>
|
|
<input type="date" id="startDate" class="filter-input">
|
|
</div>
|
|
<div class="filter-group">
|
|
<label>종료 날짜</label>
|
|
<input type="date" id="endDate" class="filter-input">
|
|
</div>
|
|
<div class="filter-group">
|
|
<label>작업자</label>
|
|
<select id="workerFilter" class="filter-input">
|
|
<option value="">전체 작업자</option>
|
|
</select>
|
|
</div>
|
|
<div class="filter-group">
|
|
<label>프로젝트</label>
|
|
<select id="projectFilter" class="filter-input">
|
|
<option value="">전체 프로젝트</option>
|
|
</select>
|
|
</div>
|
|
<div class="filter-group">
|
|
<button type="button" id="applyFilter" class="filter-btn">필터 적용</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 알림 영역 -->
|
|
<div class="alerts-section">
|
|
<div class="alerts-header">
|
|
🚨 주의 필요 항목
|
|
</div>
|
|
<div id="alertsList">
|
|
<!-- 알림 항목들이 여기에 표시됩니다 -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 메인 테이블 -->
|
|
<div class="table-section">
|
|
<div class="table-header">
|
|
<div class="table-title">작업보고서 목록</div>
|
|
<div class="table-actions">
|
|
<button class="action-btn" id="refreshBtn">🔄 새로고침</button>
|
|
<button class="action-btn" id="exportBtn">📊 내보내기</button>
|
|
</div>
|
|
</div>
|
|
<div style="overflow-x: auto;">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>날짜</th>
|
|
<th>작업자</th>
|
|
<th>출근형태</th>
|
|
<th>기대시간</th>
|
|
<th>실제시간</th>
|
|
<th>시간상태</th>
|
|
<th>프로젝트</th>
|
|
<th>작업유형</th>
|
|
<th>상태</th>
|
|
<th>검토상태</th>
|
|
<th>액션</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="reportsTableBody">
|
|
<!-- 데이터가 여기에 표시됩니다 -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 우측 수정 패널 -->
|
|
<div class="edit-panel">
|
|
<div class="panel-header">
|
|
<div class="panel-title">빠른 수정</div>
|
|
<div class="panel-subtitle">항목을 선택하여 수정하세요</div>
|
|
</div>
|
|
<div class="panel-content" id="editPanelContent">
|
|
<div class="panel-empty">
|
|
<div class="panel-empty-icon">📝</div>
|
|
<div>수정할 항목을 선택해주세요</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 로딩 오버레이 -->
|
|
<div id="loadingOverlay" class="loading-overlay" style="display: none;">
|
|
<div class="loading-spinner">
|
|
<div style="font-size: 24px; margin-bottom: 16px;">⏳</div>
|
|
<div>데이터를 처리하는 중...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="module" src="/js/load-navbar.js"></script>
|
|
<script type="module" src="/js/load-sidebar.js"></script>
|
|
<script type="module" src="/js/work-report-review.js"></script>
|
|
</body>
|
|
</html> |