mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 07:56:24 +00:00
프로토타입 회의록 대시보드 수정 및 UI/UX 설계 문서 적용
This commit is contained in:
parent
75e7146877
commit
a68340735b
@ -289,6 +289,10 @@
|
|||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.meeting-card.ongoing {
|
||||||
|
border-left: 4px solid var(--color-error-main);
|
||||||
|
}
|
||||||
|
|
||||||
.meeting-header {
|
.meeting-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@ -296,6 +300,79 @@
|
|||||||
margin-bottom: var(--spacing-3);
|
margin-bottom: var(--spacing-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.meeting-badges {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-ongoing {
|
||||||
|
background-color: var(--color-error-main);
|
||||||
|
color: var(--color-white);
|
||||||
|
animation: pulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.6; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-indicator {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-1);
|
||||||
|
font-size: var(--font-size-caption);
|
||||||
|
color: var(--color-gray-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.crown-icon {
|
||||||
|
color: var(--color-warning-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meeting-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-2);
|
||||||
|
margin-top: var(--spacing-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-join {
|
||||||
|
flex: 1;
|
||||||
|
padding: var(--spacing-2) var(--spacing-4);
|
||||||
|
background-color: var(--color-primary-main);
|
||||||
|
color: var(--color-white);
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-join:hover {
|
||||||
|
background-color: var(--color-primary-dark);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit {
|
||||||
|
padding: var(--spacing-2) var(--spacing-3);
|
||||||
|
background-color: var(--color-white);
|
||||||
|
color: var(--color-primary-main);
|
||||||
|
border: 1px solid var(--color-primary-main);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: var(--font-size-body-small);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit:hover {
|
||||||
|
background-color: rgba(0, 217, 177, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-text {
|
||||||
|
font-size: var(--font-size-caption);
|
||||||
|
color: var(--color-gray-500);
|
||||||
|
margin-top: var(--spacing-2);
|
||||||
|
}
|
||||||
|
|
||||||
.meeting-title {
|
.meeting-title {
|
||||||
font-size: var(--font-size-h4);
|
font-size: var(--font-size-h4);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
@ -364,6 +441,151 @@
|
|||||||
.dday.warning { color: var(--color-warning-main); }
|
.dday.warning { color: var(--color-warning-main); }
|
||||||
.dday.normal { color: var(--color-gray-500); }
|
.dday.normal { color: var(--color-gray-500); }
|
||||||
|
|
||||||
|
/* Quick Actions */
|
||||||
|
.quick-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-4);
|
||||||
|
margin-bottom: var(--spacing-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action-btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: var(--spacing-6);
|
||||||
|
background: linear-gradient(135deg, var(--color-primary-light) 0%, var(--color-primary-main) 100%);
|
||||||
|
color: var(--color-white);
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-base);
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action-btn.secondary {
|
||||||
|
background: var(--color-white);
|
||||||
|
color: var(--color-primary-main);
|
||||||
|
border: 2px solid var(--color-primary-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action-btn.secondary:hover {
|
||||||
|
background-color: rgba(0, 217, 177, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
margin-bottom: var(--spacing-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action-title {
|
||||||
|
font-size: var(--font-size-h4);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
margin-bottom: var(--spacing-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-action-desc {
|
||||||
|
font-size: var(--font-size-body-small);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FAB Action Modal */
|
||||||
|
.fab-modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: var(--z-modal-backdrop);
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--spacing-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fab-modal.show {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fab-menu {
|
||||||
|
background-color: var(--color-white);
|
||||||
|
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
|
||||||
|
padding: var(--spacing-6);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
animation: slideUp var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
transform: translateY(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fab-menu-title {
|
||||||
|
font-size: var(--font-size-h3);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
color: var(--color-gray-900);
|
||||||
|
margin-bottom: var(--spacing-6);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fab-menu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-4);
|
||||||
|
padding: var(--spacing-5);
|
||||||
|
background-color: var(--color-white);
|
||||||
|
border: 2px solid var(--color-gray-200);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
margin-bottom: var(--spacing-3);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fab-menu-item:hover {
|
||||||
|
border-color: var(--color-primary-main);
|
||||||
|
background-color: rgba(0, 217, 177, 0.05);
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fab-menu-icon {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
background-color: var(--color-primary-light);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 28px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fab-menu-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fab-menu-item-title {
|
||||||
|
font-size: var(--font-size-h4);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
color: var(--color-gray-900);
|
||||||
|
margin-bottom: var(--spacing-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fab-menu-item-desc {
|
||||||
|
font-size: var(--font-size-body-small);
|
||||||
|
color: var(--color-gray-600);
|
||||||
|
}
|
||||||
|
|
||||||
/* Bottom Navigation (Mobile) */
|
/* Bottom Navigation (Mobile) */
|
||||||
.bottom-nav {
|
.bottom-nav {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -479,6 +701,20 @@
|
|||||||
<p class="welcome-subtitle" id="welcomeSubtitle">오늘의 일정을 확인하세요</p>
|
<p class="welcome-subtitle" id="welcomeSubtitle">오늘의 일정을 확인하세요</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Quick Actions -->
|
||||||
|
<section class="quick-actions hide-mobile">
|
||||||
|
<button class="quick-action-btn" onclick="navigateTo('04-템플릿선택.html')">
|
||||||
|
<div class="quick-action-icon">🚀</div>
|
||||||
|
<div class="quick-action-title">새 회의 시작</div>
|
||||||
|
<div class="quick-action-desc">템플릿을 선택하여 회의를 바로 시작합니다</div>
|
||||||
|
</button>
|
||||||
|
<button class="quick-action-btn secondary" onclick="navigateTo('03-회의예약.html')">
|
||||||
|
<div class="quick-action-icon">📅</div>
|
||||||
|
<div class="quick-action-title">회의 예약</div>
|
||||||
|
<div class="quick-action-desc">향후 진행할 회의를 미리 예약합니다</div>
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- Stats Grid -->
|
<!-- Stats Grid -->
|
||||||
<section class="stats-grid">
|
<section class="stats-grid">
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
@ -498,14 +734,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Recent Meetings -->
|
<!-- 예정된/진행중 회의 -->
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2 class="section-title">최근 회의</h2>
|
<h2 class="section-title">예정된 회의</h2>
|
||||||
<a href="12-회의록목록조회.html" class="view-all-link">전체 보기 →</a>
|
<a href="12-회의록목록조회.html" class="view-all-link">전체 보기 →</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="meeting-grid" id="meetingGrid">
|
<div class="meeting-grid" id="upcomingMeetingGrid">
|
||||||
<!-- Meetings will be rendered here -->
|
<!-- Upcoming/Ongoing meetings will be rendered here -->
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 내 회의록 -->
|
||||||
|
<section class="section">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2 class="section-title">내 회의록</h2>
|
||||||
|
<a href="12-회의록목록조회.html" class="view-all-link">전체 보기 →</a>
|
||||||
|
</div>
|
||||||
|
<div class="meeting-grid" id="myMeetingGrid">
|
||||||
|
<!-- My meetings will be rendered here -->
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -519,6 +766,17 @@
|
|||||||
<!-- Todos will be rendered here -->
|
<!-- Todos will be rendered here -->
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Shared Meetings -->
|
||||||
|
<section class="section">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2 class="section-title">공유받은 회의록</h2>
|
||||||
|
<a href="12-회의록목록조회.html" class="view-all-link">전체 보기 →</a>
|
||||||
|
</div>
|
||||||
|
<div class="meeting-grid" id="sharedMeetingGrid">
|
||||||
|
<!-- Shared meetings will be rendered here -->
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -543,13 +801,198 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- FAB -->
|
<!-- FAB -->
|
||||||
<button class="fab" id="fabButton" title="새 회의 예약">+</button>
|
<button class="fab" id="fabButton" title="새 회의 시작">+</button>
|
||||||
|
|
||||||
|
<!-- FAB Action Modal -->
|
||||||
|
<div class="fab-modal" id="fabModal">
|
||||||
|
<div class="fab-menu">
|
||||||
|
<h3 class="fab-menu-title">무엇을 하시겠습니까?</h3>
|
||||||
|
<div class="fab-menu-item" onclick="navigateTo('04-템플릿선택.html')">
|
||||||
|
<div class="fab-menu-icon">🚀</div>
|
||||||
|
<div class="fab-menu-content">
|
||||||
|
<div class="fab-menu-item-title">새 회의 시작</div>
|
||||||
|
<div class="fab-menu-item-desc">템플릿을 선택하여 회의를 바로 시작합니다</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="fab-menu-item" onclick="navigateTo('03-회의예약.html')">
|
||||||
|
<div class="fab-menu-icon">📅</div>
|
||||||
|
<div class="fab-menu-content">
|
||||||
|
<div class="fab-menu-item-title">회의 예약</div>
|
||||||
|
<div class="fab-menu-item-desc">향후 진행할 회의를 미리 예약합니다</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- JavaScript -->
|
<!-- JavaScript -->
|
||||||
<script src="common.js"></script>
|
<script src="common.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const { AppState, Storage, Toast, MeetingUtils, formatDateTime, getDday, navigateTo } = window.MeetingApp;
|
const { AppState, Storage, Toast, MeetingUtils, formatDateTime, getDday, navigateTo } = window.MeetingApp;
|
||||||
|
|
||||||
|
// 샘플 데이터 초기화
|
||||||
|
function initSampleData() {
|
||||||
|
// 기존 데이터가 없으면 샘플 데이터 생성
|
||||||
|
const currentUserId = 'user-001'; // 현재 사용자 ID (예시)
|
||||||
|
|
||||||
|
if (!Storage.get('meetings') || Storage.get('meetings').length === 0) {
|
||||||
|
const sampleMeetings = [
|
||||||
|
{
|
||||||
|
id: 'meeting-001',
|
||||||
|
title: '긴급 버그 픽스 회의',
|
||||||
|
date: new Date(Date.now() - 1800000).toISOString(), // 30분 전 시작
|
||||||
|
startTime: new Date(Date.now() - 1800000).toISOString(),
|
||||||
|
location: '온라인 (Zoom)',
|
||||||
|
status: 'ongoing',
|
||||||
|
description: '프로덕션 환경 긴급 버그 대응',
|
||||||
|
creatorId: 'user-002',
|
||||||
|
attendees: ['user-001', 'user-002', 'user-003']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'meeting-002',
|
||||||
|
title: '주간 스프린트 회의',
|
||||||
|
date: new Date(Date.now() + 86400000).toISOString(), // 내일
|
||||||
|
startTime: new Date(Date.now() + 86400000).toISOString(),
|
||||||
|
location: '온라인 (Zoom)',
|
||||||
|
status: 'scheduled',
|
||||||
|
description: '이번 주 스프린트 진행사항 및 다음 주 계획 논의',
|
||||||
|
creatorId: 'user-001', // 현재 사용자가 생성자
|
||||||
|
attendees: ['user-001', 'user-002', 'user-003', 'user-004']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'meeting-003',
|
||||||
|
title: 'Q4 마케팅 전략 회의',
|
||||||
|
date: new Date(Date.now() - 172800000).toISOString(), // 2일 전
|
||||||
|
startTime: new Date(Date.now() - 172800000).toISOString(),
|
||||||
|
location: '본사 3층 회의실',
|
||||||
|
status: 'completed',
|
||||||
|
description: '4분기 마케팅 캠페인 기획 및 예산 검토',
|
||||||
|
creatorId: 'user-005',
|
||||||
|
attendees: ['user-001', 'user-005']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'meeting-004',
|
||||||
|
title: '디자인 리뷰',
|
||||||
|
date: new Date(Date.now() + 3600000).toISOString(), // 1시간 후
|
||||||
|
startTime: new Date(Date.now() + 3600000).toISOString(),
|
||||||
|
location: '온라인 (Google Meet)',
|
||||||
|
status: 'scheduled',
|
||||||
|
description: 'UI/UX 디자인 최종 검토 및 피드백',
|
||||||
|
creatorId: 'user-003',
|
||||||
|
attendees: ['user-001', 'user-003', 'user-006']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'meeting-005',
|
||||||
|
title: '월간 전체 회의',
|
||||||
|
date: new Date(Date.now() + 259200000).toISOString(), // 3일 후
|
||||||
|
startTime: new Date(Date.now() + 259200000).toISOString(),
|
||||||
|
location: '대강당',
|
||||||
|
status: 'scheduled',
|
||||||
|
description: '월간 성과 공유 및 다음 달 목표 설정',
|
||||||
|
creatorId: 'user-001', // 현재 사용자가 생성자
|
||||||
|
attendees: ['user-001', 'user-002', 'user-003', 'user-004', 'user-005']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'meeting-006',
|
||||||
|
title: '고객 피드백 리뷰',
|
||||||
|
date: new Date(Date.now() - 345600000).toISOString(), // 4일 전
|
||||||
|
startTime: new Date(Date.now() - 345600000).toISOString(),
|
||||||
|
location: '온라인 (Teams)',
|
||||||
|
status: 'completed',
|
||||||
|
description: '최근 수집된 고객 VOC 분석 및 개선 방안 도출',
|
||||||
|
creatorId: 'user-007',
|
||||||
|
attendees: ['user-001', 'user-007']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
Storage.set('meetings', sampleMeetings);
|
||||||
|
Storage.set('currentUserId', currentUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Storage.get('todos') || Storage.get('todos').length === 0) {
|
||||||
|
const sampleTodos = [
|
||||||
|
{
|
||||||
|
id: 'todo-001',
|
||||||
|
title: '마케팅 자료 최종 검토 및 승인',
|
||||||
|
assignee: '김민준',
|
||||||
|
dueDate: new Date(Date.now() + 86400000).toISOString(), // 내일
|
||||||
|
priority: 'high',
|
||||||
|
status: 'in_progress',
|
||||||
|
meetingId: 'meeting-002'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'todo-002',
|
||||||
|
title: '개발 환경 설정 가이드 문서 작성',
|
||||||
|
assignee: '이준호',
|
||||||
|
dueDate: new Date(Date.now() + 172800000).toISOString(), // 2일 후
|
||||||
|
priority: 'medium',
|
||||||
|
status: 'in_progress',
|
||||||
|
meetingId: 'meeting-003'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'todo-003',
|
||||||
|
title: '고객 설문조사 결과 분석 및 보고서 작성',
|
||||||
|
assignee: '박서연',
|
||||||
|
dueDate: new Date(Date.now() + 432000000).toISOString(), // 5일 후
|
||||||
|
priority: 'high',
|
||||||
|
status: 'pending',
|
||||||
|
meetingId: 'meeting-005'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'todo-004',
|
||||||
|
title: 'UI/UX 디자인 개선안 3차 리뷰',
|
||||||
|
assignee: '최유진',
|
||||||
|
dueDate: new Date(Date.now() - 86400000).toISOString(), // 어제 (지연)
|
||||||
|
priority: 'high',
|
||||||
|
status: 'in_progress',
|
||||||
|
meetingId: 'meeting-003'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'todo-005',
|
||||||
|
title: '다음 스프린트 백로그 정리',
|
||||||
|
assignee: '정도현',
|
||||||
|
dueDate: new Date(Date.now() + 259200000).toISOString(), // 3일 후
|
||||||
|
priority: 'medium',
|
||||||
|
status: 'pending',
|
||||||
|
meetingId: 'meeting-001'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
Storage.set('todos', sampleTodos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 공유받은 회의록 샘플 데이터
|
||||||
|
if (!Storage.get('sharedMeetings') || Storage.get('sharedMeetings').length === 0) {
|
||||||
|
const sharedMeetings = [
|
||||||
|
{
|
||||||
|
id: 'meeting-s001',
|
||||||
|
title: 'AI 기술 도입 검토 회의',
|
||||||
|
date: new Date(Date.now() - 259200000).toISOString(), // 3일 전
|
||||||
|
location: '본사 2층 회의실',
|
||||||
|
status: 'completed',
|
||||||
|
description: 'LLM 기반 서비스 개선 방안 논의',
|
||||||
|
sharedBy: '홍길동'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'meeting-s002',
|
||||||
|
title: '보안 정책 업데이트',
|
||||||
|
date: new Date(Date.now() - 432000000).toISOString(), // 5일 전
|
||||||
|
location: '온라인 (Zoom)',
|
||||||
|
status: 'completed',
|
||||||
|
description: '최신 보안 가이드라인 공유 및 적용 계획',
|
||||||
|
sharedBy: '송주영'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'meeting-s003',
|
||||||
|
title: '팀 빌딩 워크샵',
|
||||||
|
date: new Date(Date.now() - 604800000).toISOString(), // 7일 전
|
||||||
|
location: '제주 연수원',
|
||||||
|
status: 'completed',
|
||||||
|
description: '팀 협업 강화 및 커뮤니케이션 개선 활동',
|
||||||
|
sharedBy: '백현정'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
Storage.set('sharedMeetings', sharedMeetings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 인증 체크
|
// 인증 체크
|
||||||
MeetingApp.ready(() => {
|
MeetingApp.ready(() => {
|
||||||
const authToken = Storage.get('authToken');
|
const authToken = Storage.get('authToken');
|
||||||
@ -569,10 +1012,20 @@
|
|||||||
AppState.currentUser = currentUser;
|
AppState.currentUser = currentUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 샘플 데이터 초기화
|
||||||
|
initSampleData();
|
||||||
|
|
||||||
// 데이터 로드 및 렌더링
|
// 데이터 로드 및 렌더링
|
||||||
loadDashboardData();
|
loadDashboardData();
|
||||||
renderMeetings();
|
renderUpcomingMeetings();
|
||||||
|
renderMyMeetings();
|
||||||
renderTodos();
|
renderTodos();
|
||||||
|
renderSharedMeetings();
|
||||||
|
|
||||||
|
// 타이머 업데이트 (1분마다)
|
||||||
|
setInterval(() => {
|
||||||
|
renderUpcomingMeetings();
|
||||||
|
}, 60000);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 대시보드 통계 로드
|
// 대시보드 통계 로드
|
||||||
@ -594,27 +1047,182 @@
|
|||||||
document.getElementById('todoCompletionRate').textContent = `${completionRate}%`;
|
document.getElementById('todoCompletionRate').textContent = `${completionRate}%`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 회의 목록 렌더링
|
// 유틸리티 함수: 시간까지 남은 시간 계산
|
||||||
function renderMeetings() {
|
function getTimeUntilMeeting(startTime) {
|
||||||
const meetings = Storage.get('meetings', []).slice(0, 3);
|
const now = new Date();
|
||||||
const meetingGrid = document.getElementById('meetingGrid');
|
const start = new Date(startTime);
|
||||||
|
const diff = start - now;
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
if (meetings.length === 0) {
|
// 유틸리티 함수: 타이머 텍스트 포맷팅
|
||||||
meetingGrid.innerHTML = '<p style="color: var(--color-gray-500);">아직 등록된 회의가 없습니다.</p>';
|
function formatTimerText(milliseconds) {
|
||||||
|
if (milliseconds < 0) return '시작됨';
|
||||||
|
|
||||||
|
const hours = Math.floor(milliseconds / 3600000);
|
||||||
|
const minutes = Math.floor((milliseconds % 3600000) / 60000);
|
||||||
|
|
||||||
|
if (hours > 24) {
|
||||||
|
const days = Math.floor(hours / 24);
|
||||||
|
return `D-${days}`;
|
||||||
|
} else if (hours > 0) {
|
||||||
|
return `${hours}시간 후`;
|
||||||
|
} else if (minutes > 10) {
|
||||||
|
return `${minutes}분 후`;
|
||||||
|
} else if (minutes > 0) {
|
||||||
|
return `곧 시작`;
|
||||||
|
} else {
|
||||||
|
return '시작 가능';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 예정된/진행중 회의 렌더링
|
||||||
|
function renderUpcomingMeetings() {
|
||||||
|
const meetings = Storage.get('meetings', []);
|
||||||
|
const currentUserId = Storage.get('currentUserId', 'user-001');
|
||||||
|
const upcomingMeetingGrid = document.getElementById('upcomingMeetingGrid');
|
||||||
|
|
||||||
|
// 진행중 및 예정된 회의 필터링
|
||||||
|
const now = new Date();
|
||||||
|
const upcomingMeetings = meetings.filter(m => {
|
||||||
|
if (m.status === 'ongoing') return true;
|
||||||
|
if (m.status === 'scheduled') {
|
||||||
|
const meetingDate = new Date(m.startTime || m.date);
|
||||||
|
return meetingDate >= now;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 정렬: 진행중 회의 우선, 그 다음 가까운 회의순
|
||||||
|
upcomingMeetings.sort((a, b) => {
|
||||||
|
if (a.status === 'ongoing' && b.status !== 'ongoing') return -1;
|
||||||
|
if (a.status !== 'ongoing' && b.status === 'ongoing') return 1;
|
||||||
|
return new Date(a.startTime || a.date) - new Date(b.startTime || b.date);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 최대 3개만 표시
|
||||||
|
const displayMeetings = upcomingMeetings.slice(0, 3);
|
||||||
|
|
||||||
|
if (displayMeetings.length === 0) {
|
||||||
|
upcomingMeetingGrid.innerHTML = '<p style="color: var(--color-gray-500);">예정된 회의가 없습니다.</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
meetingGrid.innerHTML = meetings.map(meeting => `
|
upcomingMeetingGrid.innerHTML = displayMeetings.map(meeting => {
|
||||||
<div class="meeting-card" onclick="navigateTo('05-회의진행.html')">
|
const isCreator = meeting.creatorId === currentUserId;
|
||||||
|
const isOngoing = meeting.status === 'ongoing';
|
||||||
|
const timeUntil = isOngoing ? 0 : getTimeUntilMeeting(meeting.startTime || meeting.date);
|
||||||
|
const canJoin = isOngoing || (timeUntil > 0 && timeUntil <= 600000); // 10분 이내
|
||||||
|
const timerText = isOngoing ? '진행중' : formatTimerText(timeUntil);
|
||||||
|
|
||||||
|
let actionButtons = '';
|
||||||
|
if (isOngoing) {
|
||||||
|
// 진행중 회의: 모든 참석자에게 참여하기 버튼
|
||||||
|
actionButtons = `
|
||||||
|
<div class="meeting-actions">
|
||||||
|
<button class="btn-join" onclick="navigateTo('05-회의진행.html')">참여하기</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else if (isCreator) {
|
||||||
|
// 생성자: 수정 버튼
|
||||||
|
actionButtons = `
|
||||||
|
<div class="meeting-actions">
|
||||||
|
<button class="btn-edit" onclick="navigateTo('03-회의예약.html')">수정</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else if (canJoin) {
|
||||||
|
// 참석자: 10분 이내면 참여하기 버튼
|
||||||
|
actionButtons = `
|
||||||
|
<div class="meeting-actions">
|
||||||
|
<button class="btn-join" onclick="navigateTo('05-회의진행.html')">참여하기</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
// 참석자: 10분 이상 남음
|
||||||
|
actionButtons = `
|
||||||
|
<div class="timer-text">${timerText} 참여 가능</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleIndicator = isCreator && !isOngoing ?
|
||||||
|
'<span class="role-indicator"><span class="crown-icon">👑</span> 생성자</span>' : '';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="meeting-card ${isOngoing ? 'ongoing' : ''}" onclick="event.stopPropagation(); navigateTo('10-회의록대시보드.html')">
|
||||||
|
<div class="meeting-header">
|
||||||
|
<div style="flex: 1;">
|
||||||
|
<div class="meeting-badges">
|
||||||
|
${isOngoing ? '<span class="badge badge-ongoing">진행중</span>' : `<span class="badge badge-neutral">${timerText}</span>`}
|
||||||
|
${roleIndicator}
|
||||||
|
</div>
|
||||||
|
<div class="meeting-title">${meeting.title}</div>
|
||||||
|
<div class="meeting-meta">📅 ${formatDateTime(meeting.date)}</div>
|
||||||
|
<div class="meeting-meta">📍 ${meeting.location}</div>
|
||||||
|
<div class="meeting-meta">👥 ${meeting.attendees ? meeting.attendees.length : 0}명</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="font-size: var(--font-size-body-small); color: var(--color-gray-600); margin-bottom: var(--spacing-2);">
|
||||||
|
${meeting.description}
|
||||||
|
</div>
|
||||||
|
${actionButtons}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 내 회의록 렌더링
|
||||||
|
function renderMyMeetings() {
|
||||||
|
const meetings = Storage.get('meetings', []);
|
||||||
|
const myMeetingGrid = document.getElementById('myMeetingGrid');
|
||||||
|
|
||||||
|
// 완료된 회의만 필터링
|
||||||
|
const completedMeetings = meetings
|
||||||
|
.filter(m => m.status === 'completed')
|
||||||
|
.sort((a, b) => new Date(b.date) - new Date(a.date))
|
||||||
|
.slice(0, 3);
|
||||||
|
|
||||||
|
if (completedMeetings.length === 0) {
|
||||||
|
myMeetingGrid.innerHTML = '<p style="color: var(--color-gray-500);">작성한 회의록이 없습니다. 첫 회의를 시작해보세요!</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
myMeetingGrid.innerHTML = completedMeetings.map(meeting => `
|
||||||
|
<div class="meeting-card" onclick="navigateTo('10-회의록상세조회.html')">
|
||||||
<div class="meeting-header">
|
<div class="meeting-header">
|
||||||
<div>
|
<div>
|
||||||
<div class="meeting-title">${meeting.title}</div>
|
<div class="meeting-title">${meeting.title}</div>
|
||||||
<div class="meeting-meta">📅 ${formatDateTime(meeting.date)}</div>
|
<div class="meeting-meta">📅 ${formatDateTime(meeting.date)}</div>
|
||||||
<div class="meeting-meta">📍 ${meeting.location}</div>
|
<div class="meeting-meta">📍 ${meeting.location}</div>
|
||||||
|
<div class="meeting-meta">👥 ${meeting.attendees ? meeting.attendees.length : 0}명</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="badge ${MeetingUtils.getStatusClass(meeting.status)}">
|
<span class="badge badge-success">확정완료</span>
|
||||||
${MeetingUtils.getStatusLabel(meeting.status)}
|
</div>
|
||||||
</span>
|
<div style="font-size: var(--font-size-body-small); color: var(--color-gray-600);">
|
||||||
|
${meeting.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 공유받은 회의록 렌더링
|
||||||
|
function renderSharedMeetings() {
|
||||||
|
const sharedMeetings = Storage.get('sharedMeetings', []);
|
||||||
|
const sharedMeetingGrid = document.getElementById('sharedMeetingGrid');
|
||||||
|
|
||||||
|
if (sharedMeetings.length === 0) {
|
||||||
|
sharedMeetingGrid.innerHTML = '<p style="color: var(--color-gray-500);">공유받은 회의록이 없습니다.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedMeetingGrid.innerHTML = sharedMeetings.map(meeting => `
|
||||||
|
<div class="meeting-card" onclick="navigateTo('10-회의록상세조회.html')">
|
||||||
|
<div class="meeting-header">
|
||||||
|
<div>
|
||||||
|
<div class="meeting-title">${meeting.title}</div>
|
||||||
|
<div class="meeting-meta">📅 ${formatDateTime(meeting.date)}</div>
|
||||||
|
<div class="meeting-meta">👤 공유자: ${meeting.sharedBy}</div>
|
||||||
|
</div>
|
||||||
|
<span class="badge badge-primary">공유됨</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: var(--font-size-body-small); color: var(--color-gray-600);">
|
<div style="font-size: var(--font-size-body-small); color: var(--color-gray-600);">
|
||||||
${meeting.description}
|
${meeting.description}
|
||||||
@ -678,9 +1286,30 @@
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
// FAB 버튼
|
// FAB 버튼 - 모달 토글
|
||||||
document.getElementById('fabButton').addEventListener('click', () => {
|
const fabButton = document.getElementById('fabButton');
|
||||||
window.location.href = '03-회의예약.html';
|
const fabModal = document.getElementById('fabModal');
|
||||||
|
|
||||||
|
fabButton.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
fabModal.classList.add('show');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 모달 배경 클릭 시 닫기
|
||||||
|
fabModal.addEventListener('click', (e) => {
|
||||||
|
if (e.target === fabModal) {
|
||||||
|
fabModal.classList.remove('show');
|
||||||
|
document.body.style.overflow = 'auto';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESC 키로 모달 닫기
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape' && fabModal.classList.contains('show')) {
|
||||||
|
fabModal.classList.remove('show');
|
||||||
|
document.body.style.overflow = 'auto';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user