fix: 캘린더 모달 중복 카드 문제 및 삭제 권한 개선
- monthly_worker_status 조회 시 GROUP BY로 중복 데이터 합산 - 작업보고서 삭제 권한을 그룹장 이상으로 제한 (admin, system, group_leader) - 중복 데이터 정리를 위한 마이그레이션 SQL 추가 (009_fix_duplicate_monthly_status.sql) - synology_deployment 버전에도 동일 수정 적용
This commit is contained in:
@@ -40,9 +40,9 @@
|
||||
<span class="dropdown-icon">🔐</span>
|
||||
비밀번호 변경
|
||||
</a>
|
||||
<a href="/pages/profile/settings.html" class="dropdown-item">
|
||||
<a href="/pages/profile/admin-settings.html" class="dropdown-item admin-only">
|
||||
<span class="dropdown-icon">⚙️</span>
|
||||
설정
|
||||
관리자 설정
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button class="dropdown-item logout-item" id="dropdown-logout">
|
||||
@@ -56,9 +56,6 @@
|
||||
<button class="nav-btn dashboard-btn" title="대시보드">
|
||||
🏠 대시보드
|
||||
</button>
|
||||
<button class="nav-btn admin-btn" title="관리자 페이지" id="adminBtn" style="display: none;">
|
||||
⚙️ 관리자
|
||||
</button>
|
||||
<button class="nav-btn system-btn" title="시스템 관리자" id="systemBtn" style="display: none;">
|
||||
🔧 시스템
|
||||
</button>
|
||||
@@ -312,18 +309,6 @@
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.admin-btn {
|
||||
background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%);
|
||||
color: white;
|
||||
border: 1px solid rgba(255,255,255,0.3);
|
||||
box-shadow: 0 2px 8px rgba(255,107,53,0.3);
|
||||
}
|
||||
|
||||
.admin-btn:hover {
|
||||
background: linear-gradient(135deg, #ff5722 0%, #ff9800 100%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(255,107,53,0.4);
|
||||
}
|
||||
|
||||
.system-btn {
|
||||
background: linear-gradient(135deg, #9c27b0 0%, #673ab7 100%);
|
||||
|
||||
677
web-ui/css/admin-settings.css
Normal file
677
web-ui/css/admin-settings.css
Normal file
@@ -0,0 +1,677 @@
|
||||
/* admin-settings.css */
|
||||
|
||||
/* 기본 레이아웃 */
|
||||
.work-report-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.work-report-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 3rem 2rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.work-report-header h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem 0;
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.work-report-header .subtitle {
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.9;
|
||||
margin: 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.work-report-main {
|
||||
background: #f8f9fa;
|
||||
min-height: calc(100vh - 200px);
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #495057;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
margin: 0 2rem 2rem 2rem;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background: white;
|
||||
color: #007bff;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.dashboard-main {
|
||||
padding: 0 2rem 2rem 2rem;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.page-title-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: #1a1a1a;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
font-size: 2.25rem;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
font-size: 1rem;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 설정 섹션 */
|
||||
.settings-section {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem 2rem;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
/* 사용자 컨테이너 */
|
||||
.users-container {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.users-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* 검색 박스 */
|
||||
.search-box {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem 0.75rem 2.5rem;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
left: 0.75rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 1rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 필터 버튼 */
|
||||
.filter-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 2px solid #e9ecef;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.filter-btn:hover {
|
||||
border-color: #007bff;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.filter-btn.active {
|
||||
background: #007bff;
|
||||
border-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 사용자 테이블 */
|
||||
.users-table-container {
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.users-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.users-table th {
|
||||
background: #f8f9fa;
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
}
|
||||
|
||||
.users-table td {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.users-table tbody tr:hover {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
/* 사용자 정보 */
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.user-avatar-small {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.user-details h4 {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.user-details p {
|
||||
margin: 0;
|
||||
font-size: 0.8rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 역할 배지 */
|
||||
.role-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.role-badge.admin {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.role-badge.leader {
|
||||
background: #fd7e14;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.role-badge.user {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 상태 배지 */
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-badge.active {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status-badge.inactive {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
/* 액션 버튼 */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 0.4rem 0.8rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.action-btn.edit {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.edit:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
|
||||
.action-btn.delete {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.delete:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.action-btn.toggle {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.toggle:hover {
|
||||
background: #545b62;
|
||||
}
|
||||
|
||||
/* 빈 상태 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 3rem 2rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
font-size: 0.9rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 모달 스타일 */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 25px rgba(0, 0, 0, 0.1);
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-container.small {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem 2rem;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.modal-close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.modal-close-btn:hover {
|
||||
background: #f8f9fa;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
padding: 1.5rem 2rem;
|
||||
border-top: 1px solid #e9ecef;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
/* 폼 스타일 */
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
|
||||
}
|
||||
|
||||
.form-help {
|
||||
display: block;
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.8rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 삭제 경고 */
|
||||
.delete-warning {
|
||||
text-align: center;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.delete-warning p {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1rem;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
font-size: 0.9rem;
|
||||
color: #dc3545;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 버튼 스타일 */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #0056b3;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #545b62;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* 토스트 알림 */
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 1100;
|
||||
}
|
||||
|
||||
.toast {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
padding: 1rem 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
min-width: 300px;
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
|
||||
.toast.success {
|
||||
border-left: 4px solid #28a745;
|
||||
}
|
||||
|
||||
.toast.error {
|
||||
border-left: 4px solid #dc3545;
|
||||
}
|
||||
|
||||
.toast.warning {
|
||||
border-left: 4px solid #ffc107;
|
||||
}
|
||||
|
||||
.toast-icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.toast-message {
|
||||
flex: 1;
|
||||
font-size: 0.9rem;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.toast-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.25rem;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 1024px) {
|
||||
.dashboard-main {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.users-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.dashboard-main {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.users-table-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.users-table {
|
||||
min-width: 600px;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
width: 95%;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.filter-buttons {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,62 @@
|
||||
/* Common CSS - 공통 스타일 */
|
||||
|
||||
/* ========== 통일된 헤더 스타일 ========== */
|
||||
.work-report-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.work-report-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 3rem 2rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.work-report-header h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem 0;
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.work-report-header .subtitle {
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.9;
|
||||
margin: 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.work-report-main {
|
||||
background: #f8f9fa;
|
||||
min-height: calc(100vh - 200px);
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #495057;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
margin: 0 2rem 2rem 2rem;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background: white;
|
||||
color: #007bff;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
/* Reset and Base Styles */
|
||||
* {
|
||||
margin: 0;
|
||||
@@ -28,6 +85,42 @@ h4 { font-size: 1.125rem; }
|
||||
h5 { font-size: 1rem; }
|
||||
h6 { font-size: 0.875rem; }
|
||||
|
||||
/* ========== 헤더 액션 버튼 ========== */
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.dashboard-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.6rem 1.2rem;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.dashboard-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.dashboard-btn .btn-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -40,6 +40,46 @@
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.dashboard-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.6rem 1.2rem;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.dashboard-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.dashboard-btn .btn-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
@@ -497,3 +497,116 @@ body {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 빠른 액세스 섹션 ========== */
|
||||
.quick-access-section {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.quick-actions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 1rem;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.quick-action-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 1.5rem 1rem;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border: 2px solid rgba(59, 130, 246, 0.1);
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||
backdrop-filter: blur(10px);
|
||||
min-height: 120px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.quick-action-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(59, 130, 246, 0.15);
|
||||
border-color: rgba(59, 130, 246, 0.3);
|
||||
background: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.quick-icon {
|
||||
font-size: 2rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.quick-text {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ========== 관리 섹션 ========== */
|
||||
.management-section {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
/* ========== 시스템 상태 섹션 ========== */
|
||||
.system-status-section {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.status-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.status-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
font-size: 2rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.status-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
534
web-ui/js/admin-settings.js
Normal file
534
web-ui/js/admin-settings.js
Normal file
@@ -0,0 +1,534 @@
|
||||
// admin-settings.js - 관리자 설정 페이지
|
||||
|
||||
// 전역 변수
|
||||
let currentUser = null;
|
||||
let users = [];
|
||||
let filteredUsers = [];
|
||||
let currentEditingUser = null;
|
||||
|
||||
// DOM 요소
|
||||
const elements = {
|
||||
// 시간
|
||||
timeValue: document.getElementById('timeValue'),
|
||||
|
||||
// 사용자 정보
|
||||
userName: document.getElementById('userName'),
|
||||
userRole: document.getElementById('userRole'),
|
||||
userInitial: document.getElementById('userInitial'),
|
||||
|
||||
// 검색 및 필터
|
||||
userSearch: document.getElementById('userSearch'),
|
||||
filterButtons: document.querySelectorAll('.filter-btn'),
|
||||
|
||||
// 테이블
|
||||
usersTableBody: document.getElementById('usersTableBody'),
|
||||
emptyState: document.getElementById('emptyState'),
|
||||
|
||||
// 버튼
|
||||
addUserBtn: document.getElementById('addUserBtn'),
|
||||
saveUserBtn: document.getElementById('saveUserBtn'),
|
||||
confirmDeleteBtn: document.getElementById('confirmDeleteBtn'),
|
||||
|
||||
// 모달
|
||||
userModal: document.getElementById('userModal'),
|
||||
deleteModal: document.getElementById('deleteModal'),
|
||||
modalTitle: document.getElementById('modalTitle'),
|
||||
|
||||
// 폼
|
||||
userForm: document.getElementById('userForm'),
|
||||
userNameInput: document.getElementById('userName'),
|
||||
userIdInput: document.getElementById('userId'),
|
||||
userPasswordInput: document.getElementById('userPassword'),
|
||||
userRoleSelect: document.getElementById('userRole'),
|
||||
userEmailInput: document.getElementById('userEmail'),
|
||||
userPhoneInput: document.getElementById('userPhone'),
|
||||
passwordGroup: document.getElementById('passwordGroup'),
|
||||
|
||||
// 토스트
|
||||
toastContainer: document.getElementById('toastContainer')
|
||||
};
|
||||
|
||||
// ========== 초기화 ========== //
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
console.log('🔧 관리자 설정 페이지 초기화 시작');
|
||||
|
||||
try {
|
||||
await initializePage();
|
||||
console.log('✅ 관리자 설정 페이지 초기화 완료');
|
||||
} catch (error) {
|
||||
console.error('❌ 페이지 초기화 오류:', error);
|
||||
showToast('페이지를 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
});
|
||||
|
||||
async function initializePage() {
|
||||
// 이벤트 리스너 설정
|
||||
setupEventListeners();
|
||||
|
||||
// 사용자 목록 로드
|
||||
await loadUsers();
|
||||
}
|
||||
|
||||
// ========== 사용자 정보 설정 ========== //
|
||||
function setupUserInfo() {
|
||||
const authData = getAuthData();
|
||||
if (authData && authData.user) {
|
||||
currentUser = authData.user;
|
||||
|
||||
// 사용자 이름 설정
|
||||
if (elements.userName) {
|
||||
elements.userName.textContent = currentUser.name || currentUser.username;
|
||||
}
|
||||
|
||||
// 사용자 역할 설정
|
||||
const roleMap = {
|
||||
'admin': '관리자',
|
||||
'system': '시스템 관리자',
|
||||
'leader': '그룹장',
|
||||
'user': '작업자'
|
||||
};
|
||||
if (elements.userRole) {
|
||||
elements.userRole.textContent = roleMap[currentUser.role] || '작업자';
|
||||
}
|
||||
|
||||
// 아바타 초기값 설정
|
||||
if (elements.userInitial) {
|
||||
const initial = (currentUser.name || currentUser.username).charAt(0);
|
||||
elements.userInitial.textContent = initial;
|
||||
}
|
||||
|
||||
console.log('👤 사용자 정보 설정 완료:', currentUser.name);
|
||||
}
|
||||
}
|
||||
|
||||
function getAuthData() {
|
||||
const token = localStorage.getItem('token');
|
||||
const user = localStorage.getItem('user');
|
||||
return {
|
||||
token,
|
||||
user: user ? JSON.parse(user) : null
|
||||
};
|
||||
}
|
||||
|
||||
// ========== 시간 업데이트 ========== //
|
||||
function updateCurrentTime() {
|
||||
const now = new Date();
|
||||
const timeString = now.toLocaleTimeString('ko-KR', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
if (elements.timeValue) {
|
||||
elements.timeValue.textContent = timeString;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 이벤트 리스너 ========== //
|
||||
function setupEventListeners() {
|
||||
// 검색
|
||||
if (elements.userSearch) {
|
||||
elements.userSearch.addEventListener('input', handleSearch);
|
||||
}
|
||||
|
||||
// 필터 버튼
|
||||
elements.filterButtons.forEach(btn => {
|
||||
btn.addEventListener('click', handleFilter);
|
||||
});
|
||||
|
||||
// 사용자 추가 버튼
|
||||
if (elements.addUserBtn) {
|
||||
elements.addUserBtn.addEventListener('click', openAddUserModal);
|
||||
}
|
||||
|
||||
// 사용자 저장 버튼
|
||||
if (elements.saveUserBtn) {
|
||||
elements.saveUserBtn.addEventListener('click', saveUser);
|
||||
}
|
||||
|
||||
// 삭제 확인 버튼
|
||||
if (elements.confirmDeleteBtn) {
|
||||
elements.confirmDeleteBtn.addEventListener('click', confirmDeleteUser);
|
||||
}
|
||||
|
||||
// 로그아웃 버튼
|
||||
const logoutBtn = document.getElementById('logoutBtn');
|
||||
if (logoutBtn) {
|
||||
logoutBtn.addEventListener('click', handleLogout);
|
||||
}
|
||||
|
||||
// 프로필 드롭다운
|
||||
const userProfile = document.getElementById('userProfile');
|
||||
const profileMenu = document.getElementById('profileMenu');
|
||||
if (userProfile && profileMenu) {
|
||||
userProfile.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
profileMenu.style.display = profileMenu.style.display === 'block' ? 'none' : 'block';
|
||||
});
|
||||
|
||||
document.addEventListener('click', () => {
|
||||
profileMenu.style.display = 'none';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 사용자 관리 ========== //
|
||||
async function loadUsers() {
|
||||
try {
|
||||
console.log('👥 사용자 목록 로딩...');
|
||||
|
||||
// 실제 API에서 사용자 데이터 가져오기
|
||||
const response = await window.apiCall('/users');
|
||||
users = Array.isArray(response) ? response : (response.data || []);
|
||||
|
||||
console.log(`✅ 사용자 ${users.length}명 로드 완료`);
|
||||
|
||||
// 필터링된 사용자 목록 초기화
|
||||
filteredUsers = [...users];
|
||||
|
||||
// 테이블 렌더링
|
||||
renderUsersTable();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 사용자 목록 로딩 오류:', error);
|
||||
showToast('사용자 목록을 불러오는 중 오류가 발생했습니다.', 'error');
|
||||
users = [];
|
||||
filteredUsers = [];
|
||||
renderUsersTable();
|
||||
}
|
||||
}
|
||||
|
||||
function renderUsersTable() {
|
||||
if (!elements.usersTableBody) return;
|
||||
|
||||
if (filteredUsers.length === 0) {
|
||||
elements.usersTableBody.innerHTML = '';
|
||||
if (elements.emptyState) {
|
||||
elements.emptyState.style.display = 'block';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (elements.emptyState) {
|
||||
elements.emptyState.style.display = 'none';
|
||||
}
|
||||
|
||||
elements.usersTableBody.innerHTML = filteredUsers.map(user => `
|
||||
<tr>
|
||||
<td>
|
||||
<div class="user-info">
|
||||
<div class="user-avatar-small">${(user.name || user.username).charAt(0)}</div>
|
||||
<div class="user-details">
|
||||
<h4>${user.name || user.username}</h4>
|
||||
<p>${user.email || '이메일 없음'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><strong>${user.username}</strong></td>
|
||||
<td>
|
||||
<span class="role-badge ${user.role}">
|
||||
${getRoleIcon(user.role)} ${getRoleName(user.role)}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="status-badge ${user.is_active ? 'active' : 'inactive'}">
|
||||
${user.is_active ? '활성' : '비활성'}
|
||||
</span>
|
||||
</td>
|
||||
<td>${formatDate(user.last_login) || '로그인 기록 없음'}</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<button class="action-btn edit" onclick="editUser(${user.user_id})">
|
||||
수정
|
||||
</button>
|
||||
<button class="action-btn toggle" onclick="toggleUserStatus(${user.user_id})">
|
||||
${user.is_active ? '비활성화' : '활성화'}
|
||||
</button>
|
||||
<button class="action-btn delete" onclick="deleteUser(${user.user_id})">
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function getRoleIcon(role) {
|
||||
const icons = {
|
||||
admin: '👑',
|
||||
leader: '👨💼',
|
||||
user: '👤'
|
||||
};
|
||||
return icons[role] || '👤';
|
||||
}
|
||||
|
||||
function getRoleName(role) {
|
||||
const names = {
|
||||
admin: '관리자',
|
||||
leader: '그룹장',
|
||||
user: '작업자'
|
||||
};
|
||||
return names[role] || '작업자';
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
if (!dateString) return null;
|
||||
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('ko-KR', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
// ========== 검색 및 필터링 ========== //
|
||||
function handleSearch(e) {
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
|
||||
filteredUsers = users.filter(user => {
|
||||
return (user.name && user.name.toLowerCase().includes(searchTerm)) ||
|
||||
(user.username && user.username.toLowerCase().includes(searchTerm)) ||
|
||||
(user.email && user.email.toLowerCase().includes(searchTerm));
|
||||
});
|
||||
|
||||
renderUsersTable();
|
||||
}
|
||||
|
||||
function handleFilter(e) {
|
||||
const filterType = e.target.dataset.filter;
|
||||
|
||||
// 활성 버튼 변경
|
||||
elements.filterButtons.forEach(btn => btn.classList.remove('active'));
|
||||
e.target.classList.add('active');
|
||||
|
||||
// 필터링
|
||||
if (filterType === 'all') {
|
||||
filteredUsers = [...users];
|
||||
} else {
|
||||
filteredUsers = users.filter(user => user.role === filterType);
|
||||
}
|
||||
|
||||
renderUsersTable();
|
||||
}
|
||||
|
||||
// ========== 모달 관리 ========== //
|
||||
function openAddUserModal() {
|
||||
currentEditingUser = null;
|
||||
|
||||
if (elements.modalTitle) {
|
||||
elements.modalTitle.textContent = '새 사용자 추가';
|
||||
}
|
||||
|
||||
// 폼 초기화
|
||||
if (elements.userForm) {
|
||||
elements.userForm.reset();
|
||||
}
|
||||
|
||||
// 비밀번호 필드 표시
|
||||
if (elements.passwordGroup) {
|
||||
elements.passwordGroup.style.display = 'block';
|
||||
}
|
||||
|
||||
if (elements.userPasswordInput) {
|
||||
elements.userPasswordInput.required = true;
|
||||
}
|
||||
|
||||
if (elements.userModal) {
|
||||
elements.userModal.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
function editUser(userId) {
|
||||
const user = users.find(u => u.user_id === userId);
|
||||
if (!user) return;
|
||||
|
||||
currentEditingUser = user;
|
||||
|
||||
if (elements.modalTitle) {
|
||||
elements.modalTitle.textContent = '사용자 정보 수정';
|
||||
}
|
||||
|
||||
// 폼에 데이터 채우기
|
||||
if (elements.userNameInput) elements.userNameInput.value = user.name || '';
|
||||
if (elements.userIdInput) elements.userIdInput.value = user.username || '';
|
||||
if (elements.userRoleSelect) elements.userRoleSelect.value = user.role || '';
|
||||
if (elements.userEmailInput) elements.userEmailInput.value = user.email || '';
|
||||
if (elements.userPhoneInput) elements.userPhoneInput.value = user.phone || '';
|
||||
|
||||
// 비밀번호 필드 숨기기 (수정 시에는 선택사항)
|
||||
if (elements.passwordGroup) {
|
||||
elements.passwordGroup.style.display = 'none';
|
||||
}
|
||||
|
||||
if (elements.userPasswordInput) {
|
||||
elements.userPasswordInput.required = false;
|
||||
}
|
||||
|
||||
if (elements.userModal) {
|
||||
elements.userModal.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
function closeUserModal() {
|
||||
if (elements.userModal) {
|
||||
elements.userModal.style.display = 'none';
|
||||
}
|
||||
currentEditingUser = null;
|
||||
}
|
||||
|
||||
function deleteUser(userId) {
|
||||
const user = users.find(u => u.user_id === userId);
|
||||
if (!user) return;
|
||||
|
||||
currentEditingUser = user;
|
||||
|
||||
if (elements.deleteModal) {
|
||||
elements.deleteModal.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
function closeDeleteModal() {
|
||||
if (elements.deleteModal) {
|
||||
elements.deleteModal.style.display = 'none';
|
||||
}
|
||||
currentEditingUser = null;
|
||||
}
|
||||
|
||||
// ========== 사용자 CRUD ========== //
|
||||
async function saveUser() {
|
||||
try {
|
||||
const formData = {
|
||||
name: elements.userNameInput?.value,
|
||||
username: elements.userIdInput?.value,
|
||||
role: elements.userRoleSelect?.value,
|
||||
email: elements.userEmailInput?.value,
|
||||
phone: elements.userPhoneInput?.value
|
||||
};
|
||||
|
||||
// 유효성 검사
|
||||
if (!formData.name || !formData.username || !formData.role) {
|
||||
showToast('필수 항목을 모두 입력해주세요.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 비밀번호 처리
|
||||
if (!currentEditingUser && elements.userPasswordInput?.value) {
|
||||
formData.password = elements.userPasswordInput.value;
|
||||
} else if (currentEditingUser && elements.userPasswordInput?.value) {
|
||||
formData.password = elements.userPasswordInput.value;
|
||||
}
|
||||
|
||||
let response;
|
||||
if (currentEditingUser) {
|
||||
// 수정
|
||||
response = await window.apiCall(`/users/${currentEditingUser.user_id}`, 'PUT', formData);
|
||||
} else {
|
||||
// 생성
|
||||
response = await window.apiCall('/users', 'POST', formData);
|
||||
}
|
||||
|
||||
if (response.success || response.user_id) {
|
||||
const action = currentEditingUser ? '수정' : '생성';
|
||||
showToast(`사용자가 성공적으로 ${action}되었습니다.`, 'success');
|
||||
|
||||
closeUserModal();
|
||||
await loadUsers();
|
||||
} else {
|
||||
throw new Error(response.message || '사용자 저장에 실패했습니다.');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('사용자 저장 오류:', error);
|
||||
showToast(`사용자 저장 중 오류가 발생했습니다: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDeleteUser() {
|
||||
if (!currentEditingUser) return;
|
||||
|
||||
try {
|
||||
const response = await window.apiCall(`/users/${currentEditingUser.user_id}`, 'DELETE');
|
||||
|
||||
if (response.success) {
|
||||
showToast('사용자가 성공적으로 삭제되었습니다.', 'success');
|
||||
closeDeleteModal();
|
||||
await loadUsers();
|
||||
} else {
|
||||
throw new Error(response.message || '사용자 삭제에 실패했습니다.');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('사용자 삭제 오류:', error);
|
||||
showToast(`사용자 삭제 중 오류가 발생했습니다: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleUserStatus(userId) {
|
||||
try {
|
||||
const user = users.find(u => u.user_id === userId);
|
||||
if (!user) return;
|
||||
|
||||
const newStatus = !user.is_active;
|
||||
const response = await window.apiCall(`/users/${userId}/status`, 'PUT', { is_active: newStatus });
|
||||
|
||||
if (response.success) {
|
||||
const action = newStatus ? '활성화' : '비활성화';
|
||||
showToast(`사용자가 성공적으로 ${action}되었습니다.`, 'success');
|
||||
await loadUsers();
|
||||
} else {
|
||||
throw new Error(response.message || '사용자 상태 변경에 실패했습니다.');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('사용자 상태 변경 오류:', error);
|
||||
showToast(`사용자 상태 변경 중 오류가 발생했습니다: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 로그아웃 ========== //
|
||||
function handleLogout() {
|
||||
if (confirm('로그아웃하시겠습니까?')) {
|
||||
localStorage.clear();
|
||||
window.location.href = '/index.html';
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 토스트 알림 ========== //
|
||||
function showToast(message, type = 'info', duration = 3000) {
|
||||
if (!elements.toastContainer) return;
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast ${type}`;
|
||||
|
||||
const iconMap = {
|
||||
success: '✅',
|
||||
error: '❌',
|
||||
warning: '⚠️',
|
||||
info: 'ℹ️'
|
||||
};
|
||||
|
||||
toast.innerHTML = `
|
||||
<div class="toast-icon">${iconMap[type] || 'ℹ️'}</div>
|
||||
<div class="toast-message">${message}</div>
|
||||
<button class="toast-close" onclick="this.parentElement.remove()">×</button>
|
||||
`;
|
||||
|
||||
elements.toastContainer.appendChild(toast);
|
||||
|
||||
// 자동 제거
|
||||
setTimeout(() => {
|
||||
if (toast.parentElement) {
|
||||
toast.remove();
|
||||
}
|
||||
}, duration);
|
||||
}
|
||||
|
||||
// ========== 전역 함수 (HTML에서 호출) ========== //
|
||||
window.editUser = editUser;
|
||||
window.deleteUser = deleteUser;
|
||||
window.toggleUserStatus = toggleUserStatus;
|
||||
window.closeUserModal = closeUserModal;
|
||||
window.closeDeleteModal = closeDeleteModal;
|
||||
@@ -25,7 +25,7 @@ function filterMenuByRole(doc, userRole) {
|
||||
|
||||
selectors.forEach(({ role, selector }) => {
|
||||
// 사용자가 해당 역할을 가지고 있지 않으면 메뉴 항목을 제거
|
||||
if (userRole !== role) {
|
||||
if (userRole !== role && userRole !== 'system') { // system 권한도 admin 메뉴 접근 가능
|
||||
doc.querySelectorAll(selector).forEach(el => el.remove());
|
||||
}
|
||||
});
|
||||
@@ -56,11 +56,7 @@ function populateUserInfo(doc, user) {
|
||||
const dropdownIdEl = doc.getElementById('dropdown-user-id');
|
||||
if (dropdownIdEl) dropdownIdEl.textContent = `@${user.username}`;
|
||||
|
||||
// Admin 버튼 표시 여부 결정 (admin 권한만)
|
||||
const adminBtn = doc.getElementById('adminBtn');
|
||||
if (adminBtn && user.role === 'admin') {
|
||||
adminBtn.style.display = 'flex';
|
||||
}
|
||||
// Admin 버튼 제거됨
|
||||
|
||||
// System 버튼 표시 여부 결정 (system 권한만)
|
||||
const systemBtn = doc.getElementById('systemBtn');
|
||||
@@ -96,13 +92,7 @@ function setupNavbarEvents() {
|
||||
});
|
||||
}
|
||||
|
||||
// Admin 버튼 클릭 이벤트
|
||||
const adminButton = document.getElementById('adminBtn');
|
||||
if (adminButton) {
|
||||
adminButton.addEventListener('click', () => {
|
||||
window.location.href = '/pages/dashboard/admin.html';
|
||||
});
|
||||
}
|
||||
// Admin 버튼 제거됨
|
||||
|
||||
// System 버튼 클릭 이벤트
|
||||
const systemButton = document.getElementById('systemBtn');
|
||||
@@ -111,6 +101,14 @@ function setupNavbarEvents() {
|
||||
window.location.href = '/pages/dashboard/system.html';
|
||||
});
|
||||
}
|
||||
|
||||
// Dashboard 버튼 클릭 이벤트
|
||||
const dashboardButton = document.querySelector('.dashboard-btn');
|
||||
if (dashboardButton) {
|
||||
dashboardButton.addEventListener('click', () => {
|
||||
window.location.href = '/pages/dashboard/group-leader.html';
|
||||
});
|
||||
}
|
||||
|
||||
// 외부 클릭 시 드롭다운 닫기
|
||||
document.addEventListener('click', (e) => {
|
||||
|
||||
@@ -407,7 +407,6 @@ function displayWorkStatus() {
|
||||
<span class="legend-item legend-vacation">휴가</span>
|
||||
<span class="legend-item legend-partial">부분입력</span>
|
||||
<span class="legend-item legend-incomplete">미입력</span>
|
||||
<span class="legend-item legend-error">오류</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="worker-status-rows">
|
||||
@@ -621,11 +620,43 @@ function updateViewButtons(activeView) {
|
||||
// ========== 관리자 권한 확인 ========== //
|
||||
function checkAdminAccess() {
|
||||
const adminElements = document.querySelectorAll('.admin-only');
|
||||
const isAdmin = currentUser && ['admin', 'system'].includes(currentUser.access_level);
|
||||
const isFullAdmin = currentUser && ['admin', 'system'].includes(currentUser.access_level);
|
||||
const isGroupLeader = currentUser && currentUser.access_level === 'group_leader';
|
||||
|
||||
console.log(`🔐 권한 확인: 사용자=${currentUser?.username}, 역할=${currentUser.access_level}, 전체관리자=${isFullAdmin}, 그룹리더=${isGroupLeader}`);
|
||||
|
||||
adminElements.forEach(element => {
|
||||
if (isAdmin) {
|
||||
element.classList.add('visible');
|
||||
const href = element.getAttribute('href');
|
||||
|
||||
// 작업 분석: 전체 관리자만 접근 가능
|
||||
if (href && href.includes('work-analysis.html')) {
|
||||
if (isFullAdmin) {
|
||||
element.style.display = '';
|
||||
element.classList.add('visible');
|
||||
} else {
|
||||
element.style.display = 'none';
|
||||
element.classList.remove('visible');
|
||||
}
|
||||
}
|
||||
// 작업 관리: 전체 관리자 + 그룹 리더 접근 가능
|
||||
else if (href && href.includes('work-management.html')) {
|
||||
if (isFullAdmin || isGroupLeader) {
|
||||
element.style.display = '';
|
||||
element.classList.add('visible');
|
||||
} else {
|
||||
element.style.display = 'none';
|
||||
element.classList.remove('visible');
|
||||
}
|
||||
}
|
||||
// 기타 관리자 전용 메뉴: 전체 관리자만 접근 가능
|
||||
else {
|
||||
if (isFullAdmin) {
|
||||
element.style.display = '';
|
||||
element.classList.add('visible');
|
||||
} else {
|
||||
element.style.display = 'none';
|
||||
element.classList.remove('visible');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
initializePage();
|
||||
loadStatistics();
|
||||
loadRecentActivity();
|
||||
});
|
||||
|
||||
// 페이지 초기화
|
||||
@@ -186,63 +185,7 @@ function updateStatDisplay(elementId, value) {
|
||||
}
|
||||
}
|
||||
|
||||
// 최근 활동 로드
|
||||
async function loadRecentActivity() {
|
||||
try {
|
||||
console.log('📋 최근 활동 로딩 시작');
|
||||
|
||||
// 임시 데이터 (실제로는 API에서 가져와야 함)
|
||||
const activities = [
|
||||
{
|
||||
type: 'project',
|
||||
icon: '📁',
|
||||
title: '효성화학 에틸렌 탱크 건설공사 프로젝트가 수정되었습니다',
|
||||
user: '김두수',
|
||||
time: '2시간 전'
|
||||
},
|
||||
{
|
||||
type: 'worker',
|
||||
icon: '👥',
|
||||
title: '새로운 작업자가 등록되었습니다',
|
||||
user: '관리자',
|
||||
time: '1일 전'
|
||||
},
|
||||
{
|
||||
type: 'task',
|
||||
icon: '📋',
|
||||
title: '작업 유형이 업데이트되었습니다',
|
||||
user: '김두수',
|
||||
time: '2일 전'
|
||||
}
|
||||
];
|
||||
|
||||
renderActivityList(activities);
|
||||
|
||||
} catch (error) {
|
||||
console.error('최근 활동 로딩 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 활동 목록 렌더링
|
||||
function renderActivityList(activities) {
|
||||
const activityList = document.getElementById('activityList');
|
||||
if (!activityList) return;
|
||||
|
||||
const activitiesHtml = activities.map(activity => `
|
||||
<div class="activity-item">
|
||||
<div class="activity-icon">${activity.icon}</div>
|
||||
<div class="activity-content">
|
||||
<div class="activity-title">${activity.title}</div>
|
||||
<div class="activity-meta">
|
||||
<span class="activity-user">${activity.user}</span>
|
||||
<span class="activity-time">${activity.time}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
activityList.innerHTML = activitiesHtml;
|
||||
}
|
||||
// 최근 활동 관련 함수들 제거됨
|
||||
|
||||
// 페이지 네비게이션
|
||||
function navigateToPage(url) {
|
||||
@@ -317,4 +260,3 @@ function showToast(message, type = 'info') {
|
||||
|
||||
// 전역 함수로 노출
|
||||
window.navigateToPage = navigateToPage;
|
||||
window.loadRecentActivity = loadRecentActivity;
|
||||
|
||||
@@ -14,7 +14,15 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 네비게이션 바 -->
|
||||
<div id="navbar-container"></div>
|
||||
|
||||
<div class="analysis-container">
|
||||
<!-- 뒤로가기 버튼 -->
|
||||
<a href="javascript:history.back()" class="back-button" style="margin: 1rem 0;">
|
||||
← 뒤로가기
|
||||
</a>
|
||||
|
||||
<!-- 페이지 헤더 -->
|
||||
<header class="page-header fade-in">
|
||||
<h1 class="page-title">
|
||||
@@ -297,18 +305,6 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- 대시보드 이동 버튼 (고정) -->
|
||||
<div style="position: fixed; top: 20px; right: 20px; z-index: 1000;">
|
||||
<a href="/pages/dashboard/group-leader.html"
|
||||
style="display: inline-flex; align-items: center; gap: 8px; padding: 12px 20px;
|
||||
background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%);
|
||||
color: white; text-decoration: none; border-radius: 12px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
font-weight: 600; transition: all 0.2s ease;">
|
||||
<span>📊</span>
|
||||
<span>대시보드</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script src="/js/work-analysis.js?v=4"></script>
|
||||
@@ -2898,5 +2894,6 @@
|
||||
// 초기 모드 설정
|
||||
window.currentAnalysisMode = 'period';
|
||||
</script>
|
||||
<script src="/js/load-navbar.js?v=4"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>작업 현황 확인 - TK 건설</title>
|
||||
<link rel="stylesheet" href="/css/common.css?v=13">
|
||||
<link rel="stylesheet" href="/css/modern-dashboard.css?v=13">
|
||||
<link rel="stylesheet" href="/css/modern-dashboard.css?v=14">
|
||||
<link rel="stylesheet" href="/css/work-report-calendar.css?v=29">
|
||||
</head>
|
||||
<body>
|
||||
@@ -31,10 +31,10 @@
|
||||
|
||||
<div class="header-right">
|
||||
<div class="header-actions">
|
||||
<a href="/pages/dashboard/group-leader.html" class="dashboard-btn" title="대시보드로 이동">
|
||||
<span class="btn-icon">📊</span>
|
||||
<span class="btn-text">대시보드</span>
|
||||
</a>
|
||||
<button class="btn btn-secondary dashboard-btn" onclick="window.location.href='/pages/dashboard/group-leader.html'">
|
||||
<span class="btn-icon">🏠</span>
|
||||
대시보드
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="user-profile" id="userProfile">
|
||||
@@ -54,10 +54,6 @@
|
||||
<span class="menu-icon">🔐</span>
|
||||
비밀번호 변경
|
||||
</a>
|
||||
<a href="/pages/dashboard/group-leader.html" class="menu-item">
|
||||
<span class="menu-icon">📊</span>
|
||||
대시보드
|
||||
</a>
|
||||
<button class="menu-item logout-btn" id="logoutBtn">
|
||||
<span class="menu-icon">🚪</span>
|
||||
로그아웃
|
||||
@@ -340,7 +336,7 @@
|
||||
<!-- JavaScript -->
|
||||
<script src="/js/api-config.js?v=13"></script>
|
||||
<script src="/js/auth-check.js?v=13"></script>
|
||||
<script src="/js/load-navbar.js?v=13"></script>
|
||||
<script src="/js/load-navbar.js?v=4"></script>
|
||||
<script src="/js/work-report-calendar.js?v=41"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -172,7 +172,7 @@
|
||||
|
||||
<!-- 스크립트 -->
|
||||
<script src="/js/api-config.js?v=2"></script>
|
||||
<script src="/js/load-navbar.js?v=2"></script>
|
||||
<script src="/js/load-navbar.js?v=3"></script>
|
||||
<script src="/js/daily-work-report.js?v=10"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,34 +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">
|
||||
<!-- ✅ auth-check를 가장 먼저 로딩 -->
|
||||
<script src="/js/auth-check.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="main-layout">
|
||||
<!-- ✅ ID 통일: navbar-container -->
|
||||
<div id="navbar-container"></div>
|
||||
|
||||
<div class="content-wrapper">
|
||||
<div id="sidebar-container"></div>
|
||||
|
||||
<main id="admin-sections">
|
||||
<div class="loading">페이지를 불러오는 중...</div>
|
||||
</main>
|
||||
</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/load-sections.js"></script>
|
||||
<!-- ✅ admin.js는 다른 모듈들이 로딩된 후 실행되도록 순서 조정 -->
|
||||
<script type="module" src="/js/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -13,7 +13,7 @@
|
||||
<!-- 스크립트 (순서 중요: api-config.js가 먼저 로드되어야 함) -->
|
||||
<script src="/js/api-config.js"></script>
|
||||
<script src="/js/auth-check.js" defer></script>
|
||||
<script src="/js/modern-dashboard.js?v=3" defer></script>
|
||||
<script src="/js/modern-dashboard.js?v=10" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 메인 컨테이너 -->
|
||||
@@ -57,6 +57,10 @@
|
||||
<span class="menu-icon">🔐</span>
|
||||
비밀번호 변경
|
||||
</a>
|
||||
<a href="/pages/profile/admin-settings.html" class="menu-item admin-only">
|
||||
<span class="menu-icon">⚙️</span>
|
||||
관리자 설정
|
||||
</a>
|
||||
<button class="menu-item logout-btn" id="logoutBtn">
|
||||
<span class="menu-icon">🚪</span>
|
||||
로그아웃
|
||||
@@ -96,7 +100,7 @@
|
||||
<div class="action-arrow">→</div>
|
||||
</a>
|
||||
|
||||
<a href="/pages/analysis/work-analysis.html" class="quick-action-card">
|
||||
<a href="/pages/analysis/work-analysis.html" class="quick-action-card admin-only">
|
||||
<div class="action-icon-large">📈</div>
|
||||
<div class="action-content">
|
||||
<h3>작업 분석</h3>
|
||||
|
||||
@@ -11,58 +11,24 @@
|
||||
<script src="/js/api-config.js?v=1" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="work-report-container">
|
||||
<!-- 네비게이션 바 -->
|
||||
<div id="navbar-container"></div>
|
||||
|
||||
<!-- 헤더 -->
|
||||
<header class="dashboard-header">
|
||||
<div class="header-left">
|
||||
<div class="logo-section">
|
||||
<img src="/img/logo.png" alt="테크니컬코리아" class="logo">
|
||||
<div class="company-info">
|
||||
<h1 class="company-name">테크니컬코리아</h1>
|
||||
<p class="company-subtitle">코드 관리</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-center">
|
||||
<div class="current-time">
|
||||
<span class="time-label">현재 시각</span>
|
||||
<span class="time-value" id="timeValue">--:--:--</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<div class="header-actions">
|
||||
<a href="/pages/management/work-management.html" class="back-btn" title="작업 관리로 돌아가기">
|
||||
<span class="btn-icon">←</span>
|
||||
<span class="btn-text">작업 관리</span>
|
||||
</a>
|
||||
<a href="/pages/dashboard/group-leader.html" class="dashboard-btn" title="대시보드로 이동">
|
||||
<span class="btn-icon">📊</span>
|
||||
<span class="btn-text">대시보드</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="user-profile" id="userProfile">
|
||||
<div class="user-avatar">
|
||||
<span class="avatar-text" id="userInitial">사</span>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<span class="user-name" id="userName">사용자</span>
|
||||
<span class="user-role" id="userRole">작업자</span>
|
||||
</div>
|
||||
|
||||
<div class="profile-dropdown" id="profileMenu" style="display: none;">
|
||||
<button class="dropdown-item logout-btn" id="logoutBtn">
|
||||
<span class="dropdown-icon">🚪</span>
|
||||
로그아웃
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<header class="work-report-header">
|
||||
<h1>🏷️ 코드 관리</h1>
|
||||
<p class="subtitle">작업 상태, 오류 유형, 작업 유형 등 시스템에서 사용하는 코드를 관리합니다</p>
|
||||
</header>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main class="dashboard-main">
|
||||
<main class="work-report-main">
|
||||
<!-- 뒤로가기 버튼 -->
|
||||
<a href="/pages/management/work-management.html" class="back-button">
|
||||
← 작업관리로 돌아가기
|
||||
</a>
|
||||
|
||||
<div class="dashboard-main">
|
||||
<div class="page-header">
|
||||
<div class="page-title-section">
|
||||
<h1 class="page-title">
|
||||
@@ -282,8 +248,11 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/load-navbar.js?v=4"></script>
|
||||
<script src="/js/code-management.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -10,64 +10,24 @@
|
||||
<script src="/js/auth-check.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="work-report-container">
|
||||
<!-- 네비게이션 바 -->
|
||||
<div id="navbar-container"></div>
|
||||
|
||||
<!-- 헤더 -->
|
||||
<header class="dashboard-header">
|
||||
<div class="header-left">
|
||||
<div class="logo-section">
|
||||
<img src="/img/logo.png" alt="테크니컬코리아" class="logo">
|
||||
<div class="company-info">
|
||||
<h1 class="company-name">테크니컬코리아</h1>
|
||||
<span class="company-subtitle">생산팀 포털</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-center">
|
||||
<div class="current-time" id="currentTime">
|
||||
<span class="time-label">현재 시간</span>
|
||||
<span class="time-value" id="timeValue">--:--:--</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<div class="header-actions">
|
||||
<a href="/pages/management/work-management.html" class="back-btn" title="작업관리로 돌아가기">
|
||||
<span class="btn-icon">←</span>
|
||||
<span class="btn-text">작업관리</span>
|
||||
</a>
|
||||
<a href="/pages/dashboard/group-leader.html" class="dashboard-btn" title="대시보드로 이동">
|
||||
<span class="btn-icon">📊</span>
|
||||
<span class="btn-text">대시보드</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="user-profile" id="userProfile">
|
||||
<div class="user-avatar" id="userInitial">사</div>
|
||||
<div class="user-info">
|
||||
<span class="user-name" id="userName">사용자</span>
|
||||
<span class="user-role" id="userRole">작업자</span>
|
||||
</div>
|
||||
<div class="profile-dropdown" id="profileMenu" style="display: none;">
|
||||
<a href="/pages/profile/my-profile.html" class="dropdown-item">
|
||||
<span class="dropdown-icon">👤</span>
|
||||
내 프로필
|
||||
</a>
|
||||
<a href="/pages/profile/settings.html" class="dropdown-item">
|
||||
<span class="dropdown-icon">⚙️</span>
|
||||
설정
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button class="dropdown-item logout-btn" id="logoutBtn">
|
||||
<span class="dropdown-icon">🚪</span>
|
||||
로그아웃
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<header class="work-report-header">
|
||||
<h1>📁 프로젝트 관리</h1>
|
||||
<p class="subtitle">프로젝트 등록, 수정, 삭제 및 기본 정보를 관리합니다</p>
|
||||
</header>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main class="dashboard-main">
|
||||
<main class="work-report-main">
|
||||
<!-- 뒤로가기 버튼 -->
|
||||
<a href="/pages/management/work-management.html" class="back-button">
|
||||
← 작업관리로 돌아가기
|
||||
</a>
|
||||
|
||||
<div class="dashboard-main">
|
||||
<div class="page-header">
|
||||
<div class="page-title-section">
|
||||
<h1 class="page-title">
|
||||
@@ -242,10 +202,13 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script src="/js/api-config.js?v=13"></script>
|
||||
<script src="/js/load-navbar.js?v=4"></script>
|
||||
<script src="/js/project-management.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,77 +5,56 @@
|
||||
<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/work-management.css?v=1">
|
||||
<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>
|
||||
|
||||
<!-- 헤더 -->
|
||||
<header class="dashboard-header">
|
||||
<div class="header-left">
|
||||
<div class="logo-section">
|
||||
<img src="/img/logo.png" alt="테크니컬코리아" class="logo">
|
||||
<div class="company-info">
|
||||
<h1 class="company-name">테크니컬코리아</h1>
|
||||
<span class="company-subtitle">생산팀 포털</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-center">
|
||||
<div class="current-time" id="currentTime">
|
||||
<span class="time-label">현재 시간</span>
|
||||
<span class="time-value" id="timeValue">--:--:--</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<div class="header-actions">
|
||||
<a href="/pages/dashboard/group-leader.html" class="dashboard-btn" title="대시보드로 이동">
|
||||
<span class="btn-icon">📊</span>
|
||||
<span class="btn-text">대시보드</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="user-profile" id="userProfile">
|
||||
<div class="user-avatar" id="userInitial">사</div>
|
||||
<div class="user-info">
|
||||
<span class="user-name" id="userName">사용자</span>
|
||||
<span class="user-role" id="userRole">작업자</span>
|
||||
</div>
|
||||
<div class="profile-dropdown" id="profileMenu" style="display: none;">
|
||||
<a href="/pages/profile/my-profile.html" class="dropdown-item">
|
||||
<span class="dropdown-icon">👤</span>
|
||||
내 프로필
|
||||
</a>
|
||||
<a href="/pages/profile/settings.html" class="dropdown-item">
|
||||
<span class="dropdown-icon">⚙️</span>
|
||||
설정
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button class="dropdown-item logout-btn" id="logoutBtn">
|
||||
<span class="dropdown-icon">🚪</span>
|
||||
로그아웃
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<header class="work-report-header">
|
||||
<h1>🔧 작업 관리</h1>
|
||||
<p class="subtitle">프로젝트, 작업자, 작업 유형 등 기본 데이터를 관리합니다</p>
|
||||
</header>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main 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>
|
||||
<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/management/project-management.html')">
|
||||
<span class="quick-icon">📁</span>
|
||||
<span class="quick-text">새 프로젝트</span>
|
||||
</button>
|
||||
<button class="quick-action-btn" onclick="navigateToPage('/pages/management/worker-management.html')">
|
||||
<span class="quick-icon">👤</span>
|
||||
<span class="quick-text">작업자 등록</span>
|
||||
</button>
|
||||
<button class="quick-action-btn" onclick="navigateToPage('/pages/management/code-management.html')">
|
||||
<span class="quick-icon">🏷️</span>
|
||||
<span class="quick-text">코드 설정</span>
|
||||
</button>
|
||||
<button class="quick-action-btn" onclick="navigateToPage('/pages/analysis/work-analysis.html')">
|
||||
<span class="quick-icon">📊</span>
|
||||
<span class="quick-text">작업 분석</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 관리 메뉴 카드들 -->
|
||||
<div class="management-grid">
|
||||
<div class="management-section">
|
||||
<h2 class="section-title">🔧 관리 메뉴</h2>
|
||||
<div class="management-grid">
|
||||
<!-- 프로젝트 관리 -->
|
||||
<div class="management-card" onclick="navigateToPage('/pages/management/project-management.html')">
|
||||
<div class="card-header">
|
||||
@@ -136,46 +115,50 @@
|
||||
<span class="card-action">관리하기 →</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 최근 활동 -->
|
||||
<div class="recent-activity-section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">최근 관리 활동</h2>
|
||||
<button class="refresh-btn" onclick="loadRecentActivity()">
|
||||
<span class="refresh-icon">🔄</span>
|
||||
새로고침
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="activity-list" id="activityList">
|
||||
<div class="activity-item">
|
||||
<div class="activity-icon">📁</div>
|
||||
<div class="activity-content">
|
||||
<div class="activity-title">효성화학 에틸렌 탱크 건설공사 프로젝트가 수정되었습니다</div>
|
||||
<div class="activity-meta">
|
||||
<span class="activity-user">김두수</span>
|
||||
<span class="activity-time">2시간 전</span>
|
||||
</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="activity-item">
|
||||
<div class="activity-icon">👥</div>
|
||||
<div class="activity-content">
|
||||
<div class="activity-title">새로운 작업자가 등록되었습니다</div>
|
||||
<div class="activity-meta">
|
||||
<span class="activity-user">관리자</span>
|
||||
<span class="activity-time">1일 전</span>
|
||||
</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 src="/js/api-config.js?v=13"></script>
|
||||
<script src="/js/work-management.js?v=1"></script>
|
||||
<script src="/js/load-navbar.js?v=4"></script>
|
||||
<script src="/js/work-management.js?v=2"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -11,58 +11,24 @@
|
||||
<script src="/js/api-config.js?v=1" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="work-report-container">
|
||||
<!-- 네비게이션 바 -->
|
||||
<div id="navbar-container"></div>
|
||||
|
||||
<!-- 헤더 -->
|
||||
<header class="dashboard-header">
|
||||
<div class="header-left">
|
||||
<div class="logo-section">
|
||||
<img src="/img/logo.png" alt="테크니컬코리아" class="logo">
|
||||
<div class="company-info">
|
||||
<h1 class="company-name">테크니컬코리아</h1>
|
||||
<p class="company-subtitle">작업자 관리</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-center">
|
||||
<div class="current-time">
|
||||
<span class="time-label">현재 시각</span>
|
||||
<span class="time-value" id="timeValue">--:--:--</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<div class="header-actions">
|
||||
<a href="/pages/management/work-management.html" class="back-btn" title="작업 관리로 돌아가기">
|
||||
<span class="btn-icon">←</span>
|
||||
<span class="btn-text">작업 관리</span>
|
||||
</a>
|
||||
<a href="/pages/dashboard/group-leader.html" class="dashboard-btn" title="대시보드로 이동">
|
||||
<span class="btn-icon">📊</span>
|
||||
<span class="btn-text">대시보드</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="user-profile" id="userProfile">
|
||||
<div class="user-avatar">
|
||||
<span class="avatar-text" id="userInitial">사</span>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<span class="user-name" id="userName">사용자</span>
|
||||
<span class="user-role" id="userRole">작업자</span>
|
||||
</div>
|
||||
|
||||
<div class="profile-dropdown" id="profileMenu" style="display: none;">
|
||||
<button class="dropdown-item logout-btn" id="logoutBtn">
|
||||
<span class="dropdown-icon">🚪</span>
|
||||
로그아웃
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<header class="work-report-header">
|
||||
<h1>👥 작업자 관리</h1>
|
||||
<p class="subtitle">작업자 등록, 수정, 삭제 및 기본 정보를 관리합니다</p>
|
||||
</header>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main class="dashboard-main">
|
||||
<main class="work-report-main">
|
||||
<!-- 뒤로가기 버튼 -->
|
||||
<a href="/pages/management/work-management.html" class="back-button">
|
||||
← 작업관리로 돌아가기
|
||||
</a>
|
||||
|
||||
<div class="dashboard-main">
|
||||
<div class="page-header">
|
||||
<div class="page-title-section">
|
||||
<h1 class="page-title">
|
||||
@@ -225,8 +191,11 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/load-navbar.js?v=4"></script>
|
||||
<script src="/js/worker-management.js?v=3"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
184
web-ui/pages/profile/admin-settings.html
Normal file
184
web-ui/pages/profile/admin-settings.html
Normal file
@@ -0,0 +1,184 @@
|
||||
<!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>
|
||||
|
||||
<!-- 헤더 -->
|
||||
<header class="work-report-header">
|
||||
<h1>⚙️ 관리자 설정</h1>
|
||||
<p class="subtitle">시스템 사용자 계정 및 권한을 관리합니다</p>
|
||||
</header>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main class="work-report-main">
|
||||
<!-- 뒤로가기 버튼 -->
|
||||
<a href="javascript:history.back()" 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>
|
||||
|
||||
<!-- 사용자 관리 섹션 -->
|
||||
<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 src="/js/api-config.js?v=13"></script>
|
||||
<script src="/js/load-navbar.js?v=4"></script>
|
||||
<script src="/js/admin-settings.js?v=5"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user