1635 lines
66 KiB
HTML
1635 lines
66 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>안전회의록 작성</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Malgun Gothic', sans-serif;
|
|
line-height: 1.6;
|
|
color: #333;
|
|
background: #f5f5f5;
|
|
padding: 20px;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
background: white;
|
|
border-radius: 10px;
|
|
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.header {
|
|
background: linear-gradient(135deg, #d4653a 0%, #b8956a 100%);
|
|
color: white;
|
|
padding: 30px;
|
|
text-align: center;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.header::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -50%;
|
|
left: -50%;
|
|
width: 200%;
|
|
height: 200%;
|
|
background: repeating-linear-gradient(
|
|
45deg,
|
|
transparent,
|
|
transparent 10px,
|
|
rgba(255,255,255,0.05) 10px,
|
|
rgba(255,255,255,0.05) 20px
|
|
);
|
|
animation: headerPattern 20s linear infinite;
|
|
}
|
|
|
|
@keyframes headerPattern {
|
|
0% { transform: translateX(-100px) translateY(-100px); }
|
|
100% { transform: translateX(0) translateY(0); }
|
|
}
|
|
|
|
.header h1 {
|
|
font-size: 2.2em;
|
|
margin-bottom: 10px;
|
|
font-weight: 600;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.header p {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.main-content {
|
|
padding: 30px;
|
|
}
|
|
|
|
.section-selector {
|
|
display: flex;
|
|
margin-bottom: 30px;
|
|
background: #f8f9fa;
|
|
border-radius: 8px;
|
|
padding: 8px;
|
|
gap: 8px;
|
|
}
|
|
|
|
.section-btn {
|
|
flex: 1;
|
|
padding: 12px 20px;
|
|
border: none;
|
|
background: transparent;
|
|
color: #6c757d;
|
|
font-weight: 600;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.section-btn.active {
|
|
background: linear-gradient(135deg, #d4653a 0%, #b8956a 100%);
|
|
color: white;
|
|
box-shadow: 0 2px 8px rgba(212, 101, 58, 0.3);
|
|
}
|
|
|
|
.section-content {
|
|
display: none;
|
|
}
|
|
|
|
.section-content.active {
|
|
display: block;
|
|
}
|
|
|
|
.form-section {
|
|
background: #fff;
|
|
}
|
|
|
|
.meeting-info {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 20px;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.form-row {
|
|
display: grid;
|
|
grid-template-columns: 1fr 2fr;
|
|
gap: 15px;
|
|
align-items: center;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
label {
|
|
font-weight: 600;
|
|
color: #555;
|
|
text-align: right;
|
|
padding-right: 10px;
|
|
}
|
|
|
|
input, textarea, select {
|
|
width: 100%;
|
|
padding: 10px;
|
|
border: 2px solid #e1e1e1;
|
|
border-radius: 5px;
|
|
font-size: 14px;
|
|
transition: border-color 0.3s ease;
|
|
}
|
|
|
|
input:focus, textarea:focus, select:focus {
|
|
outline: none;
|
|
border-color: #d4653a;
|
|
box-shadow: 0 0 0 3px rgba(212, 101, 58, 0.15);
|
|
}
|
|
|
|
.section {
|
|
margin-bottom: 30px;
|
|
border: 1px solid #e1e1e1;
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.section-title {
|
|
background: linear-gradient(135deg, #faf7f4 0%, #f9f5f1 100%);
|
|
border-bottom: 3px solid #d4653a;
|
|
padding: 15px 20px;
|
|
font-weight: 600;
|
|
color: #495057;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.section-body {
|
|
padding: 20px;
|
|
}
|
|
|
|
.agenda-items {
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.agenda-item {
|
|
background: #f8f9fa;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 8px;
|
|
margin-bottom: 15px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.agenda-header {
|
|
background: linear-gradient(135deg, #f5f1ed 0%, #f7f3ef 100%);
|
|
border-bottom: 2px solid #d4653a;
|
|
padding: 10px 15px;
|
|
font-weight: 600;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.agenda-content {
|
|
padding: 15px;
|
|
}
|
|
|
|
.agenda-fields {
|
|
display: grid;
|
|
gap: 15px;
|
|
}
|
|
|
|
.field-row {
|
|
display: grid;
|
|
grid-template-columns: 100px 1fr;
|
|
gap: 10px;
|
|
align-items: start;
|
|
}
|
|
|
|
.field-label {
|
|
font-weight: 600;
|
|
color: #555;
|
|
padding-top: 8px;
|
|
}
|
|
|
|
.btn {
|
|
background: linear-gradient(135deg, #d4653a 0%, #b8956a 100%);
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 24px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
}
|
|
|
|
.btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(212, 101, 58, 0.3);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: linear-gradient(135deg, #6c757d 0%, #495057 100%);
|
|
margin-right: 10px;
|
|
}
|
|
|
|
.btn-danger {
|
|
background: linear-gradient(135deg, #dc3545 0%, #bd2130 100%);
|
|
padding: 8px 16px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.btn-warning {
|
|
background: linear-gradient(135deg, #ffc107 0%, #e0a800 100%);
|
|
color: #212529;
|
|
padding: 6px 12px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.btn-success {
|
|
background: linear-gradient(135deg, #28a745 0%, #1e7e34 100%);
|
|
padding: 6px 12px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.btn-info {
|
|
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
|
|
padding: 6px 12px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.btn-small {
|
|
padding: 6px 12px;
|
|
font-size: 12px;
|
|
margin: 2px;
|
|
}
|
|
|
|
.data-controls {
|
|
background: linear-gradient(135deg, #faf7f4 0%, #f9f5f1 100%);
|
|
border: 2px solid #d4653a;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.actions {
|
|
text-align: center;
|
|
padding: 30px;
|
|
background: #f8f9fa;
|
|
border-top: 1px solid #e1e1e1;
|
|
}
|
|
|
|
.output {
|
|
margin-top: 30px;
|
|
padding: 20px;
|
|
background: #f8f9fa;
|
|
border-radius: 8px;
|
|
border: 1px solid #e1e1e1;
|
|
}
|
|
|
|
.output h3 {
|
|
margin-bottom: 15px;
|
|
color: #495057;
|
|
}
|
|
|
|
.output pre {
|
|
background: white;
|
|
padding: 15px;
|
|
border-radius: 5px;
|
|
border: 1px solid #dee2e6;
|
|
font-size: 12px;
|
|
overflow-x: auto;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
/* 인수인계 스타일 */
|
|
.handover-table {
|
|
background: white;
|
|
border-radius: 8px;
|
|
overflow-x: auto;
|
|
border: 1px solid #dee2e6;
|
|
}
|
|
|
|
.handover-group {
|
|
border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
|
|
.handover-group:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.handover-category {
|
|
background: linear-gradient(135deg, #faf7f4 0%, #f9f5f1 100%);
|
|
border-bottom: 2px solid #d4653a;
|
|
padding: 15px 20px;
|
|
font-weight: 600;
|
|
color: #495057;
|
|
}
|
|
|
|
.handover-items {
|
|
padding: 0;
|
|
}
|
|
|
|
.handover-item {
|
|
padding: 20px;
|
|
border-bottom: 1px solid #f8f9fa;
|
|
display: grid;
|
|
grid-template-columns: 2fr 1fr;
|
|
gap: 30px;
|
|
align-items: start;
|
|
}
|
|
|
|
.handover-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.item-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.item-task {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: #495057;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.item-details {
|
|
display: flex;
|
|
gap: 20px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.item-detail {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 13px;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.current-assignee {
|
|
background: linear-gradient(135deg, #d4653a 0%, #b8956a 100%);
|
|
color: white;
|
|
padding: 4px 12px;
|
|
border-radius: 12px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.handover-control {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.new-assignee-input {
|
|
padding: 10px;
|
|
border: 2px solid #dee2e6;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.new-assignee-input:focus {
|
|
border-color: #d4653a;
|
|
outline: none;
|
|
box-shadow: 0 0 0 3px rgba(212, 101, 58, 0.15);
|
|
}
|
|
|
|
/* 기존 회의록 스타일 */
|
|
.past-item {
|
|
background: white;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin-bottom: 15px;
|
|
position: relative;
|
|
}
|
|
|
|
.past-item.completed {
|
|
background: #f8f9fa;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.past-item-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.past-item-meta {
|
|
font-size: 12px;
|
|
color: #6c757d;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.past-item-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.past-item h5 {
|
|
color: #495057;
|
|
margin-bottom: 12px;
|
|
font-size: 16px;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.past-item-content {
|
|
font-size: 14px;
|
|
line-height: 1.4;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.past-item-deadline {
|
|
background: linear-gradient(135deg, #fef7e0 0%, #fdf2d9 100%);
|
|
border: 2px solid #d4653a;
|
|
padding: 8px 12px;
|
|
border-radius: 6px;
|
|
font-size: 13px;
|
|
color: #8b5a2b;
|
|
margin-top: 10px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.filter-controls {
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.filter-input {
|
|
flex: 1;
|
|
min-width: 200px;
|
|
padding: 8px;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.reference-stats {
|
|
background: linear-gradient(135deg, #faf7f4 0%, #f9f5f1 100%);
|
|
border: 2px solid #d4653a;
|
|
padding: 15px;
|
|
border-radius: 6px;
|
|
margin-bottom: 20px;
|
|
font-size: 14px;
|
|
color: #a0522d;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* 모달 스타일 */
|
|
.modal {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0,0,0,0.5);
|
|
z-index: 1000;
|
|
}
|
|
|
|
.modal-content {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
background: white;
|
|
border-radius: 10px;
|
|
padding: 30px;
|
|
width: 90%;
|
|
max-width: 600px;
|
|
max-height: 80vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.modal-header {
|
|
margin-bottom: 20px;
|
|
padding-bottom: 15px;
|
|
border-bottom: 2px solid #dee2e6;
|
|
}
|
|
|
|
.modal-header h3 {
|
|
color: #495057;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.modal-body {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.modal-actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 10px;
|
|
padding-top: 15px;
|
|
border-top: 1px solid #dee2e6;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.handover-item {
|
|
grid-template-columns: 1fr;
|
|
gap: 15px;
|
|
}
|
|
|
|
.item-details {
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.form-row, .field-row {
|
|
grid-template-columns: 1fr;
|
|
gap: 5px;
|
|
}
|
|
|
|
label, .field-label {
|
|
text-align: left;
|
|
padding-right: 0;
|
|
}
|
|
|
|
.meeting-info {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.section-selector {
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="header">
|
|
<h1>🏭 월간 안전회의록</h1>
|
|
<p>테크니컬코리아 안전회의록 작성 및 관리 시스템</p>
|
|
</div>
|
|
|
|
<div class="main-content">
|
|
<!-- 섹션 선택 버튼 -->
|
|
<div class="section-selector">
|
|
<button class="section-btn active" onclick="showSection('handover')">📝 업무 인수인계</button>
|
|
<button class="section-btn" onclick="showSection('past')">📋 기존 회의록</button>
|
|
<button class="section-btn" onclick="showSection('inspection')">🔍 정기점검</button>
|
|
<button class="section-btn" onclick="showSection('meeting')">✏️ 회의록 작성</button>
|
|
</div>
|
|
|
|
<!-- 업무 인수인계 섹션 -->
|
|
<div id="handover-section" class="section-content active">
|
|
<div class="section">
|
|
<div class="section-title">
|
|
하주현 선임 → 업무 인수인계
|
|
<span style="font-size: 14px; color: #6c757d;">출산/육아휴직 대비</span>
|
|
</div>
|
|
<div class="section-body">
|
|
<div class="data-controls">
|
|
<button class="btn btn-info" onclick="resetHandoverData()">🔄 인수인계 초기화</button>
|
|
<button class="btn btn-secondary" onclick="exportHandoverData()">📁 인수인계 데이터 내보내기</button>
|
|
<input type="file" id="handoverFileInput" accept=".json" style="display: none;" onchange="importHandoverData(event)">
|
|
<button class="btn btn-secondary" onclick="document.getElementById('handoverFileInput').click()">📂 인수인계 데이터 가져오기</button>
|
|
</div>
|
|
<div id="handoverContainer" class="handover-table">
|
|
<!-- 인수인계 항목들이 여기에 표시됩니다 -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 기존 회의록 섹션 -->
|
|
<div id="past-section" class="section-content">
|
|
<div class="section">
|
|
<div class="section-title">기존 회의록 참조 (미완료 항목)</div>
|
|
<div class="section-body">
|
|
<div class="data-controls">
|
|
<button class="btn btn-info" onclick="resetMeetingData()">🔄 회의록 데이터 초기화</button>
|
|
<button class="btn btn-secondary" onclick="exportMeetingData()">📁 회의록 데이터 내보내기</button>
|
|
<input type="file" id="meetingFileInput" accept=".json" style="display: none;" onchange="importMeetingData(event)">
|
|
<button class="btn btn-secondary" onclick="document.getElementById('meetingFileInput').click()">📂 회의록 데이터 가져오기</button>
|
|
<button class="btn btn-warning" onclick="showCompletedItems()">✅ 완료된 항목 보기</button>
|
|
</div>
|
|
|
|
<div class="reference-stats">
|
|
<strong>미완료 항목 <span id="totalPastItems">0</span>개</strong> |
|
|
<span id="filteredPastItems">0</span>개 표시됨 |
|
|
<strong>완료된 항목 <span id="completedPastItems">0</span>개</strong>
|
|
</div>
|
|
|
|
<div class="filter-controls">
|
|
<input type="text" id="pastSearchFilter" class="filter-input" placeholder="검색어 입력" onkeyup="filterPastItems()">
|
|
<select id="pastStatusFilter" class="filter-input" style="flex: none; width: 150px;" onchange="filterPastItems()">
|
|
<option value="">전체 상태</option>
|
|
<option value="진행중">진행중</option>
|
|
<option value="보류">보류</option>
|
|
<option value="미결">미결</option>
|
|
<option value="완료">완료</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div id="pastItems">
|
|
<!-- 기존 회의록 항목들이 여기에 표시됩니다 -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 정기점검 섹션 -->
|
|
<div id="inspection-section" class="section-content">
|
|
<div class="section">
|
|
<div class="section-title">정기점검 사항</div>
|
|
<div class="section-body">
|
|
<div class="data-controls">
|
|
<button class="btn btn-info" onclick="resetInspectionData()">🔄 점검 데이터 초기화</button>
|
|
<button class="btn btn-secondary" onclick="exportInspectionData()">📁 점검 데이터 내보내기</button>
|
|
<input type="file" id="inspectionFileInput" accept=".json" style="display: none;" onchange="importInspectionData(event)">
|
|
<button class="btn btn-secondary" onclick="document.getElementById('inspectionFileInput').click()">📂 점검 데이터 가져오기</button>
|
|
</div>
|
|
<div class="reference-stats">
|
|
<strong>정기점검 사항</strong> |
|
|
<span id="urgentCount">0</span>개 긴급,
|
|
<span id="pendingCount">0</span>개 예정
|
|
</div>
|
|
<div class="filter-controls">
|
|
<input type="text" id="inspectionSearchFilter" class="filter-input" placeholder="점검 항목 검색" onkeyup="filterInspectionItems()">
|
|
<select id="inspectionStatusFilter" class="filter-input" style="flex: none; width: 150px;" onchange="filterInspectionItems()">
|
|
<option value="">전체</option>
|
|
<option value="완료">완료</option>
|
|
<option value="예정">예정</option>
|
|
<option value="진행">진행</option>
|
|
</select>
|
|
</div>
|
|
<div id="inspectionItems">
|
|
<!-- 정기점검 항목들이 여기에 표시됩니다 -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 회의록 작성 섹션 -->
|
|
<div id="meeting-section" class="section-content">
|
|
<!-- 회의 기본정보 -->
|
|
<div class="section">
|
|
<div class="section-title">회의 기본정보</div>
|
|
<div class="section-body">
|
|
<div class="meeting-info">
|
|
<div class="form-row">
|
|
<label>회의년도:</label>
|
|
<input type="number" id="meetingYear" value="2025" min="2020" max="2030">
|
|
</div>
|
|
<div class="form-row">
|
|
<label>회의월:</label>
|
|
<select id="meetingMonth">
|
|
<option value="">선택</option>
|
|
<option value="1">1월</option>
|
|
<option value="2">2월</option>
|
|
<option value="3">3월</option>
|
|
<option value="4">4월</option>
|
|
<option value="5">5월</option>
|
|
<option value="6">6월</option>
|
|
<option value="7">7월</option>
|
|
<option value="8">8월</option>
|
|
<option value="9">9월</option>
|
|
<option value="10">10월</option>
|
|
<option value="11">11월</option>
|
|
<option value="12">12월</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-row">
|
|
<label>회의일자:</label>
|
|
<input type="date" id="meetingDate">
|
|
</div>
|
|
<div class="form-row">
|
|
<label>참석자:</label>
|
|
<input type="text" id="attendees" placeholder="참석자 명단">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 논의사항 -->
|
|
<div class="section">
|
|
<div class="section-title">
|
|
논의사항
|
|
<button class="btn btn-secondary" onclick="addAgendaItem()">+ 항목 추가</button>
|
|
</div>
|
|
<div class="section-body">
|
|
<div id="agendaItems" class="agenda-items">
|
|
<!-- 논의사항 항목들이 여기에 추가됩니다 -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="actions">
|
|
<button class="btn" onclick="generateOutput()">회의록 생성</button>
|
|
<button class="btn btn-secondary" onclick="clearForm()">양식 초기화</button>
|
|
<button class="btn btn-secondary" onclick="saveTemplate()">템플릿 저장</button>
|
|
</div>
|
|
|
|
<div id="output" class="output" style="display: none;">
|
|
<h3>생성된 회의록</h3>
|
|
<pre id="outputContent"></pre>
|
|
<button class="btn" onclick="copyToClipboard()">클립보드에 복사</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 편집 모달 -->
|
|
<div id="editModal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3>회의 안건 수정</h3>
|
|
<p style="color: #6c757d; font-size: 14px;">내용을 수정하고 확인 버튼을 눌러주세요.</p>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="form-row">
|
|
<label>논의사항:</label>
|
|
<input type="text" id="editTopic" style="grid-column: 1 / -1;">
|
|
</div>
|
|
<div class="form-row">
|
|
<label>해결방법:</label>
|
|
<textarea id="editSolution" rows="3" style="grid-column: 1 / -1;"></textarea>
|
|
</div>
|
|
<div class="form-row">
|
|
<label>담당자:</label>
|
|
<input type="text" id="editAssignee" style="grid-column: 1 / -1;">
|
|
</div>
|
|
<div class="form-row">
|
|
<label>상세내용:</label>
|
|
<textarea id="editDetails" rows="2" style="grid-column: 1 / -1;"></textarea>
|
|
</div>
|
|
<div class="form-row">
|
|
<label>마감일자:</label>
|
|
<input type="date" id="editDeadline" style="grid-column: 1 / -1;">
|
|
</div>
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button class="btn btn-secondary" onclick="closeEditModal()">취소</button>
|
|
<button class="btn" onclick="confirmEdit()">확인</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let agendaCounter = 0;
|
|
let existingItems = [];
|
|
let handoverData = [];
|
|
let inspectionData = [];
|
|
let currentEditIndex = -1;
|
|
let showingCompleted = false;
|
|
|
|
// 로컬 스토리지 키
|
|
const STORAGE_KEYS = {
|
|
existingItems: 'safety_meeting_existing_items',
|
|
handoverData: 'safety_meeting_handover_data',
|
|
inspectionData: 'safety_meeting_inspection_data'
|
|
};
|
|
|
|
// 데이터 저장 함수들
|
|
function saveToStorage(key, data) {
|
|
try {
|
|
localStorage.setItem(key, JSON.stringify(data));
|
|
} catch (e) {
|
|
console.error('저장 실패:', e);
|
|
alert('데이터 저장에 실패했습니다.');
|
|
}
|
|
}
|
|
|
|
function loadFromStorage(key, defaultData = []) {
|
|
try {
|
|
const stored = localStorage.getItem(key);
|
|
return stored ? JSON.parse(stored) : defaultData;
|
|
} catch (e) {
|
|
console.error('로드 실패:', e);
|
|
return defaultData;
|
|
}
|
|
}
|
|
|
|
// 현재 날짜를 기본값으로 설정
|
|
document.getElementById('meetingDate').value = new Date().toISOString().split('T')[0];
|
|
document.getElementById('meetingMonth').value = new Date().getMonth() + 1;
|
|
|
|
// 섹션 전환
|
|
function showSection(sectionName) {
|
|
// 모든 버튼과 섹션 비활성화
|
|
document.querySelectorAll('.section-btn').forEach(btn => btn.classList.remove('active'));
|
|
document.querySelectorAll('.section-content').forEach(content => content.classList.remove('active'));
|
|
|
|
// 선택된 섹션 활성화
|
|
event.target.classList.add('active');
|
|
document.getElementById(`${sectionName}-section`).classList.add('active');
|
|
}
|
|
|
|
// 데이터 초기화
|
|
function initializeData() {
|
|
// 저장된 데이터 로드 또는 기본 데이터 사용
|
|
existingItems = loadFromStorage(STORAGE_KEYS.existingItems, getDefaultExistingItems());
|
|
handoverData = loadFromStorage(STORAGE_KEYS.handoverData, getDefaultHandoverData());
|
|
inspectionData = loadFromStorage(STORAGE_KEYS.inspectionData, getDefaultInspectionData());
|
|
|
|
displayPastItems();
|
|
displayHandoverItems();
|
|
displayInspectionItems();
|
|
}
|
|
|
|
// 기본 데이터 함수들
|
|
function getDefaultHandoverData() {
|
|
return [
|
|
{
|
|
category: "안전회의",
|
|
items: [
|
|
{ task: "일정 공유", method: "메일", frequency: "1회/매달", assignee: "하주현" },
|
|
{ task: "안전회의록 작성 및 지난달 안전회의록 인쇄 및 서명", method: "-", frequency: "1회/매달", assignee: "하주현" },
|
|
{ task: "내용 공유", method: "메일", frequency: "1회/매달", assignee: "하주현" },
|
|
{ task: "자료 저장 (서버: [공융 드라이브] - [테크니컬코리아] - [07.안전회의])", method: "서버", frequency: "1회/매달", assignee: "하주현" }
|
|
]
|
|
},
|
|
{
|
|
category: "동절기 전열기 관리",
|
|
items: [
|
|
{ task: "담당자 지정 및 전열기 전원 확인", method: "-", frequency: "매주/동절기", assignee: "하주현" }
|
|
]
|
|
},
|
|
{
|
|
category: "소화기 점검",
|
|
items: [
|
|
{ task: "점검일지 작성", method: "-", frequency: "1회/매달", assignee: "신민기 (=신상균)" },
|
|
{ task: "점검일지 자료 저장 (서버: [공융 드라이브] - [테크니컬코리아] - [02.구매물류팀] - [소방안전 박창원,하주현] - [소화기 점검일지])", method: "서버", frequency: "1회/매달", assignee: "하주현" }
|
|
]
|
|
},
|
|
{
|
|
category: "건강검진",
|
|
items: [
|
|
{ task: "제휴 병원 (화성디에스) 건강검진 버스 일정 확인", method: "", frequency: "1회/매년", assignee: "이예린" },
|
|
{ task: "일정 공유", method: "메일", frequency: "1회/매년", assignee: "하주현" },
|
|
{ task: "건강검진 실시확인서 (직장제출용) 수집", method: "메일", frequency: "1회/매년", assignee: "하주현" },
|
|
{ task: "건강검진 실시확인서 (직장제출용) 보관", method: "", frequency: "1회/매년", assignee: "안현기" }
|
|
]
|
|
},
|
|
{
|
|
category: "법정의무교육",
|
|
items: [
|
|
{ task: "일정 공유", method: "메일", frequency: "1회 / 상,하반기", assignee: "이예린" },
|
|
{ task: "교육 미이수자 Follow-up", method: "메일", frequency: "1회 / 상,하반기", assignee: "하주현" }
|
|
]
|
|
}
|
|
];
|
|
}
|
|
|
|
function getDefaultInspectionData() {
|
|
return [
|
|
{ no: 1, item: "소화기 안전 점검 (한달에 한번)", frequency: "1회/ 1개월", status: "확인 완료", assignee: "신상균" },
|
|
{ no: 2, item: "소방 관련 센서 점검", frequency: "1회/ 1년", status: "완료 확인 필요", assignee: "임영규" },
|
|
{ no: 5, item: "전기 관련 Panel 전기 점검", frequency: "1회 / 3년", status: "25년도 6월 이내 예정", assignee: "박창원" },
|
|
{ no: 9, item: "SUS작업장 호이스트 정기 점검", frequency: "1회 / 2년", status: "25년 진행 예정", assignee: "안경희" },
|
|
{ no: 10, item: "작업 환경 측정 Vendor", frequency: "1회/ 6개월", status: "25년 진행 예정", assignee: "박창원" },
|
|
{ no: 15, item: "안전관리감독자 교육", frequency: "1회/ 1년", status: "25년 진행 예정", assignee: "김두수" },
|
|
{ no: 20, item: "사내 보안 SK", frequency: "상시", status: "25년 진행 예정", assignee: "안경희" },
|
|
{ no: 21, item: "중대재해법", frequency: "상시", status: "매월 4~10월 진행 예정", assignee: "안경희" }
|
|
];
|
|
}
|
|
|
|
function getDefaultExistingItems() {
|
|
return [
|
|
// 3월
|
|
{ month: "3월", no: 1, topic: "산업안전공단 클린사업장 사업 (불꽃감지기) 진행", solution: "", assignee: "안현기", details: "2025-05-29 확인사항 : 확인 중 (공지 안뜸)" },
|
|
{ month: "3월", no: 5, topic: "가스실 밸브 가스켓 교체", solution: "목표일자 : 4월 이내", assignee: "안경희", details: "2025-05-29 확인사항 : 6월 이내 완료 예정" },
|
|
|
|
// 4월
|
|
{ month: "4월", no: 1, topic: "안전현수막 설치 예정", solution: "공장동 : 5개 (안전 4개, 품질 1개), 2공장 : 2개 (안전 1개, 품질 1개)", assignee: "안현기", details: "2025-05-29 확인사항 : 6월 이내 설치 예정" },
|
|
|
|
// 5월
|
|
{ month: "5월", no: 1, topic: "지게자 면허증 갱신 절차 확인", solution: "대상 : 취득 후 5년 이상, 교육 방식 : 온라인 or 오프라인, 교육 일정 : 주말만 가능", assignee: "안현기", details: "" },
|
|
{ month: "5월", no: 2, topic: "장마철 대비 공장 등 건물 상태 확인", solution: "예상 장마철 일자 : 6월 중순 ~ 7월 중순, 대상 : 사무동, 공장동 천장, 누수관 등", assignee: "김권호", details: "" },
|
|
{ month: "5월", no: 4, topic: "파상풍 주사 2차 접종", solution: "대상자 : 신규입사인원 (AMT, 생산팀, 품질팀), 기타 희망자", assignee: "안현기", details: "" }
|
|
];
|
|
}
|
|
|
|
// 데이터 리셋 함수들
|
|
function resetHandoverData() {
|
|
if (confirm('인수인계 데이터를 초기값으로 리셋하시겠습니까?')) {
|
|
handoverData = getDefaultHandoverData();
|
|
saveToStorage(STORAGE_KEYS.handoverData, handoverData);
|
|
displayHandoverItems();
|
|
alert('인수인계 데이터가 초기화되었습니다.');
|
|
}
|
|
}
|
|
|
|
function resetMeetingData() {
|
|
if (confirm('회의록 데이터를 초기값으로 리셋하시겠습니까?')) {
|
|
existingItems = getDefaultExistingItems();
|
|
saveToStorage(STORAGE_KEYS.existingItems, existingItems);
|
|
displayPastItems();
|
|
alert('회의록 데이터가 초기화되었습니다.');
|
|
}
|
|
}
|
|
|
|
function resetInspectionData() {
|
|
if (confirm('정기점검 데이터를 초기값으로 리셋하시겠습니까?')) {
|
|
inspectionData = getDefaultInspectionData();
|
|
saveToStorage(STORAGE_KEYS.inspectionData, inspectionData);
|
|
displayInspectionItems();
|
|
alert('정기점검 데이터가 초기화되었습니다.');
|
|
}
|
|
}
|
|
|
|
// 데이터 내보내기/가져오기 함수들
|
|
function exportHandoverData() {
|
|
const dataStr = JSON.stringify(handoverData, null, 2);
|
|
downloadJSON(dataStr, '인수인계_데이터.json');
|
|
}
|
|
|
|
function exportMeetingData() {
|
|
const dataStr = JSON.stringify(existingItems, null, 2);
|
|
downloadJSON(dataStr, '회의록_데이터.json');
|
|
}
|
|
|
|
function exportInspectionData() {
|
|
const dataStr = JSON.stringify(inspectionData, null, 2);
|
|
downloadJSON(dataStr, '정기점검_데이터.json');
|
|
}
|
|
|
|
function downloadJSON(dataStr, filename) {
|
|
const blob = new Blob([dataStr], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
alert(`${filename}이 다운로드되었습니다.`);
|
|
}
|
|
|
|
function importHandoverData(event) {
|
|
importJSONData(event, (data) => {
|
|
handoverData = data;
|
|
saveToStorage(STORAGE_KEYS.handoverData, handoverData);
|
|
displayHandoverItems();
|
|
alert('인수인계 데이터가 가져와졌습니다.');
|
|
});
|
|
}
|
|
|
|
function importMeetingData(event) {
|
|
importJSONData(event, (data) => {
|
|
existingItems = data;
|
|
saveToStorage(STORAGE_KEYS.existingItems, existingItems);
|
|
displayPastItems();
|
|
alert('회의록 데이터가 가져와졌습니다.');
|
|
});
|
|
}
|
|
|
|
function importInspectionData(event) {
|
|
importJSONData(event, (data) => {
|
|
inspectionData = data;
|
|
saveToStorage(STORAGE_KEYS.inspectionData, inspectionData);
|
|
displayInspectionItems();
|
|
alert('정기점검 데이터가 가져와졌습니다.');
|
|
});
|
|
}
|
|
|
|
function importJSONData(event, callback) {
|
|
const file = event.target.files[0];
|
|
if (!file) return;
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
try {
|
|
const data = JSON.parse(e.target.result);
|
|
callback(data);
|
|
} catch (error) {
|
|
alert('파일 형식이 잘못되었습니다.');
|
|
}
|
|
};
|
|
reader.readAsText(file);
|
|
|
|
// 파일 입력 리셋
|
|
event.target.value = '';
|
|
}
|
|
|
|
// 인수인계 항목 표시 (개선된 테이블 형태)
|
|
function displayHandoverItems() {
|
|
const container = document.getElementById('handoverContainer');
|
|
if (!handoverData || !container) return;
|
|
|
|
container.innerHTML = '';
|
|
|
|
handoverData.forEach((category, categoryIndex) => {
|
|
const groupDiv = document.createElement('div');
|
|
groupDiv.className = 'handover-group';
|
|
|
|
const categoryDiv = document.createElement('div');
|
|
categoryDiv.className = 'handover-category';
|
|
categoryDiv.textContent = category.category;
|
|
|
|
const itemsDiv = document.createElement('div');
|
|
itemsDiv.className = 'handover-items';
|
|
|
|
category.items.forEach((item, itemIndex) => {
|
|
const itemDiv = document.createElement('div');
|
|
itemDiv.className = 'handover-item';
|
|
itemDiv.innerHTML = `
|
|
<div class="item-info">
|
|
<div class="item-task">${item.task}</div>
|
|
<div class="item-details">
|
|
<div class="item-detail">
|
|
<span>📅</span>
|
|
<span>${item.frequency}</span>
|
|
</div>
|
|
<div class="item-detail">
|
|
<span>👤</span>
|
|
<span class="current-assignee">${item.assignee}</span>
|
|
</div>
|
|
${item.method ? `
|
|
<div class="item-detail">
|
|
<span>🔧</span>
|
|
<span>${item.method}</span>
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
<div style="margin-top: 12px;">
|
|
<label style="font-size: 13px; color: #6c757d; margin-bottom: 8px; display: block;">👥 신규 담당자</label>
|
|
<input type="text" id="handover-${categoryIndex}-${itemIndex}" class="new-assignee-input" placeholder="새 담당자명을 입력하세요">
|
|
</div>
|
|
</div>
|
|
<div class="handover-control">
|
|
<button class="btn btn-success" onclick="processHandover('${category.category}', ${itemIndex}, ${categoryIndex})">
|
|
✅ 인계 확인
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
itemsDiv.appendChild(itemDiv);
|
|
});
|
|
|
|
groupDiv.appendChild(categoryDiv);
|
|
groupDiv.appendChild(itemsDiv);
|
|
container.appendChild(groupDiv);
|
|
});
|
|
}
|
|
|
|
// 인수인계 처리 함수
|
|
function processHandover(categoryName, itemIndex, categoryIndex) {
|
|
const inputId = `handover-${categoryIndex}-${itemIndex}`;
|
|
const newAssignee = document.getElementById(inputId).value.trim();
|
|
|
|
if (!newAssignee) {
|
|
alert('새 담당자명을 입력해주세요.');
|
|
return;
|
|
}
|
|
|
|
const category = handoverData.find(cat => cat.category === categoryName);
|
|
const item = category.items[itemIndex];
|
|
const oldAssignee = item.assignee;
|
|
|
|
if (confirm(`${categoryName} 업무를 ${oldAssignee} → ${newAssignee}로 인계하시겠습니까?`)) {
|
|
// 담당자 업데이트
|
|
item.assignee = newAssignee;
|
|
|
|
// 저장
|
|
saveToStorage(STORAGE_KEYS.handoverData, handoverData);
|
|
|
|
alert(`✅ ${categoryName} 업무 인계 완료!\n${oldAssignee} → ${newAssignee}`);
|
|
|
|
// 화면 새로고침
|
|
displayHandoverItems();
|
|
}
|
|
}
|
|
|
|
// 완료된 항목 보기/숨기기
|
|
function showCompletedItems() {
|
|
showingCompleted = !showingCompleted;
|
|
const button = event.target;
|
|
|
|
if (showingCompleted) {
|
|
button.textContent = '📝 미완료 항목 보기';
|
|
button.className = 'btn btn-info';
|
|
} else {
|
|
button.textContent = '✅ 완료된 항목 보기';
|
|
button.className = 'btn btn-warning';
|
|
}
|
|
|
|
displayPastItems();
|
|
}
|
|
|
|
// 기존 회의록 항목 표시 (미완료 항목만 표시)
|
|
function displayPastItems() {
|
|
const pastItems = document.getElementById('pastItems');
|
|
const totalItems = document.getElementById('totalPastItems');
|
|
const filteredItems = document.getElementById('filteredPastItems');
|
|
const completedItems = document.getElementById('completedPastItems');
|
|
|
|
if (!existingItems || !pastItems) return;
|
|
|
|
// 완료/미완료 항목 분리
|
|
const completedItemsList = existingItems.filter(item => {
|
|
const status = getStatusFromItem(item);
|
|
return status === '완료' || item.details.includes('완료');
|
|
});
|
|
|
|
const incompleteItems = existingItems.filter(item => {
|
|
const status = getStatusFromItem(item);
|
|
return status !== '완료' && !item.details.includes('완료');
|
|
});
|
|
|
|
// 표시할 항목 결정
|
|
const itemsToShow = showingCompleted ? completedItemsList : incompleteItems;
|
|
|
|
totalItems.textContent = incompleteItems.length;
|
|
completedItems.textContent = completedItemsList.length;
|
|
filteredItems.textContent = itemsToShow.length;
|
|
|
|
pastItems.innerHTML = '';
|
|
|
|
if (itemsToShow.length === 0) {
|
|
const message = showingCompleted ?
|
|
'완료된 항목이 없습니다.' :
|
|
'모든 과거 안건이 완료되었습니다! 새로운 안건으로 회의를 진행하세요.';
|
|
|
|
pastItems.innerHTML = `
|
|
<div style="text-align: center; padding: 60px; color: #6c757d;">
|
|
<div style="font-size: 64px; margin-bottom: 20px;">${showingCompleted ? '📝' : '🎉'}</div>
|
|
<div style="font-size: 18px; font-weight: 600; margin-bottom: 10px;">${message}</div>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
itemsToShow.forEach((item, index) => {
|
|
const itemDiv = document.createElement('div');
|
|
itemDiv.className = showingCompleted ? 'past-item completed' : 'past-item';
|
|
itemDiv.setAttribute('data-topic', item.topic.toLowerCase());
|
|
itemDiv.setAttribute('data-assignee', (item.assignee || '').toLowerCase());
|
|
itemDiv.setAttribute('data-solution', (item.solution || '').toLowerCase());
|
|
itemDiv.setAttribute('data-status', getStatusFromItem(item));
|
|
|
|
let statusBadge = '';
|
|
const status = getStatusFromItem(item);
|
|
if (status) {
|
|
let statusColor;
|
|
switch(status) {
|
|
case '완료': statusColor = '#28a745'; break;
|
|
case '진행중': statusColor = '#007bff'; break;
|
|
case '보류': statusColor = '#ffc107'; break;
|
|
default: statusColor = '#dc3545'; break;
|
|
}
|
|
statusBadge = `<span style="background: ${statusColor}; color: white; padding: 3px 10px; border-radius: 12px; font-size: 11px; font-weight: 500;">${status}</span>`;
|
|
} else {
|
|
statusBadge = `<span style="background: #dc3545; color: white; padding: 3px 10px; border-radius: 12px; font-size: 11px; font-weight: 500;">미결</span>`;
|
|
}
|
|
|
|
// 마감일자 생성 (저장된 것이 있으면 사용, 없으면 1개월 후로 설정)
|
|
let deadlineStr;
|
|
if (item.deadline) {
|
|
deadlineStr = new Date(item.deadline).toLocaleDateString('ko-KR');
|
|
} else {
|
|
const deadline = new Date();
|
|
deadline.setMonth(deadline.getMonth() + 1);
|
|
deadlineStr = deadline.toLocaleDateString('ko-KR');
|
|
}
|
|
|
|
const originalIndex = existingItems.indexOf(item);
|
|
|
|
itemDiv.innerHTML = `
|
|
<div class="past-item-header">
|
|
<div class="past-item-meta">
|
|
<span>[${item.month || '월간회의록'}] ${item.no}번</span>
|
|
${statusBadge}
|
|
</div>
|
|
<div class="past-item-actions">
|
|
${!showingCompleted ? `
|
|
<button class="btn-warning btn-small" onclick="editPastItem(${originalIndex})">수정</button>
|
|
<button class="btn-danger btn-small" onclick="clearPastItem(${originalIndex})">완료</button>
|
|
` : `
|
|
<button class="btn-info btn-small" onclick="reactivatePastItem(${originalIndex})">재활성화</button>
|
|
`}
|
|
</div>
|
|
</div>
|
|
<h5>${item.topic}</h5>
|
|
<div class="past-item-content">
|
|
${item.solution ? `<strong>해결방법:</strong> ${item.solution}<br>` : ''}
|
|
${item.assignee ? `<strong>담당자:</strong> ${item.assignee}<br>` : ''}
|
|
${item.details ? `<strong>상세내용:</strong> ${item.details}` : ''}
|
|
</div>
|
|
<div class="past-item-deadline">
|
|
<strong>⏰ 마감일자:</strong> ${deadlineStr}
|
|
</div>
|
|
`;
|
|
|
|
pastItems.appendChild(itemDiv);
|
|
});
|
|
}
|
|
|
|
// 완료된 항목 재활성화
|
|
function reactivatePastItem(index) {
|
|
if (confirm('이 항목을 다시 미완료 상태로 되돌리시겠습니까?')) {
|
|
const item = existingItems[index];
|
|
// 완료 표시 제거
|
|
item.details = item.details.replace(/ \(완료\)/g, '');
|
|
|
|
// 저장
|
|
saveToStorage(STORAGE_KEYS.existingItems, existingItems);
|
|
displayPastItems();
|
|
alert('항목이 미완료 상태로 변경되었습니다.');
|
|
}
|
|
}
|
|
|
|
// 기존 항목 편집
|
|
function editPastItem(index) {
|
|
currentEditIndex = index;
|
|
const item = existingItems[index];
|
|
|
|
document.getElementById('editTopic').value = item.topic;
|
|
document.getElementById('editSolution').value = item.solution || '';
|
|
document.getElementById('editAssignee').value = item.assignee || '';
|
|
document.getElementById('editDetails').value = item.details || '';
|
|
|
|
// 마감일자 설정
|
|
if (item.deadline) {
|
|
document.getElementById('editDeadline').value = item.deadline;
|
|
} else {
|
|
const deadline = new Date();
|
|
deadline.setMonth(deadline.getMonth() + 1);
|
|
document.getElementById('editDeadline').value = deadline.toISOString().split('T')[0];
|
|
}
|
|
|
|
document.getElementById('editModal').style.display = 'block';
|
|
}
|
|
|
|
// 편집 모달 닫기
|
|
function closeEditModal() {
|
|
document.getElementById('editModal').style.display = 'none';
|
|
currentEditIndex = -1;
|
|
}
|
|
|
|
// 편집 확인
|
|
function confirmEdit() {
|
|
if (currentEditIndex === -1) return;
|
|
|
|
const item = existingItems[currentEditIndex];
|
|
item.topic = document.getElementById('editTopic').value;
|
|
item.solution = document.getElementById('editSolution').value;
|
|
item.assignee = document.getElementById('editAssignee').value;
|
|
item.details = document.getElementById('editDetails').value;
|
|
item.deadline = document.getElementById('editDeadline').value;
|
|
|
|
// 저장
|
|
saveToStorage(STORAGE_KEYS.existingItems, existingItems);
|
|
|
|
closeEditModal();
|
|
displayPastItems();
|
|
alert('수정이 완료되었습니다.');
|
|
}
|
|
|
|
// 기존 항목 클리어 (완료 처리)
|
|
function clearPastItem(index) {
|
|
if (confirm('이 항목을 완료 처리하시겠습니까?')) {
|
|
existingItems[index].details += ' (완료)';
|
|
|
|
// 저장
|
|
saveToStorage(STORAGE_KEYS.existingItems, existingItems);
|
|
|
|
displayPastItems();
|
|
alert('항목이 완료 처리되었습니다.');
|
|
}
|
|
}
|
|
|
|
// 정기점검 항목 표시
|
|
function displayInspectionItems() {
|
|
const inspectionItems = document.getElementById('inspectionItems');
|
|
const urgentCount = document.getElementById('urgentCount');
|
|
const pendingCount = document.getElementById('pendingCount');
|
|
|
|
if (!inspectionData || !inspectionItems) return;
|
|
|
|
inspectionItems.innerHTML = '';
|
|
|
|
let urgent = 0, pending = 0;
|
|
|
|
inspectionData.forEach((item, index) => {
|
|
const itemDiv = document.createElement('div');
|
|
let itemClass = 'past-item';
|
|
|
|
// 상태에 따른 클래스 추가
|
|
if (item.status.includes('예정') || item.status.includes('진행')) {
|
|
if (item.status.includes('25년') || item.status.includes('2025')) {
|
|
itemClass += ' urgent-item';
|
|
urgent++;
|
|
} else {
|
|
itemClass += ' pending-item';
|
|
pending++;
|
|
}
|
|
} else if (item.status.includes('완료')) {
|
|
itemClass += ' complete-item';
|
|
}
|
|
|
|
itemDiv.className = itemClass;
|
|
itemDiv.setAttribute('data-item', item.item.toLowerCase());
|
|
itemDiv.setAttribute('data-status', item.status.toLowerCase());
|
|
|
|
itemDiv.innerHTML = `
|
|
<div class="past-item-header">
|
|
<div class="past-item-meta">
|
|
<span>${item.frequency}</span>
|
|
<span>${item.assignee}</span>
|
|
</div>
|
|
<div class="past-item-actions">
|
|
<button class="btn-success btn-small" onclick="copyInspectionToCurrent(${index})">→ 복사</button>
|
|
</div>
|
|
</div>
|
|
<h5>${item.item}</h5>
|
|
<div class="past-item-content">
|
|
<strong>상태:</strong> ${item.status}
|
|
</div>
|
|
`;
|
|
|
|
inspectionItems.appendChild(itemDiv);
|
|
});
|
|
|
|
urgentCount.textContent = urgent;
|
|
pendingCount.textContent = pending;
|
|
}
|
|
|
|
// 정기점검 항목을 현재 폼에 복사
|
|
function copyInspectionToCurrent(index) {
|
|
const item = inspectionData[index];
|
|
|
|
// 회의록 작성 섹션으로 이동
|
|
showSection('meeting');
|
|
document.querySelector('[onclick="showSection(\'meeting\')"]').classList.add('active');
|
|
|
|
addAgendaItem();
|
|
|
|
const lastItem = document.querySelector('.agenda-item:last-child');
|
|
lastItem.querySelector('.agenda-topic').value = item.item;
|
|
lastItem.querySelector('.agenda-assignee').value = item.assignee;
|
|
lastItem.querySelector('.agenda-details').value = `주기: ${item.frequency}, 현재상태: ${item.status}`;
|
|
|
|
// 상태에 따른 진행상태 설정
|
|
if (item.status.includes('완료')) {
|
|
lastItem.querySelector('.agenda-status').value = '완료';
|
|
} else if (item.status.includes('진행') || item.status.includes('예정')) {
|
|
lastItem.querySelector('.agenda-status').value = '진행중';
|
|
}
|
|
|
|
alert('정기점검 항목이 회의록에 추가되었습니다.');
|
|
}
|
|
|
|
// 상태 추출 함수
|
|
function getStatusFromItem(item) {
|
|
if (item.details) {
|
|
if (item.details.includes('완료')) return '완료';
|
|
if (item.details.includes('진행중') || item.details.includes('진행 중')) return '진행중';
|
|
if (item.details.includes('보류')) return '보류';
|
|
if (item.details.includes('미결')) return '미결';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
// 필터링 기능들
|
|
function filterPastItems() {
|
|
const searchTerm = document.getElementById('pastSearchFilter').value.toLowerCase();
|
|
const statusFilter = document.getElementById('pastStatusFilter').value;
|
|
const items = document.querySelectorAll('#pastItems .past-item');
|
|
let visibleCount = 0;
|
|
|
|
items.forEach(item => {
|
|
const topic = item.getAttribute('data-topic');
|
|
const assignee = item.getAttribute('data-assignee');
|
|
const solution = item.getAttribute('data-solution');
|
|
const status = item.getAttribute('data-status');
|
|
|
|
const matchesSearch = !searchTerm ||
|
|
topic.includes(searchTerm) ||
|
|
assignee.includes(searchTerm) ||
|
|
solution.includes(searchTerm);
|
|
|
|
const matchesStatus = !statusFilter || status === statusFilter ||
|
|
(statusFilter === '미결' && !status);
|
|
|
|
if (matchesSearch && matchesStatus) {
|
|
item.style.display = 'block';
|
|
visibleCount++;
|
|
} else {
|
|
item.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
document.getElementById('filteredPastItems').textContent = visibleCount;
|
|
}
|
|
|
|
function filterInspectionItems() {
|
|
const searchTerm = document.getElementById('inspectionSearchFilter').value.toLowerCase();
|
|
const statusFilter = document.getElementById('inspectionStatusFilter').value;
|
|
const items = document.querySelectorAll('#inspectionItems .past-item');
|
|
|
|
items.forEach(item => {
|
|
const itemText = item.getAttribute('data-item');
|
|
const status = item.getAttribute('data-status');
|
|
|
|
const matchesSearch = !searchTerm || itemText.includes(searchTerm);
|
|
const matchesStatus = !statusFilter ||
|
|
(statusFilter === '완료' && status.includes('완료')) ||
|
|
(statusFilter === '예정' && (status.includes('예정') || status.includes('진행 예정'))) ||
|
|
(statusFilter === '진행' && status.includes('진행'));
|
|
|
|
if (matchesSearch && matchesStatus) {
|
|
item.style.display = 'block';
|
|
} else {
|
|
item.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
// 논의사항 항목 추가
|
|
function addAgendaItem() {
|
|
agendaCounter++;
|
|
const agendaItems = document.getElementById('agendaItems');
|
|
|
|
const agendaItem = document.createElement('div');
|
|
agendaItem.className = 'agenda-item';
|
|
agendaItem.innerHTML = `
|
|
<div class="agenda-header">
|
|
<span>논의사항 ${agendaCounter}</span>
|
|
<button class="btn btn-danger" onclick="removeAgendaItem(this)">삭제</button>
|
|
</div>
|
|
<div class="agenda-content">
|
|
<div class="agenda-fields">
|
|
<div class="field-row">
|
|
<div class="field-label">논의사항:</div>
|
|
<input type="text" class="agenda-topic" placeholder="논의할 사항을 입력하세요">
|
|
</div>
|
|
<div class="field-row">
|
|
<div class="field-label">해결방법:</div>
|
|
<textarea class="agenda-solution" rows="3" placeholder="해결방법 또는 조치사항을 입력하세요"></textarea>
|
|
</div>
|
|
<div class="field-row">
|
|
<div class="field-label">담당자:</div>
|
|
<input type="text" class="agenda-assignee" placeholder="담당자명">
|
|
</div>
|
|
<div class="field-row">
|
|
<div class="field-label">상세내용:</div>
|
|
<textarea class="agenda-details" rows="2" placeholder="추가 상세내용"></textarea>
|
|
</div>
|
|
<div class="field-row">
|
|
<div class="field-label">진행상태:</div>
|
|
<select class="agenda-status">
|
|
<option value="">선택</option>
|
|
<option value="완료">완료</option>
|
|
<option value="진행중">진행중</option>
|
|
<option value="보류">보류</option>
|
|
<option value="미결">미결</option>
|
|
</select>
|
|
</div>
|
|
<div class="field-row">
|
|
<div class="field-label">마감일자:</div>
|
|
<input type="date" class="agenda-deadline">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
agendaItems.appendChild(agendaItem);
|
|
}
|
|
|
|
// 논의사항 항목 삭제
|
|
function removeAgendaItem(button) {
|
|
const agendaItem = button.closest('.agenda-item');
|
|
agendaItem.remove();
|
|
|
|
// 항목 번호 재정렬
|
|
const items = document.querySelectorAll('.agenda-item .agenda-header span');
|
|
items.forEach((item, index) => {
|
|
item.textContent = `논의사항 ${index + 1}`;
|
|
});
|
|
}
|
|
|
|
// 회의록 생성
|
|
function generateOutput() {
|
|
const year = document.getElementById('meetingYear').value;
|
|
const month = document.getElementById('meetingMonth').value;
|
|
const date = document.getElementById('meetingDate').value;
|
|
const attendees = document.getElementById('attendees').value;
|
|
|
|
let output = `월간 안전회의록\n`;
|
|
output += `==========================================\n\n`;
|
|
output += `회의 정보\n`;
|
|
output += `- 회의년도: ${year}년\n`;
|
|
output += `- 회의월: ${month}월\n`;
|
|
output += `- 회의일자: ${date}\n`;
|
|
output += `- 참석자: ${attendees}\n\n`;
|
|
|
|
const agendaItems = document.querySelectorAll('.agenda-item');
|
|
|
|
if (agendaItems.length > 0) {
|
|
output += `논의사항\n`;
|
|
output += `==========================================\n\n`;
|
|
|
|
agendaItems.forEach((item, index) => {
|
|
const topic = item.querySelector('.agenda-topic').value;
|
|
const solution = item.querySelector('.agenda-solution').value;
|
|
const assignee = item.querySelector('.agenda-assignee').value;
|
|
const details = item.querySelector('.agenda-details').value;
|
|
const status = item.querySelector('.agenda-status').value;
|
|
const deadline = item.querySelector('.agenda-deadline').value;
|
|
|
|
output += `${index + 1}. ${topic}\n`;
|
|
if (solution) output += ` 해결방법: ${solution}\n`;
|
|
if (assignee) output += ` 담당자: ${assignee}\n`;
|
|
if (details) output += ` 상세내용: ${details}\n`;
|
|
if (status) output += ` 진행상태: ${status}\n`;
|
|
if (deadline) output += ` 마감일자: ${new Date(deadline).toLocaleDateString('ko-KR')}\n`;
|
|
output += `\n`;
|
|
});
|
|
}
|
|
|
|
output += `==========================================\n`;
|
|
output += `작성일: ${new Date().toLocaleDateString('ko-KR')}\n`;
|
|
output += `작성자: [작성자명]\n`;
|
|
|
|
document.getElementById('outputContent').textContent = output;
|
|
document.getElementById('output').style.display = 'block';
|
|
|
|
// 스크롤을 결과로 이동
|
|
document.getElementById('output').scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
|
|
// 클립보드에 복사
|
|
function copyToClipboard() {
|
|
const outputContent = document.getElementById('outputContent').textContent;
|
|
navigator.clipboard.writeText(outputContent).then(() => {
|
|
alert('회의록이 클립보드에 복사되었습니다!');
|
|
}).catch(err => {
|
|
console.error('복사 실패:', err);
|
|
alert('복사에 실패했습니다.');
|
|
});
|
|
}
|
|
|
|
// 양식 초기화
|
|
function clearForm() {
|
|
if (confirm('모든 내용을 초기화하시겠습니까?')) {
|
|
document.getElementById('attendees').value = '';
|
|
document.getElementById('agendaItems').innerHTML = '';
|
|
document.getElementById('output').style.display = 'none';
|
|
agendaCounter = 0;
|
|
}
|
|
}
|
|
|
|
// 템플릿 저장
|
|
function saveTemplate() {
|
|
const templateData = {
|
|
year: document.getElementById('meetingYear').value,
|
|
month: document.getElementById('meetingMonth').value,
|
|
attendees: document.getElementById('attendees').value,
|
|
agendaItems: []
|
|
};
|
|
|
|
const agendaItems = document.querySelectorAll('.agenda-item');
|
|
agendaItems.forEach(item => {
|
|
templateData.agendaItems.push({
|
|
topic: item.querySelector('.agenda-topic').value,
|
|
solution: item.querySelector('.agenda-solution').value,
|
|
assignee: item.querySelector('.agenda-assignee').value,
|
|
details: item.querySelector('.agenda-details').value,
|
|
status: item.querySelector('.agenda-status').value,
|
|
deadline: item.querySelector('.agenda-deadline').value
|
|
});
|
|
});
|
|
|
|
const templateJson = JSON.stringify(templateData, null, 2);
|
|
downloadJSON(templateJson, `안전회의록_템플릿_${new Date().toISOString().split('T')[0]}.json`);
|
|
}
|
|
|
|
// 모달 외부 클릭 시 닫기
|
|
window.onclick = function(event) {
|
|
const modal = document.getElementById('editModal');
|
|
if (event.target === modal) {
|
|
closeEditModal();
|
|
}
|
|
}
|
|
|
|
// 페이지 로드 시 데이터 로드
|
|
window.onload = function() {
|
|
addAgendaItem();
|
|
initializeData();
|
|
};
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|