refactor: 페이지 구조 대대적 개편 - 명확한 폴더 구조 및 파일명 개선
## 주요 변경사항
### 1. 미사용 페이지 아카이브 (24개)
- admin 폴더 전체 (8개) → .archived-admin/
- 분석 페이지 (5개) → .archived-*
- 공통 페이지 (5개) → .archived-*
- 대시보드 페이지 (2개) → .archived-*
- 기타 (4개) → .archived-*
### 2. 새로운 폴더 구조
```
pages/
├── dashboard.html (메인 대시보드)
├── work/ (작업 관련)
│ ├── report-create.html (작업보고서 작성)
│ ├── report-view.html (작업보고서 조회)
│ └── analysis.html (작업 분석)
├── admin/ (관리 기능)
│ ├── index.html (관리 메뉴 허브)
│ ├── projects.html (프로젝트 관리)
│ ├── workers.html (작업자 관리)
│ ├── codes.html (코드 관리)
│ └── accounts.html (계정 관리)
└── profile/ (프로필)
├── info.html (내 정보)
└── password.html (비밀번호 변경)
```
### 3. 파일명 개선
- group-leader.html → dashboard.html
- daily-work-report.html → work/report-create.html
- daily-work-report-viewer.html → work/report-view.html
- work-analysis.html → work/analysis.html
- work-management.html → admin/index.html
- project-management.html → admin/projects.html
- worker-management.html → admin/workers.html
- code-management.html → admin/codes.html
- my-profile.html → profile/info.html
- change-password.html → profile/password.html
- admin-settings.html → admin/accounts.html
### 4. 내부 링크 전면 수정
- navbar.html 프로필 메뉴 링크 업데이트
- dashboard.html 빠른 작업 링크 업데이트
- admin/* 페이지 간 링크 업데이트
- load-navbar.js 대시보드 경로 수정
영향받는 파일: 39개
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1
web-ui/pages/admin/.gitkeep
Normal file
1
web-ui/pages/admin/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# Placeholder file to create admin directory
|
||||
173
web-ui/pages/admin/accounts.html
Normal file
173
web-ui/pages/admin/accounts.html
Normal file
@@ -0,0 +1,173 @@
|
||||
<!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/common.css?v=1">
|
||||
<link rel="stylesheet" href="/css/admin-settings.css?v=1">
|
||||
<link rel="icon" type="image/png" href="/img/favicon.png">
|
||||
<script src="/js/auth-check.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="work-report-container">
|
||||
<!-- 네비게이션 바 -->
|
||||
<div id="navbar-container"></div>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main class="work-report-main">
|
||||
<div class="dashboard-main">
|
||||
<div class="page-header">
|
||||
<div class="page-title-section">
|
||||
<h1 class="page-title">
|
||||
<span class="title-icon">⚙️</span>
|
||||
관리자 설정
|
||||
</h1>
|
||||
<p class="page-description">시스템 사용자 계정 및 권한을 관리합니다</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 사용자 관리 섹션 -->
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<span class="section-icon">👥</span>
|
||||
사용자 계정 관리
|
||||
</h2>
|
||||
<button class="btn btn-primary" id="addUserBtn">
|
||||
<span class="btn-icon">➕</span>
|
||||
새 사용자 추가
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="users-container">
|
||||
<div class="users-header">
|
||||
<div class="search-box">
|
||||
<input type="text" id="userSearch" placeholder="사용자 검색..." class="search-input">
|
||||
<span class="search-icon">🔍</span>
|
||||
</div>
|
||||
<div class="filter-buttons">
|
||||
<button class="filter-btn active" data-filter="all">전체</button>
|
||||
<button class="filter-btn" data-filter="admin">관리자</button>
|
||||
<button class="filter-btn" data-filter="leader">그룹장</button>
|
||||
<button class="filter-btn" data-filter="user">작업자</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="users-table-container">
|
||||
<table class="users-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>사용자명</th>
|
||||
<th>아이디</th>
|
||||
<th>역할</th>
|
||||
<th>상태</th>
|
||||
<th>최종 로그인</th>
|
||||
<th>관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="usersTableBody">
|
||||
<!-- 사용자 목록이 여기에 동적으로 생성됩니다 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="empty-state" id="emptyState" style="display: none;">
|
||||
<div class="empty-icon">👥</div>
|
||||
<h3>등록된 사용자가 없습니다</h3>
|
||||
<p>새 사용자를 추가해보세요.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- 사용자 추가/수정 모달 -->
|
||||
<div id="userModal" class="modal-overlay" style="display: none;">
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<h2 id="modalTitle">새 사용자 추가</h2>
|
||||
<button class="modal-close-btn" onclick="closeUserModal()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<form id="userForm">
|
||||
<div class="form-group">
|
||||
<label class="form-label">사용자명 *</label>
|
||||
<input type="text" id="userName" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">아이디 *</label>
|
||||
<input type="text" id="userId" class="form-control" required>
|
||||
<small class="form-help">영문, 숫자만 사용 가능 (4-20자)</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="passwordGroup">
|
||||
<label class="form-label">비밀번호 *</label>
|
||||
<input type="password" id="userPassword" class="form-control" required>
|
||||
<small class="form-help">최소 6자 이상</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">역할 *</label>
|
||||
<select id="userRole" class="form-control" required>
|
||||
<option value="">역할 선택</option>
|
||||
<option value="admin">관리자</option>
|
||||
<option value="leader">그룹장</option>
|
||||
<option value="user">작업자</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">이메일</label>
|
||||
<input type="email" id="userEmail" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">전화번호</label>
|
||||
<input type="tel" id="userPhone" class="form-control">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeUserModal()">취소</button>
|
||||
<button type="button" class="btn btn-primary" id="saveUserBtn">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 사용자 삭제 확인 모달 -->
|
||||
<div id="deleteModal" class="modal-overlay" style="display: none;">
|
||||
<div class="modal-container small">
|
||||
<div class="modal-header">
|
||||
<h2>사용자 삭제</h2>
|
||||
<button class="modal-close-btn" onclick="closeDeleteModal()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="delete-warning">
|
||||
<div class="warning-icon">⚠️</div>
|
||||
<p>정말로 이 사용자를 삭제하시겠습니까?</p>
|
||||
<p class="warning-text">삭제된 사용자는 복구할 수 없습니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeDeleteModal()">취소</button>
|
||||
<button type="button" class="btn btn-danger" id="confirmDeleteBtn">삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 토스트 알림 -->
|
||||
<div class="toast-container" id="toastContainer"></div>
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script type="module" src="/js/api-config.js?v=13"></script>
|
||||
<script type="module" src="/js/load-navbar.js?v=4"></script>
|
||||
<script src="/js/admin-settings.js?v=5"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,44 +0,0 @@
|
||||
<section>
|
||||
<h2>📄 작업 보고서</h2>
|
||||
<ul>
|
||||
<li><a href="/pages/work-reports/create.html">작업보고서 입력</a></li>
|
||||
<li><a href="/pages/work-reports/manage.html">작업보고서 수정/삭제</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>📊 출근/공수 관리</h2>
|
||||
<ul>
|
||||
<li><a href="/pages/common/attendance.html">출근부</a></li>
|
||||
<li><a href="/pages/work-reports/project-labor-summary.html">프로젝트별 공수 계산</a></li>
|
||||
<li><a href="/pages/work-reports/monthly-labor-report.html">월간 공수 보고서</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>🔧 관리콘솔</h2>
|
||||
<ul>
|
||||
<li><a href="/pages/admin/manage-user.html">👤 사용자 관리</a></li>
|
||||
<li><a href="/pages/admin/manage-project.html">📁 프로젝트 관리</a></li>
|
||||
<li><a href="/pages/admin/manage-worker.html">👷 작업자 관리</a></li>
|
||||
<li><a href="/pages/admin/manage-task.html">📋 작업 유형 관리</a></li>
|
||||
<li><a href="/pages/admin/manage-issue.html">🚨 이슈 유형 관리</a></li>
|
||||
<li><a href="/pages/admin/manage-pipespec.html">🔧 배관 스펙 관리</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>🏭 공장 정보</h2>
|
||||
<ul>
|
||||
<li><a href="/pages/common/factory-upload.html">공장 정보 등록</a></li>
|
||||
<li><a href="/pages/common/factory-list.html">공장 목록 보기</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>📊 이슈 리포트</h2>
|
||||
<ul>
|
||||
<li><a href="/pages/issue-reports/daily-issue.html">일일 이슈 보고</a></li>
|
||||
<li><a href="/pages/issue-reports/issue-summary.html">이슈 현황 요약</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
252
web-ui/pages/admin/codes.html
Normal file
252
web-ui/pages/admin/codes.html
Normal file
@@ -0,0 +1,252 @@
|
||||
<!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/design-system.css">
|
||||
<link rel="stylesheet" href="/css/common.css?v=2">
|
||||
<link rel="stylesheet" href="/css/project-management.css?v=4">
|
||||
<link rel="icon" type="image/png" href="/img/favicon.png">
|
||||
<script src="/js/auth-check.js?v=1" defer></script>
|
||||
<script type="module" src="/js/api-config.js?v=3"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="work-report-container">
|
||||
<!-- 네비게이션 바 -->
|
||||
<div id="navbar-container"></div>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main class="work-report-main">
|
||||
<!-- 뒤로가기 버튼 -->
|
||||
<a href="/pages/admin/index.html" class="back-button">
|
||||
← 작업관리로 돌아가기
|
||||
</a>
|
||||
|
||||
<div class="dashboard-main">
|
||||
<div class="page-header">
|
||||
<div class="page-title-section">
|
||||
<h1 class="page-title">
|
||||
<span class="title-icon">🏷️</span>
|
||||
코드 관리
|
||||
</h1>
|
||||
<p class="page-description">작업 상태, 오류 유형, 작업 유형 등 시스템에서 사용하는 코드를 관리합니다</p>
|
||||
</div>
|
||||
|
||||
<div class="page-actions">
|
||||
<button class="btn btn-secondary" onclick="refreshAllCodes()">
|
||||
<span class="btn-icon">🔄</span>
|
||||
전체 새로고침
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 코드 유형 탭 -->
|
||||
<div class="code-tabs">
|
||||
<button class="tab-btn active" data-tab="work-status" onclick="switchCodeTab('work-status')">
|
||||
<span class="tab-icon">📊</span>
|
||||
작업 상태 유형
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="error-types" onclick="switchCodeTab('error-types')">
|
||||
<span class="tab-icon">⚠️</span>
|
||||
오류 유형
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="work-types" onclick="switchCodeTab('work-types')">
|
||||
<span class="tab-icon">🔧</span>
|
||||
작업 유형
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 작업 상태 유형 관리 -->
|
||||
<div id="work-status-tab" class="code-tab-content active">
|
||||
<div class="code-section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<span class="section-icon">📊</span>
|
||||
작업 상태 유형 관리
|
||||
</h2>
|
||||
<div class="section-actions">
|
||||
<button class="btn btn-primary" onclick="openCodeModal('work-status')">
|
||||
<span class="btn-icon">➕</span>
|
||||
새 상태 추가
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-stats">
|
||||
<span class="stat-item">
|
||||
<span class="stat-icon">📊</span>
|
||||
총 <span id="workStatusCount">0</span>개
|
||||
</span>
|
||||
<span class="stat-item">
|
||||
<span class="stat-icon">✅</span>
|
||||
정상 <span id="normalStatusCount">0</span>개
|
||||
</span>
|
||||
<span class="stat-item">
|
||||
<span class="stat-icon">❌</span>
|
||||
오류 <span id="errorStatusCount">0</span>개
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="code-grid" id="workStatusGrid">
|
||||
<!-- 작업 상태 유형 카드들이 여기에 동적으로 생성됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 오류 유형 관리 -->
|
||||
<div id="error-types-tab" class="code-tab-content">
|
||||
<div class="code-section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<span class="section-icon">⚠️</span>
|
||||
오류 유형 관리
|
||||
</h2>
|
||||
<div class="section-actions">
|
||||
<button class="btn btn-primary" onclick="openCodeModal('error-types')">
|
||||
<span class="btn-icon">➕</span>
|
||||
새 오류 유형 추가
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-stats">
|
||||
<span class="stat-item">
|
||||
<span class="stat-icon">⚠️</span>
|
||||
총 <span id="errorTypesCount">0</span>개
|
||||
</span>
|
||||
<span class="stat-item critical-stat">
|
||||
<span class="stat-icon">🔴</span>
|
||||
심각 <span id="criticalErrorsCount">0</span>개
|
||||
</span>
|
||||
<span class="stat-item high-stat">
|
||||
<span class="stat-icon">🟠</span>
|
||||
높음 <span id="highErrorsCount">0</span>개
|
||||
</span>
|
||||
<span class="stat-item medium-stat">
|
||||
<span class="stat-icon">🟡</span>
|
||||
보통 <span id="mediumErrorsCount">0</span>개
|
||||
</span>
|
||||
<span class="stat-item low-stat">
|
||||
<span class="stat-icon">🟢</span>
|
||||
낮음 <span id="lowErrorsCount">0</span>개
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="code-grid" id="errorTypesGrid">
|
||||
<!-- 오류 유형 카드들이 여기에 동적으로 생성됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 작업 유형 관리 -->
|
||||
<div id="work-types-tab" class="code-tab-content">
|
||||
<div class="code-section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<span class="section-icon">🔧</span>
|
||||
작업 유형 관리
|
||||
</h2>
|
||||
<div class="section-actions">
|
||||
<button class="btn btn-primary" onclick="openCodeModal('work-types')">
|
||||
<span class="btn-icon">➕</span>
|
||||
새 작업 유형 추가
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-stats">
|
||||
<span class="stat-item">
|
||||
<span class="stat-icon">🔧</span>
|
||||
총 <span id="workTypesCount">0</span>개
|
||||
</span>
|
||||
<span class="stat-item">
|
||||
<span class="stat-icon">📁</span>
|
||||
카테고리 <span id="workCategoriesCount">0</span>개
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="code-grid" id="workTypesGrid">
|
||||
<!-- 작업 유형 카드들이 여기에 동적으로 생성됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 코드 추가/수정 모달 -->
|
||||
<div id="codeModal" class="modal-overlay" style="display: none;">
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<h2 id="modalTitle">코드 추가</h2>
|
||||
<button class="modal-close-btn" onclick="closeCodeModal()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<form id="codeForm" onsubmit="event.preventDefault(); saveCode();">
|
||||
<input type="hidden" id="codeId">
|
||||
<input type="hidden" id="codeType">
|
||||
|
||||
<!-- 공통 필드 -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">이름 *</label>
|
||||
<input type="text" id="codeName" class="form-control" placeholder="코드명을 입력하세요" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">설명</label>
|
||||
<textarea id="codeDescription" class="form-control" rows="3" placeholder="상세 설명을 입력하세요"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 작업 상태 유형 전용 필드 -->
|
||||
<div class="form-group" id="isErrorGroup" style="display: none;">
|
||||
<label class="form-label">
|
||||
<input type="checkbox" id="isError" class="form-checkbox">
|
||||
오류 상태로 분류
|
||||
</label>
|
||||
<small class="form-help">체크하면 이 상태는 오류로 간주됩니다</small>
|
||||
</div>
|
||||
|
||||
<!-- 오류 유형 전용 필드 -->
|
||||
<div class="form-group" id="severityGroup" style="display: none;">
|
||||
<label class="form-label">심각도 *</label>
|
||||
<select id="severity" class="form-control">
|
||||
<option value="low">낮음 (Low)</option>
|
||||
<option value="medium" selected>보통 (Medium)</option>
|
||||
<option value="high">높음 (High)</option>
|
||||
<option value="critical">심각 (Critical)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="solutionGuideGroup" style="display: none;">
|
||||
<label class="form-label">해결 가이드</label>
|
||||
<textarea id="solutionGuide" class="form-control" rows="4" placeholder="이 오류 발생 시 해결 방법을 입력하세요"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 작업 유형 전용 필드 -->
|
||||
<div class="form-group" id="categoryGroup" style="display: none;">
|
||||
<label class="form-label">카테고리</label>
|
||||
<input type="text" id="category" class="form-control" placeholder="작업 카테고리 (예: PKG, Vessel)" list="categoryList">
|
||||
<datalist id="categoryList">
|
||||
<!-- 기존 카테고리 목록이 여기에 동적으로 생성됩니다 -->
|
||||
</datalist>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeCodeModal()">취소</button>
|
||||
<button type="button" class="btn btn-danger" id="deleteCodeBtn" onclick="deleteCode()" style="display: none;">
|
||||
🗑️ 삭제
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveCode()">
|
||||
💾 저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/js/load-navbar.js?v=5"></script>
|
||||
<script type="module" src="/js/code-management.js?v=2"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,35 +0,0 @@
|
||||
<section>
|
||||
<h2>📄 작업 보고서</h2>
|
||||
<ul>
|
||||
<li><a href="/pages/work/work-report-create.html">작업보고서 입력</a></li>
|
||||
<li><a href="/pages/work/work-report-manage.html">작업보고서 수정/삭제</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>📊 출근/공수 관리</h2>
|
||||
<ul>
|
||||
<li><a href="/pages/attendance/attendance.html">출근부</a></li>
|
||||
<li><a href="/pages/attendance/project-labor-summary.html">프로젝트별 공수 계산</a></li>
|
||||
<li><a href="/pages/attendance/monthly-labor-report.html">월간 공수 보고서</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>관리콘솔</h2>
|
||||
<ul>
|
||||
<li><a href="/pages/admin/manage-all.html">📋 기본정보 관리</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>🏭 공장 정보</h2>
|
||||
<ul>
|
||||
<li><a href="/pages/factory/factory-map-upload.html">공장 지도 업로드</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>🗂 기타 관리</h2>
|
||||
<p>프로젝트 및 작업자 관련 기능은 추후 확장 예정</p>
|
||||
</section>
|
||||
159
web-ui/pages/admin/index.html
Normal file
159
web-ui/pages/admin/index.html
Normal file
@@ -0,0 +1,159 @@
|
||||
<!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/design-system.css">
|
||||
<link rel="stylesheet" href="/css/common.css?v=2">
|
||||
<link rel="stylesheet" href="/css/work-management.css?v=2">
|
||||
<link rel="icon" type="image/png" href="/img/favicon.png">
|
||||
<script src="/js/auth-check.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="work-report-container">
|
||||
<!-- 네비게이션 바 -->
|
||||
<div id="navbar-container"></div>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main class="work-report-main">
|
||||
<!-- 뒤로가기 버튼 -->
|
||||
<a href="javascript:history.back()" class="back-button">
|
||||
← 뒤로가기
|
||||
</a>
|
||||
|
||||
<div class="dashboard-main">
|
||||
<!-- 빠른 액세스 섹션 -->
|
||||
<div class="quick-access-section">
|
||||
<h2 class="section-title">⚡ 빠른 액세스</h2>
|
||||
<div class="quick-actions-grid">
|
||||
<button class="quick-action-btn" onclick="navigateToPage('/pages/admin/projects.html')">
|
||||
<span class="quick-icon">📁</span>
|
||||
<span class="quick-text">새 프로젝트</span>
|
||||
</button>
|
||||
<button class="quick-action-btn" onclick="navigateToPage('/pages/admin/workers.html')">
|
||||
<span class="quick-icon">👤</span>
|
||||
<span class="quick-text">작업자 등록</span>
|
||||
</button>
|
||||
<button class="quick-action-btn" onclick="navigateToPage('/pages/admin/codes.html')">
|
||||
<span class="quick-icon">🏷️</span>
|
||||
<span class="quick-text">코드 설정</span>
|
||||
</button>
|
||||
<button class="quick-action-btn" onclick="navigateToPage('/pages/work/analysis.html')">
|
||||
<span class="quick-icon">📊</span>
|
||||
<span class="quick-text">작업 분석</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 관리 메뉴 카드들 -->
|
||||
<div class="management-section">
|
||||
<h2 class="section-title">🔧 관리 메뉴</h2>
|
||||
<div class="management-grid">
|
||||
<!-- 프로젝트 관리 -->
|
||||
<div class="management-card" onclick="navigateToPage('/pages/admin/projects.html')">
|
||||
<div class="card-header">
|
||||
<div class="card-icon">📁</div>
|
||||
<h3 class="card-title">프로젝트 관리</h3>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p class="card-description">프로젝트 등록, 수정, 삭제 및 기본 정보 관리</p>
|
||||
<div class="card-stats">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">등록된 프로젝트</span>
|
||||
<span class="stat-value" id="projectCount">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<span class="card-action">관리하기 →</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 작업자 관리 -->
|
||||
<div class="management-card" onclick="navigateToPage('/pages/admin/workers.html')">
|
||||
<div class="card-header">
|
||||
<div class="card-icon">👥</div>
|
||||
<h3 class="card-title">작업자 관리</h3>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p class="card-description">작업자 등록, 수정, 비활성화 및 정보 관리</p>
|
||||
<div class="card-stats">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">활성 작업자</span>
|
||||
<span class="stat-value" id="workerCount">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<span class="card-action">관리하기 →</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 코드 관리 -->
|
||||
<div class="management-card" onclick="navigateToPage('/pages/admin/codes.html')">
|
||||
<div class="card-header">
|
||||
<div class="card-icon">🏷️</div>
|
||||
<h3 class="card-title">코드 관리</h3>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p class="card-description">이슈 타입, 에러 타입, 작업 상태 등 코드 관리</p>
|
||||
<div class="card-stats">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">코드 타입</span>
|
||||
<span class="stat-value" id="codeTypeCount">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<span class="card-action">관리하기 →</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 시스템 상태 섹션 -->
|
||||
<div class="system-status-section">
|
||||
<h2 class="section-title">📊 시스템 현황</h2>
|
||||
<div class="status-grid">
|
||||
<div class="status-card">
|
||||
<div class="status-icon">📁</div>
|
||||
<div class="status-info">
|
||||
<span class="status-label">활성 프로젝트</span>
|
||||
<span class="status-value" id="activeProjectCount">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-card">
|
||||
<div class="status-icon">👥</div>
|
||||
<div class="status-info">
|
||||
<span class="status-label">등록 작업자</span>
|
||||
<span class="status-value" id="totalWorkerCount">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-card">
|
||||
<div class="status-icon">📋</div>
|
||||
<div class="status-info">
|
||||
<span class="status-label">이번 달 작업</span>
|
||||
<span class="status-value" id="monthlyWorkCount">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-card">
|
||||
<div class="status-icon">⚠️</div>
|
||||
<div class="status-info">
|
||||
<span class="status-label">미완료 작업</span>
|
||||
<span class="status-value" id="incompleteWorkCount">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script type="module" src="/js/api-config.js?v=3"></script>
|
||||
<script type="module" src="/js/load-navbar.js?v=5"></script>
|
||||
<script type="module" src="/js/work-management.js?v=3"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,667 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Calendar, Clock, Users, AlertTriangle, CheckCircle, Edit3, Filter } from 'lucide-react';
|
||||
|
||||
const AttendanceValidationPage = () => {
|
||||
const [currentDate, setCurrentDate] = useState(new Date());
|
||||
const [selectedDate, setSelectedDate] = useState(null);
|
||||
const [attendanceData, setAttendanceData] = useState({});
|
||||
const [selectedDateWorkers, setSelectedDateWorkers] = useState([]);
|
||||
const [filter, setFilter] = useState('all'); // all, needsReview, normal, missing
|
||||
const [monthlyData, setMonthlyData] = useState({ workReports: [], dailyReports: [] });
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isAuthorized, setIsAuthorized] = useState(true); // TODO: 실제 권한 체크
|
||||
|
||||
// 월이 변경될 때마다 해당 월의 전체 데이터 로드
|
||||
useEffect(() => {
|
||||
loadMonthlyData();
|
||||
}, [currentDate]);
|
||||
|
||||
const loadMonthlyData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const year = currentDate.getFullYear();
|
||||
const month = currentDate.getMonth() + 1;
|
||||
|
||||
console.log(`${year}년 ${month}월 데이터 로딩 중...`);
|
||||
const data = await fetchMonthlyData(year, month);
|
||||
setMonthlyData(data);
|
||||
|
||||
} catch (error) {
|
||||
console.error('월간 데이터 로딩 실패:', error);
|
||||
// 실패 시 빈 데이터로 설정
|
||||
setMonthlyData({ workReports: [], dailyReports: [] });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 실제 API 호출 함수들
|
||||
const fetchWorkReports = async (date) => {
|
||||
try {
|
||||
const response = await fetch(`/api/workreports/date/${date}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
});
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('WorkReports API 호출 오류:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDailyWorkReports = async (date) => {
|
||||
try {
|
||||
const response = await fetch(`/api/daily-work-reports/date/${date}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
});
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('DailyWorkReports API 호출 오류:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// 월간 데이터 가져오기 (캘린더용)
|
||||
const fetchMonthlyData = async (year, month) => {
|
||||
const start = `${year}-${month.toString().padStart(2, '0')}-01`;
|
||||
const end = `${year}-${month.toString().padStart(2, '0')}-31`;
|
||||
|
||||
try {
|
||||
const [workReports, dailyReports] = await Promise.all([
|
||||
fetch(`/api/workreports?start=${start}&end=${end}`, {
|
||||
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
||||
}).then(res => res.json()),
|
||||
fetch(`/api/daily-work-reports/search?start_date=${start}&end_date=${end}`, {
|
||||
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
||||
}).then(res => res.json())
|
||||
]);
|
||||
|
||||
return { workReports, dailyReports: dailyReports.reports || [] };
|
||||
} catch (error) {
|
||||
console.error('월간 데이터 가져오기 오류:', error);
|
||||
return { workReports: [], dailyReports: [] };
|
||||
}
|
||||
};
|
||||
|
||||
// 목업 데이터 (개발/테스트용)
|
||||
const mockWorkReports = {
|
||||
'2025-06-16': [
|
||||
{ worker_id: 1, worker_name: '김철수', overtime_hours: 1, status: 'normal' },
|
||||
{ worker_id: 2, worker_name: '이영희', overtime_hours: 0, status: 'half_day' },
|
||||
{ worker_id: 3, worker_name: '박민수', overtime_hours: 0, status: 'vacation' },
|
||||
],
|
||||
'2025-06-17': [
|
||||
{ worker_id: 1, worker_name: '김철수', overtime_hours: 2, status: 'normal' },
|
||||
{ worker_id: 2, worker_name: '이영희', overtime_hours: 0, status: 'normal' },
|
||||
{ worker_id: 4, worker_name: '정수현', overtime_hours: 0, status: 'early_leave' },
|
||||
],
|
||||
'2025-06-19': [
|
||||
{ worker_id: 1, worker_name: '김철수', overtime_hours: 1, status: 'normal' },
|
||||
{ worker_id: 2, worker_name: '이영희', overtime_hours: 0, status: 'half_day' },
|
||||
{ worker_id: 3, worker_name: '박민수', overtime_hours: 0, status: 'vacation' },
|
||||
{ worker_id: 4, worker_name: '정수현', overtime_hours: 0, status: 'normal' },
|
||||
]
|
||||
};
|
||||
|
||||
const mockDailyReports = {
|
||||
'2025-06-16': [
|
||||
{ worker_id: 1, worker_name: '김철수', work_hours: 9 },
|
||||
{ worker_id: 2, worker_name: '이영희', work_hours: 4 },
|
||||
{ worker_id: 3, worker_name: '박민수', work_hours: 0 },
|
||||
],
|
||||
'2025-06-17': [
|
||||
{ worker_id: 1, worker_name: '김철수', work_hours: 10 },
|
||||
{ worker_id: 2, worker_name: '이영희', work_hours: 8 },
|
||||
{ worker_id: 4, worker_name: '정수현', work_hours: 6 },
|
||||
],
|
||||
'2025-06-19': [
|
||||
{ worker_id: 1, worker_name: '김철수', work_hours: 9 },
|
||||
{ worker_id: 2, worker_name: '이영희', work_hours: 4 },
|
||||
{ worker_id: 3, worker_name: '박민수', work_hours: 0 },
|
||||
// 정수현 데이터 누락 - 미입력 상태
|
||||
]
|
||||
};
|
||||
|
||||
// 시간 계산 함수
|
||||
const calculateExpectedHours = (status, overtime_hours = 0) => {
|
||||
const baseHours = {
|
||||
'normal': 8,
|
||||
'half_day': 4,
|
||||
'early_leave': 6,
|
||||
'quarter_day': 2,
|
||||
'vacation': 0,
|
||||
'sick_leave': 0
|
||||
};
|
||||
return (baseHours[status] || 8) + (overtime_hours || 0);
|
||||
};
|
||||
|
||||
// 날짜별 상태 계산 (월간 데이터 기반)
|
||||
const calculateDateStatus = (dateStr) => {
|
||||
const workReports = monthlyData.workReports.filter(wr => wr.date === dateStr);
|
||||
const dailyReports = monthlyData.dailyReports.filter(dr => dr.report_date === dateStr);
|
||||
|
||||
if (workReports.length === 0 && dailyReports.length === 0) {
|
||||
return 'no-data';
|
||||
}
|
||||
|
||||
if (workReports.length === 0 || dailyReports.length === 0) {
|
||||
return 'missing';
|
||||
}
|
||||
|
||||
// 작업자별 시간 집계
|
||||
const dailyGrouped = dailyReports.reduce((acc, dr) => {
|
||||
if (!acc[dr.worker_id]) {
|
||||
acc[dr.worker_id] = 0;
|
||||
}
|
||||
acc[dr.worker_id] += parseFloat(dr.work_hours || 0);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// 불일치 검사
|
||||
const hasDiscrepancy = workReports.some(wr => {
|
||||
const reportedHours = dailyGrouped[wr.worker_id] || 0;
|
||||
const expectedHours = calculateExpectedHours('normal', wr.overtime_hours || 0);
|
||||
return Math.abs(reportedHours - expectedHours) > 0;
|
||||
});
|
||||
|
||||
return hasDiscrepancy ? 'needs-review' : 'normal';
|
||||
};
|
||||
|
||||
// 권한 체크 (실제 구현 시)
|
||||
const checkPermission = () => {
|
||||
// TODO: 실제 권한 체크 로직
|
||||
const userRole = localStorage.getItem('userRole') || 'user';
|
||||
return userRole === 'admin' || userRole === 'manager';
|
||||
};
|
||||
|
||||
// 권한 없음 UI
|
||||
if (!isAuthorized) {
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto p-6 bg-gray-50 min-h-screen flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="text-6xl mb-4">🔒</div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-2">접근 권한이 없습니다</h1>
|
||||
<p className="text-gray-600">이 페이지는 관리자(Admin) 이상만 접근 가능합니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 로딩 중 UI
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto p-6 bg-gray-50 min-h-screen">
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
<span className="ml-3 text-gray-600">데이터를 불러오는 중...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 캘린더 생성
|
||||
const generateCalendar = () => {
|
||||
const year = currentDate.getFullYear();
|
||||
const month = currentDate.getMonth();
|
||||
const firstDay = new Date(year, month, 1);
|
||||
const lastDay = new Date(year, month + 1, 0);
|
||||
const startDate = new Date(firstDay);
|
||||
startDate.setDate(startDate.getDate() - firstDay.getDay());
|
||||
|
||||
const calendar = [];
|
||||
const current = new Date(startDate);
|
||||
|
||||
for (let week = 0; week < 6; week++) {
|
||||
const weekDays = [];
|
||||
for (let day = 0; day < 7; day++) {
|
||||
const dateStr = current.toISOString().split('T')[0];
|
||||
const isCurrentMonth = current.getMonth() === month;
|
||||
const status = isCurrentMonth ? calculateDateStatus(dateStr) : 'no-data';
|
||||
|
||||
weekDays.push({
|
||||
date: new Date(current),
|
||||
dateStr,
|
||||
isCurrentMonth,
|
||||
status
|
||||
});
|
||||
current.setDate(current.getDate() + 1);
|
||||
}
|
||||
calendar.push(weekDays);
|
||||
}
|
||||
return calendar;
|
||||
};
|
||||
|
||||
// 실제 API를 사용한 작업자 데이터 조합
|
||||
const getWorkersForDate = async (dateStr) => {
|
||||
try {
|
||||
// 실제 API 호출
|
||||
const [workReports, dailyReports] = await Promise.all([
|
||||
fetchWorkReports(dateStr),
|
||||
fetchDailyWorkReports(dateStr)
|
||||
]);
|
||||
|
||||
console.log('API 응답:', { workReports, dailyReports });
|
||||
|
||||
const workerMap = new Map();
|
||||
|
||||
// WorkReports 데이터 추가 (생산지원팀 입력)
|
||||
workReports.forEach(wr => {
|
||||
workerMap.set(wr.worker_id, {
|
||||
worker_id: wr.worker_id,
|
||||
worker_name: wr.worker_name,
|
||||
overtime_hours: wr.overtime_hours || 0,
|
||||
status: 'normal', // 실제 테이블 구조에 맞게 수정 필요
|
||||
expected_hours: calculateExpectedHours('normal', wr.overtime_hours),
|
||||
reported_hours: null,
|
||||
hasWorkReport: true,
|
||||
hasDailyReport: false
|
||||
});
|
||||
});
|
||||
|
||||
// DailyReports 데이터 추가 (그룹장 입력) - 작업자별 총 시간 집계
|
||||
const dailyGrouped = dailyReports.reduce((acc, dr) => {
|
||||
if (!acc[dr.worker_id]) {
|
||||
acc[dr.worker_id] = {
|
||||
worker_id: dr.worker_id,
|
||||
worker_name: dr.worker_name,
|
||||
total_work_hours: 0
|
||||
};
|
||||
}
|
||||
acc[dr.worker_id].total_work_hours += parseFloat(dr.work_hours || 0);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
Object.values(dailyGrouped).forEach(dr => {
|
||||
if (workerMap.has(dr.worker_id)) {
|
||||
const worker = workerMap.get(dr.worker_id);
|
||||
worker.reported_hours = dr.total_work_hours;
|
||||
worker.hasDailyReport = true;
|
||||
} else {
|
||||
workerMap.set(dr.worker_id, {
|
||||
worker_id: dr.worker_id,
|
||||
worker_name: dr.worker_name,
|
||||
overtime_hours: 0,
|
||||
status: 'normal',
|
||||
expected_hours: 8,
|
||||
reported_hours: dr.total_work_hours,
|
||||
hasWorkReport: false,
|
||||
hasDailyReport: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(workerMap.values()).map(worker => ({
|
||||
...worker,
|
||||
difference: worker.reported_hours !== null ? worker.reported_hours - worker.expected_hours : -worker.expected_hours,
|
||||
validationStatus: getValidationStatus(worker)
|
||||
}));
|
||||
|
||||
} catch (error) {
|
||||
console.error('데이터 조합 오류:', error);
|
||||
// 오류 시 목업 데이터 사용
|
||||
return getWorkersForDateMock(dateStr);
|
||||
}
|
||||
};
|
||||
|
||||
// 목업 데이터용 함수 (개발/테스트)
|
||||
const getWorkersForDateMock = (dateStr) => {
|
||||
const workReports = mockWorkReports[dateStr] || [];
|
||||
const dailyReports = mockDailyReports[dateStr] || [];
|
||||
|
||||
const workerMap = new Map();
|
||||
|
||||
// WorkReports 데이터 추가
|
||||
workReports.forEach(wr => {
|
||||
workerMap.set(wr.worker_id, {
|
||||
worker_id: wr.worker_id,
|
||||
worker_name: wr.worker_name,
|
||||
overtime_hours: wr.overtime_hours,
|
||||
status: wr.status,
|
||||
expected_hours: calculateExpectedHours(wr.status, wr.overtime_hours),
|
||||
reported_hours: null,
|
||||
hasWorkReport: true,
|
||||
hasDailyReport: false
|
||||
});
|
||||
});
|
||||
|
||||
// DailyReports 데이터 추가
|
||||
dailyReports.forEach(dr => {
|
||||
if (workerMap.has(dr.worker_id)) {
|
||||
const worker = workerMap.get(dr.worker_id);
|
||||
worker.reported_hours = dr.work_hours;
|
||||
worker.hasDailyReport = true;
|
||||
} else {
|
||||
workerMap.set(dr.worker_id, {
|
||||
worker_id: dr.worker_id,
|
||||
worker_name: dr.worker_name,
|
||||
overtime_hours: 0,
|
||||
status: 'normal',
|
||||
expected_hours: 8,
|
||||
reported_hours: dr.work_hours,
|
||||
hasWorkReport: false,
|
||||
hasDailyReport: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(workerMap.values()).map(worker => ({
|
||||
...worker,
|
||||
difference: worker.reported_hours !== null ? worker.reported_hours - worker.expected_hours : -worker.expected_hours,
|
||||
validationStatus: getValidationStatus(worker)
|
||||
}));
|
||||
};
|
||||
|
||||
const getValidationStatus = (worker) => {
|
||||
if (!worker.hasWorkReport || !worker.hasDailyReport) return 'missing';
|
||||
if (Math.abs(worker.difference) > 0) return 'needs-review';
|
||||
return 'normal';
|
||||
};
|
||||
|
||||
// 작업자 수정 핸들러
|
||||
const handleEditWorker = (worker) => {
|
||||
// TODO: 수정 모달 또는 인라인 편집 구현
|
||||
const newHours = prompt(
|
||||
`${worker.worker_name}의 근무시간을 수정하세요.\n현재: ${worker.reported_hours || 0}시간`,
|
||||
worker.reported_hours || 0
|
||||
);
|
||||
|
||||
if (newHours !== null && !isNaN(newHours)) {
|
||||
updateWorkerHours(worker.worker_id, parseFloat(newHours));
|
||||
}
|
||||
};
|
||||
|
||||
// 작업자 시간 업데이트 (실제 API 호출)
|
||||
const updateWorkerHours = async (workerId, newHours) => {
|
||||
try {
|
||||
// TODO: 실제 수정 API 호출
|
||||
console.log(`작업자 ${workerId}의 시간을 ${newHours}시간으로 수정`);
|
||||
|
||||
// 임시: 로컬 상태 업데이트
|
||||
setSelectedDateWorkers(prev =>
|
||||
prev.map(worker =>
|
||||
worker.worker_id === workerId
|
||||
? {
|
||||
...worker,
|
||||
reported_hours: newHours,
|
||||
difference: newHours - worker.expected_hours,
|
||||
validationStatus: Math.abs(newHours - worker.expected_hours) > 0 ? 'needs-review' : 'normal'
|
||||
}
|
||||
: worker
|
||||
)
|
||||
);
|
||||
|
||||
alert('수정이 완료되었습니다.');
|
||||
|
||||
} catch (error) {
|
||||
console.error('수정 실패:', error);
|
||||
alert('수정 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
// 날짜 클릭 핸들러 (실제 API 호출)
|
||||
const handleDateClick = async (dateInfo) => {
|
||||
if (!dateInfo.isCurrentMonth) return;
|
||||
|
||||
setSelectedDate(dateInfo.dateStr);
|
||||
|
||||
try {
|
||||
// 로딩 상태 표시
|
||||
setSelectedDateWorkers([]);
|
||||
|
||||
// 실제 API에서 데이터 가져오기
|
||||
const workers = await getWorkersForDate(dateInfo.dateStr);
|
||||
setSelectedDateWorkers(workers);
|
||||
|
||||
} catch (error) {
|
||||
console.error('날짜별 데이터 로딩 오류:', error);
|
||||
// 오류 시 목업 데이터 사용
|
||||
const workers = getWorkersForDateMock(dateInfo.dateStr);
|
||||
setSelectedDateWorkers(workers);
|
||||
}
|
||||
};
|
||||
|
||||
// 필터링된 작업자 목록
|
||||
const filteredWorkers = selectedDateWorkers.filter(worker => {
|
||||
if (filter === 'all') return true;
|
||||
if (filter === 'needsReview') return worker.validationStatus === 'needs-review';
|
||||
if (filter === 'normal') return worker.validationStatus === 'normal';
|
||||
if (filter === 'missing') return worker.validationStatus === 'missing';
|
||||
return true;
|
||||
});
|
||||
|
||||
// 상태별 아이콘 및 색상
|
||||
const getStatusIcon = (status) => {
|
||||
switch (status) {
|
||||
case 'normal': return <CheckCircle className="w-4 h-4 text-green-500" />;
|
||||
case 'needs-review': return <AlertTriangle className="w-4 h-4 text-yellow-500" />;
|
||||
case 'missing': return <Clock className="w-4 h-4 text-red-500" />;
|
||||
default: return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
switch (status) {
|
||||
case 'normal': return 'bg-green-100 text-green-800';
|
||||
case 'needs-review': return 'bg-yellow-100 text-yellow-800';
|
||||
case 'missing': return 'bg-red-100 text-red-800';
|
||||
default: return 'bg-gray-100 text-gray-800';
|
||||
}
|
||||
};
|
||||
|
||||
const calendar = generateCalendar();
|
||||
const monthNames = ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'];
|
||||
const dayNames = ['일', '월', '화', '수', '목', '금', '토'];
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto p-6 bg-gray-50 min-h-screen">
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
{/* 헤더 */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Users className="w-6 h-6 text-blue-600" />
|
||||
<h1 className="text-2xl font-bold text-gray-900">근태 검증 관리</h1>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
Admin 전용 페이지
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* 캘린더 섹션 */}
|
||||
<div className="lg:col-span-2">
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-4">
|
||||
{/* 캘린더 헤더 */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
const newDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1);
|
||||
setCurrentDate(newDate);
|
||||
}}
|
||||
className="p-2 hover:bg-gray-100 rounded-md"
|
||||
disabled={loading}
|
||||
>
|
||||
◀
|
||||
</button>
|
||||
<h2 className="text-xl font-semibold">
|
||||
{currentDate.getFullYear()}년 {monthNames[currentDate.getMonth()]}
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => {
|
||||
const newDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1);
|
||||
setCurrentDate(newDate);
|
||||
}}
|
||||
className="p-2 hover:bg-gray-100 rounded-md"
|
||||
disabled={loading}
|
||||
>
|
||||
▶
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 월간 요약 정보 */}
|
||||
<div className="mb-4 p-3 bg-gray-50 rounded-lg">
|
||||
<div className="grid grid-cols-3 gap-4 text-sm">
|
||||
<div className="text-center">
|
||||
<div className="text-green-600 font-semibold">
|
||||
{calendar.flat().filter(d => d.isCurrentMonth && d.status === 'normal').length}
|
||||
</div>
|
||||
<div className="text-gray-600">정상</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-yellow-600 font-semibold">
|
||||
{calendar.flat().filter(d => d.isCurrentMonth && d.status === 'needs-review').length}
|
||||
</div>
|
||||
<div className="text-gray-600">검토필요</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-red-600 font-semibold">
|
||||
{calendar.flat().filter(d => d.isCurrentMonth && d.status === 'missing').length}
|
||||
</div>
|
||||
<div className="text-gray-600">미입력</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 요일 헤더 */}
|
||||
<div className="grid grid-cols-7 gap-1 mb-2">
|
||||
{dayNames.map(day => (
|
||||
<div key={day} className="p-2 text-center text-sm font-medium text-gray-500">
|
||||
{day}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 캘린더 본체 */}
|
||||
<div className="grid grid-cols-7 gap-1">
|
||||
{calendar.flat().map((dateInfo, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => handleDateClick(dateInfo)}
|
||||
className={`
|
||||
p-2 text-sm rounded-md h-12 relative transition-colors
|
||||
${dateInfo.isCurrentMonth ? 'text-gray-900' : 'text-gray-400'}
|
||||
${selectedDate === dateInfo.dateStr ? 'bg-blue-100 border-2 border-blue-500' : 'hover:bg-gray-50'}
|
||||
${dateInfo.status === 'needs-review' ? 'bg-yellow-50 border border-yellow-200' : ''}
|
||||
${dateInfo.status === 'missing' ? 'bg-red-50 border border-red-200' : ''}
|
||||
${dateInfo.status === 'normal' ? 'bg-green-50 border border-green-200' : ''}
|
||||
`}
|
||||
>
|
||||
<div className="flex flex-col items-center">
|
||||
<span>{dateInfo.date.getDate()}</span>
|
||||
{dateInfo.isCurrentMonth && dateInfo.status !== 'no-data' && (
|
||||
<div className="absolute top-1 right-1">
|
||||
{dateInfo.status === 'needs-review' && <div className="w-2 h-2 bg-yellow-500 rounded-full"></div>}
|
||||
{dateInfo.status === 'missing' && <div className="w-2 h-2 bg-red-500 rounded-full"></div>}
|
||||
{dateInfo.status === 'normal' && <div className="w-2 h-2 bg-green-500 rounded-full"></div>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 범례 */}
|
||||
<div className="flex items-center justify-center space-x-4 mt-4 text-xs">
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
|
||||
<span>정상</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="w-3 h-3 bg-yellow-500 rounded-full"></div>
|
||||
<span>검토필요</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="w-3 h-3 bg-red-500 rounded-full"></div>
|
||||
<span>미입력</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 작업자 리스트 섹션 */}
|
||||
<div className="lg:col-span-1">
|
||||
{selectedDate ? (
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold">
|
||||
📅 {selectedDate}
|
||||
</h3>
|
||||
<select
|
||||
value={filter}
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
className="text-sm border border-gray-300 rounded-md px-2 py-1"
|
||||
>
|
||||
<option value="all">전체</option>
|
||||
<option value="needsReview">검토필요</option>
|
||||
<option value="normal">정상</option>
|
||||
<option value="missing">미입력</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{filteredWorkers.map(worker => (
|
||||
<div key={worker.worker_id} className={`p-3 rounded-lg border ${getStatusColor(worker.validationStatus)}`}>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="font-medium">{worker.worker_name}</span>
|
||||
{getStatusIcon(worker.validationStatus)}
|
||||
</div>
|
||||
|
||||
<div className="text-xs space-y-1">
|
||||
<div className="flex justify-between">
|
||||
<span>그룹장 입력:</span>
|
||||
<span className="font-mono">
|
||||
{worker.reported_hours !== null ? `${worker.reported_hours}시간` : '미입력'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>시스템 계산:</span>
|
||||
<span className="font-mono">{worker.expected_hours}시간</span>
|
||||
</div>
|
||||
{worker.difference !== 0 && (
|
||||
<div className="flex justify-between font-semibold">
|
||||
<span>차이:</span>
|
||||
<span className={`font-mono ${worker.difference > 0 ? 'text-red-600' : 'text-blue-600'}`}>
|
||||
{worker.difference > 0 ? '+' : ''}{worker.difference}시간
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{worker.validationStatus === 'needs-review' && (
|
||||
<button
|
||||
onClick={() => handleEditWorker(worker)}
|
||||
className="mt-2 w-full text-xs bg-blue-600 text-white px-2 py-1 rounded-md hover:bg-blue-700 flex items-center justify-center space-x-1"
|
||||
>
|
||||
<Edit3 className="w-3 h-3" />
|
||||
<span>수정</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{filteredWorkers.length === 0 && (
|
||||
<div className="text-center text-gray-500 py-8">
|
||||
해당 조건의 작업자가 없습니다.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-4 text-center text-gray-500">
|
||||
<Calendar className="w-12 h-12 mx-auto mb-3 text-gray-300" />
|
||||
<p>날짜를 선택하면</p>
|
||||
<p>작업자 검증 내역을 확인할 수 있습니다.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AttendanceValidationPage;
|
||||
@@ -1,62 +0,0 @@
|
||||
<!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="icon" type="image/png" href="/img/favicon.png">
|
||||
<script src="/js/auth-check.js" defer></script>
|
||||
</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 class="card">
|
||||
<h3>새 이슈 유형 등록</h3>
|
||||
<form id="issueTypeForm" class="form-horizontal">
|
||||
<div class="form-row">
|
||||
<input type="text" id="category" placeholder="카테고리" required>
|
||||
<input type="text" id="subcategory" placeholder="서브카테고리" required>
|
||||
<button type="submit" class="btn btn-primary">등록</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>등록된 이슈 유형</h3>
|
||||
<div class="table-responsive">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>카테고리</th>
|
||||
<th>서브카테고리</th>
|
||||
<th>작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="issueTypeTableBody">
|
||||
<tr><td colspan="4" class="text-center">불러오는 중...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</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/manage-issue.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,76 +0,0 @@
|
||||
<!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="icon" type="image/png" href="/img/favicon.png">
|
||||
<script src="/js/auth-check.js" defer></script>
|
||||
</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 class="card">
|
||||
<h3>새 프로젝트 등록</h3>
|
||||
<form id="projectForm" class="form-vertical">
|
||||
<div class="form-row">
|
||||
<input type="text" id="job_no" placeholder="공사번호" required>
|
||||
<input type="text" id="project_name" placeholder="프로젝트명" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input type="date" id="contract_date" placeholder="계약일">
|
||||
<input type="date" id="due_date" placeholder="납기일">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input type="text" id="delivery_method" placeholder="납품방식">
|
||||
<input type="text" id="site" placeholder="현장명">
|
||||
<input type="text" id="pm" placeholder="담당 PM">
|
||||
<button type="submit" class="btn btn-primary">등록</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>등록된 프로젝트</h3>
|
||||
<div class="table-responsive">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>공사번호</th>
|
||||
<th>프로젝트명</th>
|
||||
<th>계약일</th>
|
||||
<th>납기일</th>
|
||||
<th>납품방식</th>
|
||||
<th>현장</th>
|
||||
<th>PM</th>
|
||||
<th>작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="projectTableBody">
|
||||
<tr><td colspan="9" class="text-center">불러오는 중...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</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/manage-project.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,68 +0,0 @@
|
||||
<!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="icon" type="image/png" href="/img/favicon.png">
|
||||
<script src="/js/auth-check.js" defer></script>
|
||||
</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 class="card">
|
||||
<h3>새 작업 항목 등록</h3>
|
||||
<form id="taskForm" class="form-vertical">
|
||||
<div class="form-row">
|
||||
<input type="text" id="category" placeholder="카테고리" required>
|
||||
<input type="text" id="subcategory" placeholder="서브카테고리" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input type="text" id="task_name" placeholder="작업명" required>
|
||||
<input type="text" id="description" placeholder="설명">
|
||||
<button type="submit" class="btn btn-primary">등록</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>등록된 작업 항목</h3>
|
||||
<div class="table-responsive">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>카테고리</th>
|
||||
<th>서브카테고리</th>
|
||||
<th>작업명</th>
|
||||
<th>설명</th>
|
||||
<th>작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="taskTableBody">
|
||||
<tr><td colspan="6" class="text-center">불러오는 중...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</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/manage-task.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,287 +0,0 @@
|
||||
<!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">
|
||||
<script src="/js/auth-check.js" defer></script>
|
||||
</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 class="card">
|
||||
<h3>🔐 내 비밀번호 변경</h3>
|
||||
<form id="myPasswordForm" class="form-horizontal">
|
||||
<div class="form-row">
|
||||
<input type="password" id="currentPassword" placeholder="현재 비밀번호" required />
|
||||
<input type="password" id="newPassword" placeholder="새 비밀번호" required />
|
||||
<input type="password" id="confirmPassword" placeholder="새 비밀번호 확인" required />
|
||||
<button type="submit" class="btn btn-warning">내 비밀번호 변경</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 등록 폼 -->
|
||||
<div class="card">
|
||||
<h3>새 사용자 등록</h3>
|
||||
<form id="userForm" class="form-horizontal">
|
||||
<div class="form-row">
|
||||
<input type="text" id="username" placeholder="아이디" required />
|
||||
<input type="password" id="password" placeholder="비밀번호" required />
|
||||
<input type="text" id="name" placeholder="이름" required />
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<select id="access_level" required>
|
||||
<option value="">권한 선택</option>
|
||||
<option value="worker">작업자</option>
|
||||
<option value="group_leader">그룹장</option>
|
||||
<option value="support_team">지원팀</option>
|
||||
<option value="admin">관리자</option>
|
||||
<option value="system">시스템</option>
|
||||
</select>
|
||||
<select id="worker_id">
|
||||
<option value="">작업자 연결 (선택)</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary">등록</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 사용자 비밀번호 변경 (시스템 권한자만) -->
|
||||
<div class="card" id="systemPasswordChangeCard" style="display: none;">
|
||||
<h3>🔑 사용자 비밀번호 변경 (시스템 권한자 전용)</h3>
|
||||
<form id="userPasswordForm" class="form-horizontal">
|
||||
<div class="form-row">
|
||||
<select id="targetUserId" required>
|
||||
<option value="">사용자 선택</option>
|
||||
</select>
|
||||
<input type="password" id="targetNewPassword" placeholder="새 비밀번호" required />
|
||||
<button type="submit" class="btn btn-danger">비밀번호 변경</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 사용자 목록 -->
|
||||
<div class="card">
|
||||
<h3>등록된 사용자</h3>
|
||||
<div class="table-responsive">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>아이디</th>
|
||||
<th>이름</th>
|
||||
<th>권한</th>
|
||||
<th>연결 작업자</th>
|
||||
<th>작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="userTableBody">
|
||||
<tr><td colspan="6" class="text-center">불러오는 중...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 페이지 권한 관리 모달 -->
|
||||
<div id="pageAccessModal" class="modal" style="display: none;">
|
||||
<div class="modal-content large">
|
||||
<div class="modal-header">
|
||||
<h2>🔐 페이지 접근 권한 관리</h2>
|
||||
<span class="close">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="user-info-section">
|
||||
<h3 id="modalUserInfo">사용자 정보</h3>
|
||||
<p id="modalUserRole" class="text-muted"></p>
|
||||
</div>
|
||||
|
||||
<div class="page-access-grid">
|
||||
<div class="category-section" id="dashboardPages">
|
||||
<h4>📊 대시보드</h4>
|
||||
<div class="page-list" id="dashboardPageList"></div>
|
||||
</div>
|
||||
|
||||
<div class="category-section" id="managementPages">
|
||||
<h4>⚙️ 관리</h4>
|
||||
<div class="page-list" id="managementPageList"></div>
|
||||
</div>
|
||||
|
||||
<div class="category-section" id="commonPages">
|
||||
<h4>📝 공통</h4>
|
||||
<div class="page-list" id="commonPageList"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closePageAccessModal()">취소</button>
|
||||
<button type="button" class="btn btn-primary" onclick="savePageAccessChanges()">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* 모달 스타일 */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: 5% auto;
|
||||
padding: 0;
|
||||
border: 1px solid #888;
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.modal-content.large {
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 20px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border-radius: 8px 8px 0 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 30px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 15px 20px;
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 0 0 8px 8px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.close {
|
||||
color: white;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.user-info-section {
|
||||
margin-bottom: 30px;
|
||||
padding: 15px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.user-info-section h3 {
|
||||
margin: 0 0 5px 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.page-access-grid {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.category-section h4 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #4CAF50;
|
||||
font-size: 1.1rem;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
}
|
||||
|
||||
.page-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.page-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.page-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.page-item input[type="checkbox"] {
|
||||
margin-right: 15px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.page-item label {
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page-item .page-path {
|
||||
font-size: 0.85rem;
|
||||
color: #666;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #6c757d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="module" src="/js/load-navbar.js"></script>
|
||||
<script type="module" src="/js/load-sidebar.js"></script>
|
||||
<script type="module" src="/js/manage-user.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,62 +0,0 @@
|
||||
<!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="icon" type="image/png" href="/img/favicon.png">
|
||||
<script src="/js/auth-check.js" defer></script>
|
||||
</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 class="card">
|
||||
<h3>새 작업자 등록</h3>
|
||||
<form id="workerForm" class="form-horizontal">
|
||||
<div class="form-row">
|
||||
<input type="text" id="workerName" placeholder="작업자명" required>
|
||||
<input type="text" id="position" placeholder="직책">
|
||||
<button type="submit" class="btn btn-primary">등록</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>등록된 작업자</h3>
|
||||
<div class="table-responsive">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>이름</th>
|
||||
<th>직책</th>
|
||||
<th>작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="workerTableBody">
|
||||
<tr><td colspan="4" class="text-center">불러오는 중...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</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/manage-worker.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
208
web-ui/pages/admin/projects.html
Normal file
208
web-ui/pages/admin/projects.html
Normal file
@@ -0,0 +1,208 @@
|
||||
<!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/design-system.css">
|
||||
<link rel="stylesheet" href="/css/common.css?v=2">
|
||||
<link rel="stylesheet" href="/css/project-management.css?v=4">
|
||||
<link rel="icon" type="image/png" href="/img/favicon.png">
|
||||
<script src="/js/auth-check.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="work-report-container">
|
||||
<!-- 네비게이션 바 -->
|
||||
<div id="navbar-container"></div>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main class="work-report-main">
|
||||
<!-- 뒤로가기 버튼 -->
|
||||
<a href="/pages/admin/index.html" class="back-button">
|
||||
← 작업관리로 돌아가기
|
||||
</a>
|
||||
|
||||
<div class="dashboard-main">
|
||||
<div class="page-header">
|
||||
<div class="page-title-section">
|
||||
<h1 class="page-title">
|
||||
<span class="title-icon">📁</span>
|
||||
프로젝트 관리
|
||||
</h1>
|
||||
<p class="page-description">프로젝트 등록, 수정, 삭제 및 기본 정보를 관리합니다</p>
|
||||
</div>
|
||||
|
||||
<div class="page-actions">
|
||||
<button class="btn btn-primary" onclick="openProjectModal()">
|
||||
<span class="btn-icon">➕</span>
|
||||
새 프로젝트 등록
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="refreshProjectList()">
|
||||
<span class="btn-icon">🔄</span>
|
||||
새로고침
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 검색 및 필터 -->
|
||||
<div class="search-section">
|
||||
<div class="search-bar">
|
||||
<input type="text" id="searchInput" placeholder="프로젝트명 또는 Job No.로 검색..." class="search-input">
|
||||
<button class="search-btn" onclick="searchProjects()">
|
||||
<span class="search-icon">🔍</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="filter-options">
|
||||
<select id="statusFilter" class="filter-select" onchange="filterProjects()">
|
||||
<option value="">전체 상태</option>
|
||||
<option value="active">진행중</option>
|
||||
<option value="completed">완료</option>
|
||||
<option value="paused">중단</option>
|
||||
</select>
|
||||
|
||||
<select id="sortBy" class="filter-select" onchange="sortProjects()">
|
||||
<option value="created_at">등록일순</option>
|
||||
<option value="project_name">프로젝트명순</option>
|
||||
<option value="due_date">납기일순</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 프로젝트 목록 -->
|
||||
<div class="projects-section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">등록된 프로젝트</h2>
|
||||
<div class="project-stats">
|
||||
<span class="stat-item active-stat" onclick="filterByStatus('active')" title="활성 프로젝트만 보기">
|
||||
<span class="stat-icon">🟢</span>
|
||||
활성 <span id="activeProjects">0</span>개
|
||||
</span>
|
||||
<span class="stat-item inactive-stat" onclick="filterByStatus('inactive')" title="비활성 프로젝트만 보기">
|
||||
<span class="stat-icon">🔴</span>
|
||||
비활성 <span id="inactiveProjects">0</span>개
|
||||
</span>
|
||||
<span class="stat-item total-stat" onclick="filterByStatus('all')" title="전체 프로젝트 보기">
|
||||
<span class="stat-icon">📊</span>
|
||||
총 <span id="totalProjects">0</span>개
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="projects-grid" id="projectsGrid">
|
||||
<!-- 프로젝트 카드들이 여기에 동적으로 생성됩니다 -->
|
||||
</div>
|
||||
|
||||
<div class="empty-state" id="emptyState" style="display: none;">
|
||||
<div class="empty-icon">📁</div>
|
||||
<h3>등록된 프로젝트가 없습니다</h3>
|
||||
<p>새 프로젝트를 등록해보세요.</p>
|
||||
<button class="btn btn-primary" onclick="openProjectModal()">
|
||||
<span class="btn-icon">➕</span>
|
||||
첫 번째 프로젝트 등록
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 프로젝트 등록/수정 모달 -->
|
||||
<div id="projectModal" class="modal-overlay" style="display: none;">
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<h2 id="modalTitle">새 프로젝트 등록</h2>
|
||||
<button class="modal-close-btn" onclick="closeProjectModal()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<form id="projectForm">
|
||||
<input type="hidden" id="projectId">
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Job No. *</label>
|
||||
<input type="text" id="jobNo" class="form-control" required placeholder="예: TK-2024-001">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">프로젝트명 *</label>
|
||||
<input type="text" id="projectName" class="form-control" required placeholder="프로젝트명을 입력하세요">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">계약일</label>
|
||||
<input type="date" id="contractDate" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">납기일</label>
|
||||
<input type="date" id="dueDate" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">납품방법</label>
|
||||
<select id="deliveryMethod" class="form-control">
|
||||
<option value="">선택하세요</option>
|
||||
<option value="직접납품">직접납품</option>
|
||||
<option value="택배">택배</option>
|
||||
<option value="화물">화물</option>
|
||||
<option value="현장설치">현장설치</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">현장</label>
|
||||
<input type="text" id="site" class="form-control" placeholder="현장 위치를 입력하세요">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">PM (프로젝트 매니저)</label>
|
||||
<input type="text" id="pm" class="form-control" placeholder="담당 PM을 입력하세요">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">프로젝트 상태</label>
|
||||
<select id="projectStatus" class="form-control">
|
||||
<option value="planning">📋 계획</option>
|
||||
<option value="active" selected>🚀 진행중</option>
|
||||
<option value="completed">✅ 완료</option>
|
||||
<option value="cancelled">❌ 취소</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">완료일 (납품일)</label>
|
||||
<input type="date" id="completedDate" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<input type="checkbox" id="isActive" checked style="margin: 0;">
|
||||
<span>프로젝트 활성화</span>
|
||||
</label>
|
||||
<small style="color: #6b7280; font-size: 0.8rem;">체크 해제 시 작업보고서 입력에서 숨겨집니다</small>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeProjectModal()">취소</button>
|
||||
<button type="button" class="btn btn-danger" id="deleteProjectBtn" onclick="deleteProject()" style="display: none;">
|
||||
🗑️ 삭제
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveProject()">
|
||||
💾 저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script type="module" src="/js/api-config.js?v=3"></script>
|
||||
<script type="module" src="/js/load-navbar.js?v=5"></script>
|
||||
<script type="module" src="/js/project-management.js?v=2"></script>
|
||||
</body>
|
||||
</html>
|
||||
221
web-ui/pages/admin/workers.html
Normal file
221
web-ui/pages/admin/workers.html
Normal file
@@ -0,0 +1,221 @@
|
||||
<!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/design-system.css">
|
||||
<link rel="stylesheet" href="/css/common.css?v=2">
|
||||
<link rel="stylesheet" href="/css/project-management.css?v=3">
|
||||
<link rel="icon" type="image/png" href="/img/favicon.png">
|
||||
<script src="/js/auth-check.js?v=1" defer></script>
|
||||
<script type="module" src="/js/api-config.js?v=3"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="work-report-container">
|
||||
<!-- 네비게이션 바 -->
|
||||
<div id="navbar-container"></div>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main class="work-report-main">
|
||||
<!-- 뒤로가기 버튼 -->
|
||||
<a href="/pages/admin/index.html" class="back-button">
|
||||
← 작업관리로 돌아가기
|
||||
</a>
|
||||
|
||||
<div class="dashboard-main">
|
||||
<div class="page-header">
|
||||
<div class="page-title-section">
|
||||
<h1 class="page-title">
|
||||
<span class="title-icon">👥</span>
|
||||
작업자 관리
|
||||
</h1>
|
||||
<p class="page-description">작업자 등록, 수정, 삭제 및 기본 정보를 관리합니다</p>
|
||||
</div>
|
||||
|
||||
<div class="page-actions">
|
||||
<button class="btn btn-primary" onclick="openWorkerModal()">
|
||||
<span class="btn-icon">➕</span>
|
||||
새 작업자 등록
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="refreshWorkerList()">
|
||||
<span class="btn-icon">🔄</span>
|
||||
새로고침
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 검색 및 필터 -->
|
||||
<div class="search-section">
|
||||
<div class="search-bar">
|
||||
<input type="text" id="searchInput" class="search-input" placeholder="작업자명, 직책, 전화번호로 검색...">
|
||||
<button class="search-btn" onclick="searchWorkers()">
|
||||
<span>🔍</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="filter-options">
|
||||
<select id="jobTypeFilter" class="filter-select" onchange="filterWorkers()">
|
||||
<option value="">모든 직책</option>
|
||||
<option value="leader">그룹장</option>
|
||||
<option value="worker">작업자</option>
|
||||
<option value="admin">관리자</option>
|
||||
</select>
|
||||
|
||||
<select id="statusFilter" class="filter-select" onchange="filterWorkers()">
|
||||
<option value="">모든 상태</option>
|
||||
<option value="active">활성</option>
|
||||
<option value="inactive">비활성</option>
|
||||
</select>
|
||||
|
||||
<select id="sortBy" class="filter-select" onchange="sortWorkers()">
|
||||
<option value="created_at">등록일순</option>
|
||||
<option value="worker_name">이름순</option>
|
||||
<option value="job_type">직책순</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 작업자 목록 -->
|
||||
<div class="projects-section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">등록된 작업자</h2>
|
||||
<div class="project-stats">
|
||||
<span class="stat-item active-stat" onclick="filterByStatus('active')" title="활성 작업자만 보기">
|
||||
<span class="stat-icon">🟢</span>
|
||||
활성 <span id="activeWorkers">0</span>명
|
||||
</span>
|
||||
<span class="stat-item inactive-stat" onclick="filterByStatus('inactive')" title="비활성 작업자만 보기">
|
||||
<span class="stat-icon">🔴</span>
|
||||
비활성 <span id="inactiveWorkers">0</span>명
|
||||
</span>
|
||||
<span class="stat-item total-stat" onclick="filterByStatus('all')" title="전체 작업자 보기">
|
||||
<span class="stat-icon">📊</span>
|
||||
총 <span id="totalWorkers">0</span>명
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="projects-grid" id="workersGrid">
|
||||
<!-- 작업자 카드들이 여기에 동적으로 생성됩니다 -->
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div class="empty-state" id="emptyState" style="display: none;">
|
||||
<div class="empty-icon">👥</div>
|
||||
<h3>등록된 작업자가 없습니다.</h3>
|
||||
<p>"새 작업자 등록" 버튼을 눌러 작업자를 등록해보세요.</p>
|
||||
<button class="btn btn-primary" onclick="openWorkerModal()">
|
||||
➕ 첫 작업자 등록하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 작업자 추가/수정 모달 -->
|
||||
<div id="workerModal" class="modal-overlay" style="display: none;">
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<h2 id="modalTitle">새 작업자 등록</h2>
|
||||
<button class="modal-close-btn" onclick="closeWorkerModal()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<form id="workerForm" onsubmit="event.preventDefault(); saveWorker();">
|
||||
<input type="hidden" id="workerId">
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">작업자명 *</label>
|
||||
<input type="text" id="workerName" class="form-control" placeholder="작업자 이름을 입력하세요" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">직책</label>
|
||||
<select id="jobType" class="form-control">
|
||||
<option value="worker">👷 작업자</option>
|
||||
<option value="leader">👨💼 그룹장</option>
|
||||
<option value="admin">👨💻 관리자</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">전화번호</label>
|
||||
<input type="tel" id="phoneNumber" class="form-control" placeholder="010-0000-0000">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">이메일</label>
|
||||
<input type="email" id="email" class="form-control" placeholder="example@company.com">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">입사일</label>
|
||||
<input type="date" id="hireDate" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">부서</label>
|
||||
<input type="text" id="department" class="form-control" placeholder="소속 부서">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">비고</label>
|
||||
<textarea id="notes" class="form-control" rows="3" placeholder="추가 정보나 특이사항을 입력하세요"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 상태 관리 섹션 -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" style="font-weight: 600; margin-bottom: 0.75rem; display: block;">상태 관리</label>
|
||||
|
||||
<div style="display: flex; flex-direction: column; gap: 0.75rem;">
|
||||
<!-- 계정 생성/연동 -->
|
||||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
||||
<input type="checkbox" id="hasAccount" style="margin: 0; cursor: pointer;">
|
||||
<span>🔐 계정 생성/연동</span>
|
||||
</label>
|
||||
<small style="color: #6b7280; font-size: 0.75rem; margin-top: -0.5rem; margin-left: 1.5rem;">
|
||||
체크 시 로그인 계정이 자동 생성됩니다 (나의 대시보드, 연차/출퇴근 관리 가능)
|
||||
</small>
|
||||
|
||||
<!-- 현장직/사무직 구분 -->
|
||||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
||||
<input type="checkbox" id="isActive" checked style="margin: 0; cursor: pointer;">
|
||||
<span>🏭 현장직 (활성화)</span>
|
||||
</label>
|
||||
<small style="color: #6b7280; font-size: 0.75rem; margin-top: -0.5rem; margin-left: 1.5rem;">
|
||||
체크: 현장직 (출퇴근 관리 필요) / 체크 해제: 사무직 (출퇴근 관리 불필요)
|
||||
</small>
|
||||
|
||||
<!-- 퇴사 처리 -->
|
||||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
||||
<input type="checkbox" id="isResigned" style="margin: 0; cursor: pointer;">
|
||||
<span style="color: #ef4444;">🚪 퇴사 처리</span>
|
||||
</label>
|
||||
<small style="color: #ef4444; font-size: 0.75rem; margin-top: -0.5rem; margin-left: 1.5rem;">
|
||||
퇴사한 작업자로 표시됩니다. 작업 보고서에서 제외됩니다
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeWorkerModal()">취소</button>
|
||||
<button type="button" class="btn btn-danger" id="deleteWorkerBtn" onclick="deleteWorker()" style="display: none;">
|
||||
🗑️ 삭제
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveWorker()">
|
||||
💾 저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/js/load-navbar.js?v=5"></script>
|
||||
<script type="module" src="/js/worker-management.js?v=7"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user