🚀 초기 프로젝트 설정 완료

 기능:
- 기간제 근로자 작업관리 시스템 기본 구조
- 한국어 기반 프론트엔드 (로그인, 대시보드, 작업자 관리)
- Node.js Express 백엔드 API 서버 구조
- MySQL 데이터베이스 스키마 설계
- 14000번대 포트 구성으로 충돌 방지

📁 구조:
- frontend/ : HTML, CSS, JS (Bootstrap 5)
- backend/ : Node.js, Express, MySQL
- database/ : 초기화 스크립트
- docs/ : 문서

🔌 포트:
- 웹: 14000, API: 14001, DB: 14002, phpMyAdmin: 14003

🎯 다음 단계: 백엔드 API 라우트 구현 및 Docker 설정
This commit is contained in:
hyungi
2025-10-20 13:31:39 +09:00
commit 13b09ef2ae
11 changed files with 1674 additions and 0 deletions

292
frontend/css/dashboard.css Normal file
View File

@@ -0,0 +1,292 @@
/* 작업관리 시스템 공통 스타일 */
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap');
body {
font-family: 'Noto Sans KR', sans-serif;
background-color: #f8f9fa;
}
/* 네비게이션 스타일 */
.navbar-brand {
font-weight: 700;
font-size: 1.5rem;
}
.nav-link {
font-weight: 500;
transition: all 0.3s ease;
}
.nav-link:hover {
transform: translateY(-1px);
}
.nav-link.active {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 5px;
}
/* 카드 스타일 */
.card {
border: none;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.card-header {
background-color: #fff;
border-bottom: 2px solid #f8f9fa;
border-radius: 15px 15px 0 0 !important;
font-weight: 600;
}
/* 통계 카드 */
.card.bg-primary,
.card.bg-success,
.card.bg-warning,
.card.bg-info {
background: linear-gradient(135deg, var(--bs-primary) 0%, #764ba2 100%) !important;
}
.card.bg-success {
background: linear-gradient(135deg, var(--bs-success) 0%, #56ab2f 100%) !important;
}
.card.bg-warning {
background: linear-gradient(135deg, var(--bs-warning) 0%, #f093fb 100%) !important;
}
.card.bg-info {
background: linear-gradient(135deg, var(--bs-info) 0%, #4facfe 100%) !important;
}
/* 버튼 스타일 */
.btn {
border-radius: 10px;
font-weight: 500;
transition: all 0.3s ease;
}
.btn:hover {
transform: translateY(-1px);
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
}
.btn-success {
background: linear-gradient(135deg, #56ab2f 0%, #a8e6cf 100%);
border: none;
}
.btn-warning {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
border: none;
}
.btn-info {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
border: none;
}
/* 테이블 스타일 */
.table {
border-radius: 10px;
overflow: hidden;
}
.table thead th {
background-color: #f8f9fa;
border: none;
font-weight: 600;
color: #495057;
}
.table tbody tr {
transition: all 0.3s ease;
}
.table tbody tr:hover {
background-color: #f8f9fa;
transform: scale(1.01);
}
/* 폼 스타일 */
.form-control,
.form-select {
border-radius: 10px;
border: 2px solid #e9ecef;
transition: all 0.3s ease;
}
.form-control:focus,
.form-select:focus {
border-color: #667eea;
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
}
/* 모달 스타일 */
.modal-content {
border-radius: 15px;
border: none;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
}
.modal-header {
border-bottom: 2px solid #f8f9fa;
border-radius: 15px 15px 0 0;
}
.modal-footer {
border-top: 2px solid #f8f9fa;
border-radius: 0 0 15px 15px;
}
/* 배지 스타일 */
.badge {
border-radius: 20px;
font-weight: 500;
padding: 0.5em 1em;
}
/* 직종별 배지 색상 */
.badge.job-welder {
background-color: #ff6b6b;
}
.badge.job-plumber {
background-color: #4ecdc4;
}
/* 상태별 배지 색상 */
.badge.status-active {
background-color: #51cf66;
}
.badge.status-inactive {
background-color: #868e96;
}
.badge.status-completed {
background-color: #51cf66;
}
.badge.status-in-progress {
background-color: #339af0;
}
.badge.status-planned {
background-color: #ffd43b;
color: #495057;
}
.badge.status-cancelled {
background-color: #868e96;
}
/* 우선순위별 배지 색상 */
.badge.priority-low {
background-color: #51cf66;
}
.badge.priority-normal {
background-color: #339af0;
}
.badge.priority-high {
background-color: #ff8cc8;
}
.badge.priority-urgent {
background-color: #ff6b6b;
}
/* 심각도별 배지 색상 */
.badge.severity-low {
background-color: #51cf66;
}
.badge.severity-medium {
background-color: #ffd43b;
color: #495057;
}
.badge.severity-high {
background-color: #ff8cc8;
}
.badge.severity-critical {
background-color: #ff6b6b;
}
/* 토스트 스타일 */
.toast {
border-radius: 10px;
border: none;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
/* 반응형 스타일 */
@media (max-width: 768px) {
.container-fluid {
padding-left: 15px;
padding-right: 15px;
}
.card-body {
padding: 1rem;
}
.btn {
font-size: 0.9rem;
}
.table-responsive {
font-size: 0.9rem;
}
}
/* 로딩 애니메이션 */
@keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
.loading {
animation: pulse 1.5s ease-in-out infinite;
}
/* 스크롤바 스타일 */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}

216
frontend/dashboard.html Normal file
View File

@@ -0,0 +1,216 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>작업관리 시스템 - 대시보드</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<link href="css/dashboard.css" rel="stylesheet">
</head>
<body>
<!-- 상단 네비게이션 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<a class="navbar-brand" href="#">
<i class="bi bi-tools me-2"></i>작업관리 시스템
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="dashboard.html">
<i class="bi bi-house-door me-1"></i>대시보드
</a>
</li>
<li class="nav-item" id="workerManagementNav" style="display: none;">
<a class="nav-link" href="workers.html">
<i class="bi bi-people me-1"></i>작업자 관리
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="daily-work.html">
<i class="bi bi-calendar-check me-1"></i>일일 작업
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="errors.html">
<i class="bi bi-exclamation-triangle me-1"></i>에러사항
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="requests.html">
<i class="bi bi-cart me-1"></i>요청사항
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="bi bi-person-circle me-1"></i><span id="userNameDisplay">사용자</span>
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="logout()">
<i class="bi bi-box-arrow-right me-2"></i>로그아웃
</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid mt-4">
<div class="row">
<!-- 메인 콘텐츠 -->
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-speedometer2 me-2"></i>대시보드</h2>
<div class="text-muted">
<i class="bi bi-calendar3 me-1"></i><span id="currentDate"></span>
</div>
</div>
<!-- 통계 카드 -->
<div class="row mb-4">
<div class="col-md-3 mb-3">
<div class="card bg-primary text-white h-100">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="card-title mb-0" id="totalWorkers">0</h4>
<p class="card-text">전체 작업자</p>
</div>
<div class="align-self-center">
<i class="bi bi-people fs-1"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card bg-success text-white h-100">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="card-title mb-0" id="todayWork">0</h4>
<p class="card-text">오늘 작업</p>
</div>
<div class="align-self-center">
<i class="bi bi-calendar-check fs-1"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card bg-warning text-white h-100">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="card-title mb-0" id="pendingErrors">0</h4>
<p class="card-text">미해결 에러</p>
</div>
<div class="align-self-center">
<i class="bi bi-exclamation-triangle fs-1"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card bg-info text-white h-100">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="card-title mb-0" id="pendingRequests">0</h4>
<p class="card-text">대기 요청</p>
</div>
<div class="align-self-center">
<i class="bi bi-cart fs-1"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 최근 활동 -->
<div class="row">
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-clock-history me-2"></i>최근 작업</h5>
</div>
<div class="card-body">
<div id="recentWork">
<div class="text-center text-muted">
<i class="bi bi-hourglass-split fs-1"></i>
<p>데이터를 불러오는 중...</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-exclamation-circle me-2"></i>최근 에러</h5>
</div>
<div class="card-body">
<div id="recentErrors">
<div class="text-center text-muted">
<i class="bi bi-hourglass-split fs-1"></i>
<p>데이터를 불러오는 중...</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 빠른 작업 버튼 -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-lightning me-2"></i>빠른 작업</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-3 mb-2" id="addWorkerBtn" style="display: none;">
<button class="btn btn-outline-primary w-100" onclick="location.href='workers.html'">
<i class="bi bi-person-plus me-2"></i>작업자 추가
</button>
</div>
<div class="col-md-3 mb-2">
<button class="btn btn-outline-success w-100" onclick="location.href='daily-work.html'">
<i class="bi bi-plus-circle me-2"></i>작업 등록
</button>
</div>
<div class="col-md-3 mb-2">
<button class="btn btn-outline-warning w-100" onclick="location.href='errors.html'">
<i class="bi bi-exclamation-triangle me-2"></i>에러 신고
</button>
</div>
<div class="col-md-3 mb-2">
<button class="btn btn-outline-info w-100" onclick="location.href='requests.html'">
<i class="bi bi-cart-plus me-2"></i>요청 등록
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="js/auth.js"></script>
<script src="js/dashboard.js"></script>
</body>
</html>

140
frontend/index.html Normal file
View File

@@ -0,0 +1,140 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>작업관리 시스템 - 로그인</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
font-family: 'Noto Sans KR', sans-serif;
}
.login-container {
background: white;
border-radius: 15px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
overflow: hidden;
max-width: 400px;
width: 100%;
}
.login-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem;
text-align: center;
}
.login-body {
padding: 2rem;
}
.form-control {
border-radius: 10px;
border: 2px solid #e9ecef;
padding: 12px 15px;
font-size: 16px;
}
.form-control:focus {
border-color: #667eea;
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
}
.btn-login {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 10px;
padding: 12px;
font-size: 16px;
font-weight: 600;
width: 100%;
color: white;
transition: transform 0.2s;
}
.btn-login:hover {
transform: translateY(-2px);
color: white;
}
.input-group-text {
background: #f8f9fa;
border: 2px solid #e9ecef;
border-right: none;
border-radius: 10px 0 0 10px;
}
.input-group .form-control {
border-left: none;
border-radius: 0 10px 10px 0;
}
</style>
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="login-container">
<div class="login-header">
<h2><i class="bi bi-tools"></i> 작업관리 시스템</h2>
<p class="mb-0">기간제 근로자 작업관리</p>
</div>
<div class="login-body">
<form id="loginForm">
<div class="mb-3">
<label for="username" class="form-label">아이디</label>
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-person"></i>
</span>
<input type="text" class="form-control" id="username" name="username"
placeholder="아이디를 입력하세요" required>
</div>
</div>
<div class="mb-4">
<label for="password" class="form-label">비밀번호</label>
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-lock"></i>
</span>
<input type="password" class="form-control" id="password" name="password"
placeholder="비밀번호를 입력하세요" required>
</div>
</div>
<button type="submit" class="btn btn-login">
<i class="bi bi-box-arrow-in-right me-2"></i>로그인
</button>
</form>
<div class="mt-4 text-center">
<small class="text-muted">
<strong>기본 계정:</strong><br>
관리자: admin / admin123<br>
사용자: user / user123
</small>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 알림 모달 -->
<div class="modal fade" id="alertModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">알림</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="alertMessage">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">확인</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="js/auth.js"></script>
</body>
</html>

266
frontend/js/auth.js Normal file
View File

@@ -0,0 +1,266 @@
// 인증 관련 JavaScript 함수들
const API_BASE_URL = 'http://localhost:14001/api';
// 로그인 함수
async function login(username, password) {
try {
const response = await fetch(`${API_BASE_URL}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (response.ok) {
// 토큰과 사용자 정보 저장
localStorage.setItem('token', data.token);
localStorage.setItem('user', JSON.stringify(data.user));
// 대시보드로 이동
window.location.href = 'dashboard.html';
} else {
throw new Error(data.message || '로그인에 실패했습니다.');
}
} catch (error) {
console.error('로그인 오류:', error);
showAlert('로그인 실패', error.message);
}
}
// 로그아웃 함수
function logout() {
localStorage.removeItem('token');
localStorage.removeItem('user');
window.location.href = 'index.html';
}
// 토큰 확인 함수
function checkAuth() {
const token = localStorage.getItem('token');
const user = localStorage.getItem('user');
if (!token || !user) {
// 로그인 페이지가 아닌 경우에만 리다이렉트
if (!window.location.pathname.includes('index.html') && window.location.pathname !== '/') {
window.location.href = 'index.html';
}
return null;
}
try {
return JSON.parse(user);
} catch (error) {
console.error('사용자 정보 파싱 오류:', error);
logout();
return null;
}
}
// 관리자 권한 확인
function isAdmin() {
const user = checkAuth();
return user && user.role === 'admin';
}
// API 요청 헤더에 토큰 추가
function getAuthHeaders() {
const token = localStorage.getItem('token');
return {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
};
}
// API 요청 함수
async function apiRequest(url, options = {}) {
const defaultOptions = {
headers: getAuthHeaders()
};
const mergedOptions = {
...defaultOptions,
...options,
headers: {
...defaultOptions.headers,
...options.headers
}
};
try {
const response = await fetch(`${API_BASE_URL}${url}`, mergedOptions);
// 인증 오류 처리
if (response.status === 401) {
logout();
return;
}
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || '요청 처리에 실패했습니다.');
}
return data;
} catch (error) {
console.error('API 요청 오류:', error);
throw error;
}
}
// 알림 표시 함수
function showAlert(title, message, type = 'danger') {
const alertModal = document.getElementById('alertModal');
if (alertModal) {
document.getElementById('alertMessage').innerHTML = `<strong>${title}</strong><br>${message}`;
const modal = new bootstrap.Modal(alertModal);
modal.show();
} else {
alert(`${title}: ${message}`);
}
}
// 토스트 알림 표시 함수
function showToast(message, type = 'success') {
const toastElement = document.getElementById('alertToast');
const toastMessage = document.getElementById('toastMessage');
if (toastElement && toastMessage) {
toastMessage.textContent = message;
// 토스트 색상 설정
toastElement.className = `toast ${type === 'success' ? 'bg-success' : 'bg-danger'} text-white`;
const toast = new bootstrap.Toast(toastElement);
toast.show();
}
}
// 페이지 로드 시 인증 확인
document.addEventListener('DOMContentLoaded', function() {
// 로그인 페이지가 아닌 경우 인증 확인
if (!window.location.pathname.includes('index.html') && window.location.pathname !== '/') {
const user = checkAuth();
if (user) {
// 사용자 이름 표시
const userNameDisplay = document.getElementById('userNameDisplay');
if (userNameDisplay) {
userNameDisplay.textContent = user.name;
}
// 관리자가 아닌 경우 작업자 관리 메뉴 숨기기
if (!isAdmin()) {
const workerManagementNav = document.getElementById('workerManagementNav');
const addWorkerBtn = document.getElementById('addWorkerBtn');
if (workerManagementNav) workerManagementNav.style.display = 'none';
if (addWorkerBtn) addWorkerBtn.style.display = 'none';
}
}
}
// 로그인 폼 처리
const loginForm = document.getElementById('loginForm');
if (loginForm) {
loginForm.addEventListener('submit', async function(e) {
e.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
if (!username || !password) {
showAlert('입력 오류', '아이디와 비밀번호를 모두 입력해주세요.');
return;
}
await login(username, password);
});
}
});
// 날짜 포맷 함수
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('ko-KR', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
}
// 시간 포맷 함수
function formatTime(timeString) {
if (!timeString) return '';
return timeString.substring(0, 5); // HH:MM 형식으로 자르기
}
// 직종 한글 변환
function getJobTypeText(jobType) {
const jobTypes = {
'welder': '용접사',
'plumber': '배관사'
};
return jobTypes[jobType] || jobType;
}
// 상태 한글 변환
function getStatusText(status, type = 'general') {
const statusTexts = {
general: {
'active': '활성',
'inactive': '비활성',
'pending': '대기',
'approved': '승인',
'rejected': '거부',
'completed': '완료',
'cancelled': '취소'
},
work: {
'planned': '계획',
'in_progress': '진행중',
'completed': '완료',
'cancelled': '취소'
},
error: {
'reported': '신고됨',
'investigating': '조사중',
'resolved': '해결됨',
'closed': '종료'
},
request: {
'pending': '대기',
'approved': '승인',
'ordered': '주문',
'delivered': '배송완료',
'rejected': '거부'
}
};
return statusTexts[type][status] || status;
}
// 우선순위 한글 변환
function getPriorityText(priority) {
const priorities = {
'low': '낮음',
'normal': '보통',
'high': '높음',
'urgent': '긴급'
};
return priorities[priority] || priority;
}
// 심각도 한글 변환
function getSeverityText(severity) {
const severities = {
'low': '낮음',
'medium': '보통',
'high': '높음',
'critical': '심각'
};
return severities[severity] || severity;
}

224
frontend/workers.html Normal file
View File

@@ -0,0 +1,224 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>작업관리 시스템 - 작업자 관리</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<link href="css/dashboard.css" rel="stylesheet">
</head>
<body>
<!-- 상단 네비게이션 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<a class="navbar-brand" href="dashboard.html">
<i class="bi bi-tools me-2"></i>작업관리 시스템
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="dashboard.html">
<i class="bi bi-house-door me-1"></i>대시보드
</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="workers.html">
<i class="bi bi-people me-1"></i>작업자 관리
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="daily-work.html">
<i class="bi bi-calendar-check me-1"></i>일일 작업
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="errors.html">
<i class="bi bi-exclamation-triangle me-1"></i>에러사항
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="requests.html">
<i class="bi bi-cart me-1"></i>요청사항
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="bi bi-person-circle me-1"></i><span id="userNameDisplay">사용자</span>
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="logout()">
<i class="bi bi-box-arrow-right me-2"></i>로그아웃
</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid mt-4">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-people me-2"></i>작업자 관리</h2>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#workerModal" onclick="openWorkerModal()">
<i class="bi bi-person-plus me-2"></i>작업자 추가
</button>
</div>
<!-- 필터 -->
<div class="card mb-4">
<div class="card-body">
<div class="row">
<div class="col-md-4">
<label for="jobTypeFilter" class="form-label">직종 필터</label>
<select class="form-select" id="jobTypeFilter" onchange="filterWorkers()">
<option value="">전체</option>
<option value="welder">용접사</option>
<option value="plumber">배관사</option>
</select>
</div>
<div class="col-md-4">
<label for="statusFilter" class="form-label">상태 필터</label>
<select class="form-select" id="statusFilter" onchange="filterWorkers()">
<option value="">전체</option>
<option value="active">활성</option>
<option value="inactive">비활성</option>
</select>
</div>
<div class="col-md-4">
<label for="searchInput" class="form-label">이름 검색</label>
<input type="text" class="form-control" id="searchInput" placeholder="작업자 이름 검색" oninput="filterWorkers()">
</div>
</div>
</div>
</div>
<!-- 작업자 목록 -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">작업자 목록</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-light">
<tr>
<th>이름</th>
<th>직종</th>
<th>연락처</th>
<th>입사일</th>
<th>상태</th>
<th>등록일</th>
<th>작업</th>
</tr>
</thead>
<tbody id="workersTableBody">
<tr>
<td colspan="7" class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">로딩 중...</span>
</div>
<p class="mt-2">작업자 목록을 불러오는 중...</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 작업자 추가/수정 모달 -->
<div class="modal fade" id="workerModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="workerModalTitle">작업자 추가</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="workerForm">
<div class="modal-body">
<input type="hidden" id="workerId" name="id">
<div class="mb-3">
<label for="workerName" class="form-label">이름 <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="workerName" name="name" required>
</div>
<div class="mb-3">
<label for="jobType" class="form-label">직종 <span class="text-danger">*</span></label>
<select class="form-select" id="jobType" name="job_type" required>
<option value="">직종을 선택하세요</option>
<option value="welder">용접사</option>
<option value="plumber">배관사</option>
</select>
</div>
<div class="mb-3">
<label for="workerPhone" class="form-label">연락처</label>
<input type="tel" class="form-control" id="workerPhone" name="phone" placeholder="010-1234-5678">
</div>
<div class="mb-3">
<label for="hireDate" class="form-label">입사일</label>
<input type="date" class="form-control" id="hireDate" name="hire_date">
</div>
<div class="mb-3">
<label for="workerStatus" class="form-label">상태</label>
<select class="form-select" id="workerStatus" name="status">
<option value="active">활성</option>
<option value="inactive">비활성</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
<button type="submit" class="btn btn-primary">저장</button>
</div>
</form>
</div>
</div>
</div>
<!-- 삭제 확인 모달 -->
<div class="modal fade" id="deleteModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">작업자 삭제</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>정말로 이 작업자를 삭제하시겠습니까?</p>
<p class="text-danger"><strong>주의:</strong> 삭제된 작업자의 모든 작업 기록도 함께 삭제됩니다.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
<button type="button" class="btn btn-danger" id="confirmDeleteBtn">삭제</button>
</div>
</div>
</div>
</div>
<!-- 알림 토스트 -->
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<div id="alertToast" class="toast" role="alert">
<div class="toast-header">
<strong class="me-auto">알림</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body" id="toastMessage">
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="js/auth.js"></script>
<script src="js/workers.js"></script>
</body>
</html>