feat: 페이지 구조 재구성 및 사이드바 네비게이션 구현

- 페이지 폴더 재구성: safety/, attendance/ 폴더 신규 생성
  - work/ → safety/: 이슈 신고, 출입 신청 관련 페이지 이동
  - common/ → attendance/: 근태/휴가 관련 페이지 이동
  - admin/ 정리: safety-* 파일들을 safety/로 이동

- 사이드바 네비게이션 메뉴 구현
  - 카테고리별 메뉴: 작업관리, 안전관리, 근태관리, 시스템관리
  - 접기/펼치기 기능 및 상태 저장
  - 관리자 전용 메뉴 자동 표시/숨김

- 날씨 API 연동 (기상청 단기예보)
  - TBM 및 navbar에 현재 날씨 표시
  - weatherService.js 추가

- 안전 체크리스트 확장
  - 기본/날씨별/작업별 체크 유형 추가
  - checklist-manage.html 페이지 추가

- 이슈 신고 시스템 구현
  - workIssueController, workIssueModel, workIssueRoutes 추가

- DB 마이그레이션 파일 추가 (실행 대기)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-02 14:27:22 +09:00
parent b6485e3140
commit 74d3a78aa3
116 changed files with 23117 additions and 294 deletions

View File

@@ -0,0 +1,391 @@
<!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">
<script src="/js/auth-check.js" defer></script>
<style>
/* 페이지 전용 스타일 */
.password-page {
max-width: 600px;
margin: 0 auto;
padding: 40px 20px;
}
.page-title {
text-align: center;
margin-bottom: 40px;
}
.page-title h1 {
font-size: 2rem;
color: #333;
margin-bottom: 12px;
}
.page-title p {
color: #666;
font-size: 1.1rem;
}
/* 카드 스타일 */
.password-card {
background: white;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
overflow: hidden;
}
.card-header {
background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
color: white;
padding: 24px 32px;
}
.card-header h2 {
margin: 0;
font-size: 1.5rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 12px;
}
.card-body {
padding: 32px;
}
/* 알림 박스 */
.info-box {
background: #e3f2fd;
border: 1px solid #90caf9;
border-radius: 8px;
padding: 16px;
margin-bottom: 28px;
}
.info-box h4 {
margin: 0 0 12px 0;
color: #1565c0;
font-size: 1rem;
display: flex;
align-items: center;
gap: 8px;
}
.info-box ul {
margin: 0;
padding-left: 24px;
color: #0d47a1;
}
.info-box li {
margin-bottom: 6px;
}
/* 폼 스타일 */
.password-form {
display: flex;
flex-direction: column;
gap: 24px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 10px;
}
.form-group label {
font-weight: 600;
color: #333;
font-size: 0.95rem;
display: flex;
align-items: center;
gap: 8px;
}
.input-wrapper {
position: relative;
}
.form-control {
width: 100%;
padding: 14px 48px 14px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1rem;
transition: all 0.3s ease;
font-family: inherit;
}
.form-control:focus {
outline: none;
border-color: #ff9800;
box-shadow: 0 0 0 3px rgba(255, 152, 0, 0.1);
}
.password-toggle {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
padding: 8px;
font-size: 1.2rem;
opacity: 0.6;
transition: opacity 0.2s;
}
.password-toggle:hover {
opacity: 1;
}
/* 버튼 */
.form-actions {
display: flex;
gap: 12px;
margin-top: 12px;
}
.btn {
padding: 14px 28px;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-family: inherit;
}
.btn-primary {
background: #ff9800;
color: white;
flex: 1;
}
.btn-primary:hover {
background: #f57c00;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(255, 152, 0, 0.3);
}
.btn-primary:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
}
.btn-secondary {
background: #f5f5f5;
color: #666;
border: 1px solid #e0e0e0;
}
.btn-secondary:hover {
background: #e0e0e0;
}
/* 메시지 */
.message-box {
padding: 16px;
border-radius: 8px;
margin-bottom: 20px;
animation: slideDown 0.3s ease-out;
}
.message-box.error {
background: #ffebee;
border: 1px solid #ffcdd2;
color: #c62828;
}
.message-box.success {
background: #e8f5e9;
border: 1px solid #c8e6c9;
color: #2e7d32;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 하단 링크 */
.back-link {
text-align: center;
margin-top: 32px;
padding-top: 32px;
border-top: 1px solid #e0e0e0;
}
.back-link a {
color: #1976d2;
text-decoration: none;
font-size: 0.95rem;
display: inline-flex;
align-items: center;
gap: 6px;
transition: color 0.2s;
}
.back-link a:hover {
color: #1565c0;
text-decoration: underline;
}
/* 반응형 */
@media (max-width: 768px) {
.password-page {
padding: 20px 16px;
}
.card-body {
padding: 24px;
}
.form-actions {
flex-direction: column;
}
.btn {
width: 100%;
}
}
</style>
</head>
<body>
<div class="main-layout-no-sidebar">
<div id="navbar-container"></div>
<div class="password-page">
<div class="page-title">
<h1>🔐 비밀번호 변경</h1>
<p>계정 보안을 위해 정기적으로 비밀번호를 변경해주세요</p>
</div>
<div class="password-card">
<div class="card-header">
<h2>
<span>🔑</span>
<span>새 비밀번호 설정</span>
</h2>
</div>
<div class="card-body">
<!-- 메시지 영역 -->
<div id="message-area"></div>
<!-- 안내 정보 -->
<div class="info-box">
<h4>
<span></span>
<span>비밀번호 요구사항</span>
</h4>
<ul>
<li>최소 6자 이상 입력해주세요</li>
<li>영문 대/소문자, 숫자, 특수문자를 조합하면 더 안전합니다</li>
<li>개인정보나 쉬운 단어는 피해주세요</li>
<li>이전 비밀번호와 다르게 설정해주세요</li>
</ul>
</div>
<!-- 비밀번호 변경 폼 -->
<form id="changePasswordForm" class="password-form">
<div class="form-group">
<label for="currentPassword">
<span>🔓</span>
<span>현재 비밀번호</span>
</label>
<div class="input-wrapper">
<input
type="password"
id="currentPassword"
class="form-control"
placeholder="현재 비밀번호를 입력하세요"
required
autocomplete="current-password"
/>
<button type="button" class="password-toggle" data-target="currentPassword">👁️</button>
</div>
</div>
<div class="form-group">
<label for="newPassword">
<span>🔐</span>
<span>새 비밀번호</span>
</label>
<div class="input-wrapper">
<input
type="password"
id="newPassword"
class="form-control"
placeholder="새 비밀번호를 입력하세요"
required
autocomplete="new-password"
/>
<button type="button" class="password-toggle" data-target="newPassword">👁️</button>
</div>
<div id="passwordStrength"></div>
</div>
<div class="form-group">
<label for="confirmPassword">
<span></span>
<span>새 비밀번호 확인</span>
</label>
<div class="input-wrapper">
<input
type="password"
id="confirmPassword"
class="form-control"
placeholder="새 비밀번호를 다시 입력하세요"
required
autocomplete="new-password"
/>
<button type="button" class="password-toggle" data-target="confirmPassword">👁️</button>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" id="submitBtn">
<span>🔑</span>
<span>비밀번호 변경</span>
</button>
<button type="button" class="btn btn-secondary" id="resetBtn">
초기화
</button>
</div>
</form>
<div class="back-link">
<a href="javascript:history.back()">
<span></span>
<span>이전 페이지로 돌아가기</span>
</a>
</div>
</div>
</div>
</div>
</div>
<script type="module" src="/js/load-navbar.js"></script>
<script type="module" src="/js/change-password.js"></script>
</body>
</html>