UI/UX 설계서 v1.4.18 업데이트

05-회의진행 실시간 주요 메모 추천 기능 명확화
- UFR-MEET-030: AI가 중요한 내용으로 판단한 경우에만 추천
- 추천 빈도는 가변적 (3-5초 고정 간격 아님)
- 프로토타입 확인: 05-회의진행.html AI 제안 탭 기능 포함

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
yabo0812
2025-10-24 15:01:35 +09:00
parent e37d20942a
commit 734e182287
7 changed files with 826 additions and 809 deletions
-527
View File
@@ -1,527 +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="common.css">
<style>
/* 페이지별 커스텀 스타일만 유지 */
/* 공통 스타일(헤더, 메인콘텐츠, 액션바)은 common.css 사용 */
.progress-container {
margin-bottom: var(--space-lg);
}
.progress-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-sm);
}
.progress-percentage {
font-size: var(--font-h2);
font-weight: var(--font-weight-bold);
color: var(--primary);
}
.verification-card {
display: flex;
align-items: center;
gap: var(--space-md);
padding: var(--space-md);
background: var(--white);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
margin-bottom: var(--space-md);
transition: all var(--transition-normal);
}
.verification-card:hover {
box-shadow: var(--shadow-lg);
}
.verification-card.verified {
border-left: 4px solid var(--success);
}
.verification-card.unverified {
border-left: 4px solid var(--gray-300);
}
.verification-card.locked {
background: var(--gray-100);
}
.verify-icon {
font-size: 32px;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.verify-icon.verified {
color: var(--success);
}
.verify-icon.unverified {
color: var(--gray-300);
}
.lock-icon {
font-size: 20px;
color: var(--gray-500);
}
/* 편집 모드 스타일 */
.edit-field {
width: 100%;
padding: var(--space-sm);
border: 1px solid var(--gray-300);
border-radius: var(--radius-md);
font-size: var(--font-small);
line-height: 1.6;
margin-bottom: var(--space-sm);
transition: border-color var(--transition-normal);
}
.edit-field:focus {
outline: none;
border-color: var(--primary);
}
.edit-label {
display: block;
font-weight: var(--font-weight-medium);
margin-bottom: var(--space-xs);
color: var(--text-primary);
}
</style>
</head>
<body>
<div class="page">
<!-- Header -->
<header class="header">
<div class="header-left">
<button class="back-btn" onclick="history.back()"></button>
<h1 class="header-title">검증 완료</h1>
</div>
</header>
<!-- Main Content -->
<main class="main-content has-action-bar">
<!-- Progress Bar -->
<div class="progress-container">
<div class="progress-header">
<h2 class="text-small font-bold">전체 진행률</h2>
<span class="progress-percentage" id="progressText">50%</span>
</div>
<div class="progress" style="height: 12px;">
<div class="progress-bar progress-bar-success" id="progressBar" style="width: 50%;"></div>
</div>
<p class="text-small text-muted mt-sm">4개 섹션 중 2개 검증 완료</p>
</div>
<!-- Meeting Info -->
<div class="card mb-lg">
<div class="card-header">
<h3 class="card-title">2025년 1분기 제품 기획 회의</h3>
</div>
<div class="card-body">
<div class="text-small text-muted">
<div style="display: flex; gap: var(--space-md); margin-bottom: var(--space-xs);">
<span>📅 2025-10-25 14:00</span>
<span>⏱️ 90분</span>
</div>
<div>👥 김민준, 박서연, 이준호, 최유진</div>
</div>
</div>
</div>
<!-- Section List -->
<div>
<h2 class="text-small font-bold mb-md">섹션별 검증 상태</h2>
<!-- 섹션 1 - 검증 완료 -->
<div class="verification-card verified">
<div class="verify-icon verified"></div>
<div style="flex: 1;">
<h3 class="text-small font-bold" style="margin: 0 0 4px 0;">회의 개요</h3>
<div style="display: flex; align-items: center; gap: var(--space-sm);">
<div class="avatar-group">
<div class="avatar avatar-green avatar-sm"></div>
<div class="avatar avatar-blue avatar-sm"></div>
</div>
<span class="text-caption text-muted">2명 검증 완료</span>
</div>
</div>
<button class="btn btn-secondary btn-sm" onclick="viewSection(0)">보기</button>
</div>
<!-- 섹션 2 - 검증 완료 + 잠금 -->
<div class="verification-card verified locked">
<div class="verify-icon verified"></div>
<div style="flex: 1;">
<h3 class="text-small font-bold" style="margin: 0 0 4px 0;">
논의 사항
<span class="lock-icon">🔒</span>
</h3>
<div style="display: flex; align-items: center; gap: var(--space-sm);">
<div class="avatar-group">
<div class="avatar avatar-green avatar-sm"></div>
<div class="avatar avatar-blue avatar-sm"></div>
<div class="avatar avatar-yellow avatar-sm"></div>
</div>
<span class="text-caption text-muted">3명 검증 완료 · 잠금됨</span>
</div>
</div>
<button class="btn btn-secondary btn-sm" onclick="unlockSection(1)">잠금해제</button>
</div>
<!-- 섹션 3 - 미검증 -->
<div class="verification-card unverified">
<div class="verify-icon unverified"></div>
<div style="flex: 1;">
<h3 class="text-small font-bold" style="margin: 0 0 4px 0;">결정 사항</h3>
<div style="display: flex; align-items: center; gap: var(--space-sm);">
<div class="avatar-group">
<div class="avatar avatar-blue avatar-sm"></div>
</div>
<span class="text-caption text-muted">1명 검증 완료</span>
</div>
</div>
<button class="btn btn-primary btn-sm" onclick="verifySection(2)">검증하기</button>
</div>
<!-- 섹션 4 - 미검증 -->
<div class="verification-card unverified">
<div class="verify-icon unverified"></div>
<div style="flex: 1;">
<h3 class="text-small font-bold" style="margin: 0 0 4px 0;">액션 아이템</h3>
<div style="display: flex; align-items: center; gap: var(--space-sm);">
<span class="text-caption text-muted">아직 검증되지 않음</span>
</div>
</div>
<button class="btn btn-primary btn-sm" onclick="verifySection(3)">검증하기</button>
</div>
</div>
</main>
<!-- 하단 액션 바 -->
<div class="action-bar">
<button class="btn btn-secondary" onclick="saveLater()">나중에 하기</button>
<button class="btn btn-primary" id="completeBtn" disabled onclick="completeAllVerification()">
모두 검증 완료
</button>
</div>
</div>
<!-- Section View Modal -->
<div id="sectionModal" class="modal-overlay">
<div class="modal" style="max-height: 80vh; overflow-y: auto;">
<div class="modal-header">
<h2 class="modal-title" id="sectionTitle">회의 개요</h2>
<button class="modal-close" onclick="closeModal('sectionModal')"></button>
</div>
<div class="modal-body">
<div id="sectionContent">
<!-- 섹션 내용이 여기에 표시됨 -->
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary btn-sm" onclick="closeModal('sectionModal')">닫기</button>
<button class="btn btn-primary btn-sm" id="editBtn" onclick="toggleEditMode()">편집</button>
<button class="btn btn-primary btn-sm" id="saveBtn" style="display: none;" onclick="saveSection()">저장</button>
</div>
</div>
</div>
<!-- Verification Confirm Modal -->
<div id="verifyModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h2 class="modal-title">섹션 검증</h2>
<button class="modal-close" onclick="closeModal('verifyModal')"></button>
</div>
<div class="modal-body">
<p class="text-small mb-md" id="verifyMessage">이 섹션의 내용을 검증하시겠습니까?</p>
<div class="card" style="background: var(--primary-light);">
<p class="text-small font-medium">검증 후에는 다른 참석자들도 이 섹션이 확인되었음을 알 수 있습니다.</p>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary btn-sm" onclick="closeModal('verifyModal')">취소</button>
<button class="btn btn-primary btn-sm" onclick="confirmVerification()">검증 완료</button>
</div>
</div>
</div>
<!-- Unlock Confirm Modal -->
<div id="unlockModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h2 class="modal-title">섹션 잠금 해제</h2>
<button class="modal-close" onclick="closeModal('unlockModal')"></button>
</div>
<div class="modal-body">
<p class="text-small mb-md">이 섹션의 잠금을 해제하시겠습니까?</p>
<div class="card" style="background: transparent; color: var(--error); border: 1px solid var(--error);">
<p class="text-small font-medium">⚠️ 잠금 해제 시 다른 참석자들이 내용을 수정할 수 있습니다.</p>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-sm" style="background: transparent; color: var(--error); border: 1px solid var(--error);" onclick="closeModal('unlockModal')">취소</button>
<button class="btn btn-error btn-sm" onclick="confirmUnlock()">잠금 해제</button>
</div>
</div>
</div>
<script src="common.js"></script>
<script>
// 섹션 검증 상태
const sectionVerifications = [
{ name: '회의 개요', verified: true, locked: false, verifiers: ['김민준', '박서연'] },
{ name: '논의 사항', verified: true, locked: true, verifiers: ['김민준', '박서연', '이준호'] },
{ name: '결정 사항', verified: false, locked: false, verifiers: ['박서연'] },
{ name: '액션 아이템', verified: false, locked: false, verifiers: [] }
];
let currentSectionIndex = -1;
let isEditMode = false;
let originalContent = '';
// 섹션 데이터 (편집 가능한 필드)
const sectionData = [
{
purpose: '2025년 1분기 신제품 개발 방향 수립',
attendees: '김민준(PM), 박서연(AI), 이준호(Backend), 최유진(Frontend)',
datetime: '2025년 10월 25일 14:00 - 15:30',
location: '본사 2층 대회의실'
},
{
topic1: 'AI 모델 정확도',
detail1: '현재 STT 정확도: 92%, 목표 정확도: 95% 이상',
topic2: '사용자 인터페이스',
detail2: 'Mobile First 디자인 채택, 실시간 협업 기능 필수'
}
];
// 진행률 업데이트
function updateProgress() {
const totalSections = sectionVerifications.length;
const verifiedCount = sectionVerifications.filter(s => s.verified).length;
const percentage = Math.round((verifiedCount / totalSections) * 100);
$('#progressBar').style.width = percentage + '%';
$('#progressText').textContent = percentage + '%';
const completeBtn = $('#completeBtn');
if (percentage === 100) {
completeBtn.disabled = false;
completeBtn.style.opacity = '1';
} else {
completeBtn.disabled = true;
completeBtn.style.opacity = '0.5';
}
}
// 섹션 보기
function viewSection(index) {
currentSectionIndex = index;
isEditMode = false;
const section = sectionVerifications[index];
$('#sectionTitle').textContent = section.name;
// 편집 모드 초기화
$('#editBtn').style.display = 'inline-block';
$('#saveBtn').style.display = 'none';
// 회의 개요만 편집 가능
if (index === 0) {
renderSectionContent(index, false);
} else {
// 다른 섹션은 읽기 전용
const sampleContent = `
<p><strong>1. AI 모델 정확도</strong></p>
<p>- 현재 STT 정확도: 92%</p>
<p>- 목표 정확도: 95% 이상</p>
<br>
<p><strong>2. 사용자 인터페이스</strong></p>
<p>- Mobile First 디자인 채택</p>
<p>- 실시간 협업 기능 필수</p>
`;
$('#sectionContent').innerHTML = sampleContent;
}
openModal('sectionModal');
}
// 섹션 내용 렌더링
function renderSectionContent(index, editMode) {
const data = sectionData[index];
const contentDiv = $('#sectionContent');
if (editMode && index === 0) {
// 회의 개요 편집 모드
contentDiv.innerHTML = `
<div>
<label class="edit-label">회의 목적</label>
<input type="text" class="edit-field" id="edit_purpose" value="${data.purpose}">
</div>
<div>
<label class="edit-label">참석자</label>
<input type="text" class="edit-field" id="edit_attendees" value="${data.attendees}">
</div>
<div>
<label class="edit-label">일시</label>
<input type="text" class="edit-field" id="edit_datetime" value="${data.datetime}">
</div>
<div>
<label class="edit-label">장소</label>
<input type="text" class="edit-field" id="edit_location" value="${data.location}">
</div>
`;
} else if (index === 0) {
// 회의 개요 보기 모드
contentDiv.innerHTML = `
<p><strong>회의 목적:</strong> ${data.purpose}</p>
<p><strong>참석자:</strong> ${data.attendees}</p>
<p><strong>일시:</strong> ${data.datetime}</p>
<p><strong>장소:</strong> ${data.location}</p>
`;
}
}
// 섹션 검증
function verifySection(index) {
currentSectionIndex = index;
const section = sectionVerifications[index];
$('#verifyMessage').textContent = `"${section.name}" 섹션의 내용을 검증하시겠습니까?`;
openModal('verifyModal');
}
// 검증 확인
function confirmVerification() {
const section = sectionVerifications[currentSectionIndex];
section.verified = true;
if (!section.verifiers.includes('김민준')) {
section.verifiers.push('김민준');
}
closeModal('verifyModal');
showToast(`"${section.name}" 섹션이 검증되었습니다`, 'success');
// 화면 새로고침 시뮬레이션
setTimeout(() => {
location.reload();
}, 1000);
}
// 섹션 잠금 해제
function unlockSection(index) {
currentSectionIndex = index;
openModal('unlockModal');
}
// 잠금 해제 확인
function confirmUnlock() {
const section = sectionVerifications[currentSectionIndex];
section.locked = false;
closeModal('unlockModal');
showToast(`"${section.name}" 섹션의 잠금이 해제되었습니다`, 'success');
// 화면 새로고침 시뮬레이션
setTimeout(() => {
location.reload();
}, 1000);
}
// 편집 모드 토글
function toggleEditMode() {
if (currentSectionIndex !== 0) {
// 회의 개요가 아닌 경우
closeModal('sectionModal');
showToast('이 섹션은 회의록수정 화면에서 수정할 수 있습니다', 'info');
return;
}
isEditMode = !isEditMode;
if (isEditMode) {
// 편집 모드로 전환
renderSectionContent(currentSectionIndex, true);
$('#editBtn').style.display = 'none';
$('#saveBtn').style.display = 'inline-block';
} else {
// 보기 모드로 전환
renderSectionContent(currentSectionIndex, false);
$('#editBtn').style.display = 'inline-block';
$('#saveBtn').style.display = 'none';
}
}
// 섹션 저장
function saveSection() {
if (currentSectionIndex !== 0) return;
// 편집된 값 가져오기
const updatedData = {
purpose: $('#edit_purpose').value,
attendees: $('#edit_attendees').value,
datetime: $('#edit_datetime').value,
location: $('#edit_location').value
};
// 데이터 업데이트
sectionData[currentSectionIndex] = updatedData;
// 보기 모드로 전환
isEditMode = false;
renderSectionContent(currentSectionIndex, false);
$('#editBtn').style.display = 'inline-block';
$('#saveBtn').style.display = 'none';
// 성공 메시지
showToast('회의 개요가 저장되었습니다', 'success');
}
// 모두 검증 완료
function completeAllVerification() {
if (confirm('모든 섹션 검증을 완료하고 회의록을 확정하시겠습니까?')) {
showToast('회의록이 최종 확정되었습니다', 'success');
// 회의 종료 화면 또는 대시보드로 이동
setTimeout(() => {
alert('회의록이 확정되었습니다.\n참석자들에게 알림이 전송되었습니다.');
// navigateTo('01-대시보드.html');
}, 1500);
}
}
// 나중에 하기
function saveLater() {
if (confirm('검증을 나중에 완료하시겠습니까?\n회의록은 임시 저장됩니다.')) {
// 회의록 상태를 '작성중'으로 저장
// 실제로는 Meeting Service API 호출하여 임시 저장
showToast('회의록이 임시 저장되었습니다', 'info');
// 대시보드로 이동
setTimeout(() => {
navigateTo('02-대시보드.html');
}, 1000);
}
}
// 초기 진행률 업데이트
updateProgress();
</script>
</body>
</html>
+178 -230
View File
@@ -25,92 +25,85 @@
color: var(--gray-700);
}
/* 통계 카드 그리드 */
/* 통계 카드 그리드 - 10-회의록상세조회와 동일한 디자인 */
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-md);
margin-bottom: var(--space-lg);
}
.stat-card {
background: var(--white);
padding: var(--space-md);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
/* 모바일에서 gap 축소 */
@media (max-width: 600px) {
.stats-grid {
gap: var(--space-xs);
}
.stat-item {
padding: var(--space-sm);
}
.stat-value {
font-size: var(--font-base);
}
.stat-label {
font-size: var(--font-xs);
}
}
.stat-item {
text-align: center;
padding: var(--space-lg);
background: var(--gray-100);
border-radius: var(--radius-md);
}
.stat-value {
font-size: var(--font-h1);
font-weight: var(--font-weight-bold);
color: var(--primary);
margin-bottom: var(--space-xs);
margin-bottom: var(--space-sm);
}
.stat-label {
font-size: var(--font-small);
color: var(--gray-500);
}
/* 키워드 클라우드 */
.keyword-cloud {
display: flex;
flex-wrap: wrap;
gap: var(--space-sm);
padding: var(--space-md);
}
.keyword-tag {
display: inline-block;
padding: 6px 12px;
background: var(--primary-light);
color: var(--primary-dark);
border-radius: 16px;
font-size: var(--font-small);
font-size: var(--font-body);
color: var(--gray-600);
font-weight: var(--font-weight-medium);
}
/* 발언 통계 바 차트 */
.speaker-stats {
padding: var(--space-md) 0;
/* 키워드 섹션 - 10-회의록상세조회와 동일한 디자인 */
.keywords-section {
margin-bottom: 0;
}
.speaker-item {
display: flex;
align-items: center;
gap: var(--space-md);
.keywords-title {
font-size: var(--font-h4);
font-weight: var(--font-weight-bold);
color: var(--gray-900);
margin-bottom: var(--space-md);
}
.speaker-info {
.keyword-tags {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: var(--space-sm);
min-width: 120px;
margin: var(--space-md) 0;
}
.speaker-bar-container {
flex: 1;
height: 32px;
background: var(--gray-100);
.keyword-tag {
padding: 6px 12px;
background: var(--primary-light);
color: var(--primary);
border-radius: 16px;
overflow: hidden;
position: relative;
font-size: var(--font-small);
cursor: pointer;
transition: all var(--transition-fast);
}
.speaker-bar {
height: 100%;
.keyword-tag:hover {
background: var(--primary);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: var(--space-sm);
color: var(--white);
font-size: var(--font-caption);
font-weight: var(--font-weight-bold);
transition: width 1s ease;
}
/* 안건 카드 */
@@ -182,99 +175,69 @@
}
.agenda-section {
margin-bottom: var(--space-md);
margin-bottom: var(--space-lg);
padding-bottom: var(--space-md);
border-bottom: 1px solid var(--gray-200);
}
.agenda-section:last-child {
border-bottom: none;
padding-bottom: 0;
}
.agenda-section-title {
font-size: var(--font-small);
font-weight: var(--font-weight-bold);
color: var(--gray-500);
margin-bottom: var(--space-xs);
text-transform: uppercase;
color: var(--gray-900);
margin-bottom: var(--space-sm);
display: flex;
align-items: center;
gap: var(--space-xs);
}
.agenda-section-title::before {
content: '';
display: inline-block;
width: 4px;
height: 16px;
background: var(--primary);
border-radius: 2px;
}
.agenda-section-content {
font-size: var(--font-body);
color: var(--gray-700);
line-height: 1.6;
padding-left: var(--space-md);
}
/* Todo 리스트 (읽기 전용) */
.todo-list-item {
padding: var(--space-md);
background: var(--gray-50);
border-radius: var(--radius-md);
margin-bottom: var(--space-sm);
opacity: 0.8;
.agenda-section-content ul {
margin: 0;
padding-left: var(--space-lg);
}
.todo-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: var(--space-sm);
.agenda-section-content li {
margin-bottom: var(--space-xs);
}
.todo-checkbox {
margin-right: var(--space-sm);
cursor: not-allowed;
}
.todo-content {
flex: 1;
font-weight: var(--font-weight-medium);
color: var(--gray-700);
}
.todo-meta {
display: flex;
align-items: center;
gap: var(--space-md);
font-size: var(--font-small);
color: var(--gray-500);
}
/* 하단 액션 바 - 3개 버튼 배치 */
.action-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: var(--white);
padding: var(--space-md);
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
display: flex;
gap: var(--space-sm);
z-index: 100;
}
.action-bar .btn {
/* 하단 버튼 비율 조정 (1:2:1) */
.action-bar .btn:nth-child(1) {
flex: 1;
}
.action-bar .btn-primary {
flex: 2; /* 바로 최종 확정 버튼 강조 */
.action-bar .btn:nth-child(2) {
flex: 2;
}
.action-bar .btn:nth-child(3) {
flex: 1;
}
/* 데스크톱 반응형 */
@media (min-width: 768px) {
.stats-grid {
grid-template-columns: repeat(4, 1fr);
}
.action-bar {
position: static;
box-shadow: none;
justify-content: center;
gap: var(--space-md);
}
.action-bar .btn {
flex: 0 1 200px;
}
.action-bar .btn-primary {
flex: 0 1 250px;
}
}
.readonly-notice {
@@ -303,44 +266,42 @@
🔒 이 화면은 <strong>확인 전용</strong>입니다. 내용을 수정하려면 "회의록 수정" 버튼을 클릭하세요.
</div>
<!-- 통계 카드 그리-->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value" id="durationValue">0</div>
<div class="stat-label">회의 시간 (분)</div>
<!-- 회의 통계 및 키워드 카-->
<div class="card mb-lg">
<!-- 통계 카드 그리드 -->
<div class="stats-grid">
<div class="stat-item">
<div class="stat-value" id="participantsValue">4명</div>
<div class="stat-label">참석자</div>
</div>
<div class="stat-item">
<div class="stat-value" id="durationValue">90분</div>
<div class="stat-label">회의 시간</div>
</div>
<div class="stat-item">
<div class="stat-value" id="agendasValue">3개</div>
<div class="stat-label">주요 안건</div>
</div>
<div class="stat-item">
<div class="stat-value" id="todosValue">5개</div>
<div class="stat-label">Todo 생성</div>
</div>
</div>
<div class="stat-card">
<div class="stat-value" id="participantsValue">0</div>
<div class="stat-label">참석자</div>
</div>
<div class="stat-card">
<div class="stat-value" id="agendasValue">0</div>
<div class="stat-label">안건</div>
</div>
<div class="stat-card">
<div class="stat-value" id="todosValue">0</div>
<div class="stat-label">Todo</div>
<!-- 주요 키워드 -->
<div class="keywords-section">
<h3 class="keywords-title">주요 키워드</h3>
<div class="keyword-tags">
<span class="keyword-tag">#신제품기획</span>
<span class="keyword-tag">#예산편성</span>
<span class="keyword-tag">#일정조율</span>
<span class="keyword-tag">#시장조사</span>
<span class="keyword-tag">#UI/UX</span>
<span class="keyword-tag">#개발스펙</span>
</div>
</div>
</div>
<!-- 주요 키워드 -->
<div class="card mb-md">
<h3 class="card-title">주요 키워드</h3>
<div class="keyword-cloud">
<span class="keyword-tag">신제품 기획</span>
<span class="keyword-tag">예산 편성</span>
<span class="keyword-tag">일정 조율</span>
<span class="keyword-tag">시장 조사</span>
<span class="keyword-tag">UI/UX</span>
<span class="keyword-tag">개발 스펙</span>
</div>
</div>
<!-- 발언 통계 -->
<div class="card mb-md">
<h3 class="card-title">발언 통계</h3>
<div class="speaker-stats" id="speakerStats"></div>
</div>
<!-- 안건별 AI 요약 -->
<div class="card mb-md">
@@ -352,7 +313,7 @@
<!-- 하단 액션 바 (3가지 선택 옵션) -->
<div class="action-bar">
<button class="btn btn-ghost" onclick="navigateTo('02-대시보드.html')">
<button class="btn btn-neutral" onclick="navigateTo('02-대시보드.html')">
대시보드
</button>
<button class="btn btn-secondary" onclick="navigateTo('11-회의록수정.html')">
@@ -459,9 +420,6 @@
const totalTodos = SAMPLE_AGENDAS.reduce((sum, agenda) => sum + (agenda.todos?.length || 0), 0);
animateCounter('todosValue', totalTodos);
// 발언 통계 렌더링
renderSpeakerStats();
// 안건 리스트 렌더링
renderAgendaList();
}
@@ -482,42 +440,6 @@
}, 30);
}
// 발언 통계 렌더링
function renderSpeakerStats() {
const stats = [
{ user: SAMPLE_MEETINGS[0].participants[0], count: 15, duration: 35 },
{ user: SAMPLE_MEETINGS[0].participants[1], count: 12, duration: 28 },
{ user: SAMPLE_MEETINGS[0].participants[2], count: 10, duration: 20 },
{ user: SAMPLE_MEETINGS[0].participants[3], count: 8, duration: 17 }
];
const maxDuration = Math.max(...stats.map(s => s.duration));
const container = $('#speakerStats');
stats.forEach(stat => {
const percentage = (stat.duration / maxDuration) * 100;
const item = createElement('div', { className: 'speaker-item' }, `
<div class="speaker-info">
${createAvatar(stat.user, 'sm')}
<span class="text-small">${stat.user.name}</span>
</div>
<div class="speaker-bar-container">
<div class="speaker-bar" style="width: 0%;" data-width="${percentage}">
${stat.duration}
</div>
</div>
`);
container.appendChild(item);
});
// 애니메이션 시작
setTimeout(() => {
$$('.speaker-bar').forEach(bar => {
bar.style.width = bar.dataset.width + '%';
});
}, 100);
}
// 안건 리스트 렌더링
function renderAgendaList() {
const container = $('#agendaList');
@@ -554,59 +476,85 @@
`));
}
// 발언자별 의견
if (agenda.details.opinions && agenda.details.opinions.length > 0) {
const opinionsHtml = agenda.details.opinions.map(op =>
`<li><strong>${op.speaker}:</strong> ${op.opinion}</li>`
).join('');
content.appendChild(createElement('div', { className: 'agenda-section' }, `
<div class="agenda-section-title">발언자별 의견</div>
<div class="agenda-section-content"><ul>${opinionsHtml}</ul></div>
`));
}
// 결정 사항
if (agenda.details.decisions && agenda.details.decisions.length > 0) {
const decisionsHtml = agenda.details.decisions.map(d => `<li>✓ ${d}</li>`).join('');
content.appendChild(createElement('div', { className: 'agenda-section' }, `
const decisionsSection = createElement('div', { className: 'agenda-section' }, `
<div class="agenda-section-title">결정 사항</div>
<div class="agenda-section-content"><ul>${decisionsHtml}</ul></div>
`));
`);
const decisionsList = createElement('ul', {
className: 'agenda-section-content',
style: 'list-style: none; padding: 0; margin: 0; padding-left: var(--space-md);'
});
agenda.details.decisions.forEach(decision => {
const decisionItem = createElement('li', {
style: 'display: flex; align-items: flex-start; gap: var(--space-sm); padding: var(--space-xs) 0; font-size: var(--font-body); color: var(--gray-700);'
}, `
<span style="color: var(--gray-500); margin-top: 2px;">•</span>
<span style="flex: 1;">${decision}</span>
`);
decisionsList.appendChild(decisionItem);
});
decisionsSection.appendChild(decisionsList);
content.appendChild(decisionsSection);
}
// 보류 사항
if (agenda.details.pending && agenda.details.pending.length > 0) {
const pendingHtml = agenda.details.pending.map(p => `<li>⏸ ${p}</li>`).join('');
content.appendChild(createElement('div', { className: 'agenda-section' }, `
const pendingSection = createElement('div', { className: 'agenda-section' }, `
<div class="agenda-section-title">보류 사항</div>
<div class="agenda-section-content"><ul>${pendingHtml}</ul></div>
`));
`);
const pendingList = createElement('ul', {
className: 'agenda-section-content',
style: 'list-style: none; padding: 0; margin: 0; padding-left: var(--space-md);'
});
agenda.details.pending.forEach(pending => {
const pendingItem = createElement('li', {
style: 'display: flex; align-items: flex-start; gap: var(--space-sm); padding: var(--space-xs) 0; font-size: var(--font-body); color: var(--gray-700);'
}, `
<span style="color: var(--gray-500); margin-top: 2px;">•</span>
<span style="flex: 1;">${pending}</span>
`);
pendingList.appendChild(pendingItem);
});
pendingSection.appendChild(pendingList);
content.appendChild(pendingSection);
}
// Todo 목록
// Todo 목록 - 제목만 간단히 표시
if (agenda.todos && agenda.todos.length > 0) {
const todosSection = createElement('div', { className: 'agenda-section' }, `
<div class="agenda-section-title">Todo 자동 추출 결과</div>
`);
agenda.todos.forEach(todo => {
const todoItem = createElement('div', { className: 'todo-list-item' }, `
<div class="todo-header">
<input type="checkbox" class="todo-checkbox" disabled>
<div class="todo-content">${todo.title}</div>
${createBadge(todo.priority === 'high' ? '높음' : todo.priority === 'medium' ? '보통' : '낮음',
`priority-${todo.priority}`)}
</div>
<div class="todo-meta">
${createAvatar(todo.assignee, 'sm')}
<span>${todo.assignee.name}</span>
<span>•</span>
<span>${formatDate(todo.dueDate)}</span>
</div>
`);
todosSection.appendChild(todoItem);
const todoList = createElement('ul', {
className: 'agenda-section-content',
style: 'list-style: none; padding: 0; margin: 0; padding-left: var(--space-md);'
});
agenda.todos.forEach(todo => {
const todoItem = createElement('li', {
style: 'display: flex; align-items: flex-start; gap: var(--space-sm); padding: var(--space-xs) 0; font-size: var(--font-body); color: var(--gray-700);'
}, `
<span style="color: var(--gray-500); margin-top: 2px;">•</span>
<span style="flex: 1;">${todo.title}</span>
`);
todoList.appendChild(todoItem);
});
todosSection.appendChild(todoList);
// 안내 문구 추가
const notice = createElement('p', {
style: 'font-size: var(--font-small); color: var(--gray-500); margin-top: var(--space-md); padding-top: var(--space-sm); border-top: 1px solid var(--gray-200);'
}, '💡 담당자 및 마감일은 회의록 수정 화면에서 지정할 수 있습니다.');
todosSection.appendChild(notice);
content.appendChild(todosSection);
}
@@ -165,6 +165,23 @@
.participant {
width: calc(50% - var(--space-md) / 2);
}
/* 통계 그리드: 모바일에서도 4열 유지, gap만 축소 */
.stats-grid {
gap: var(--space-xs);
}
.stat-item {
padding: var(--space-sm);
}
.stat-value {
font-size: var(--font-base);
}
.stat-label {
font-size: var(--font-xs);
}
}
/* 회의록 섹션 */
@@ -333,9 +350,6 @@
}
/* 대시보드 탭 콘텐츠 */
.dashboard-section {
margin-bottom: var(--space-lg);
}
.key-points {
list-style: none;
@@ -393,7 +407,7 @@
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-columns: repeat(4, 1fr);
gap: var(--space-md);
margin-top: var(--space-md);
}
@@ -724,7 +738,11 @@
<!-- 기본 정보 카드 -->
<div class="info-card">
<div class="meeting-basic-info">
<h2>2025년 1분기 제품 기획 회의</h2>
<div id="meeting-title-container" style="display: flex; align-items: center; gap: var(--space-sm); margin-bottom: var(--space-sm);">
<span class="badge badge-complete">확정완료</span>
<!-- 생성자일 경우 👑 아이콘이 JavaScript로 추가됨 -->
<h2 style="margin: 0;">2025년 1분기 제품 기획 회의</h2>
</div>
<div class="info-row">
<span class="info-icon">📅</span>
<span>2025년 10월 25일 14:00 (90분)</span>
@@ -733,10 +751,6 @@
<span class="info-icon">📍</span>
<span>본사 2층 대회의실</span>
</div>
<div class="info-row">
<span class="info-icon"></span>
<span class="badge badge-complete">확정완료</span>
</div>
</div>
<div class="participants-list">
@@ -951,10 +965,8 @@
<!-- 대시보드 탭 (기본 노출 탭 - 유저스토리 UFR-MEET-047 요구사항) -->
<div id="dashboard-content" class="tab-content active">
<!-- 핵심내용 -->
<div class="section dashboard-section">
<div class="section-header">
<h3 class="section-title">💡 핵심내용</h3>
</div>
<div class="card mb-lg">
<h3 class="card-title">💡 핵심내용</h3>
<ol class="key-points">
<li class="key-point">
@@ -1004,10 +1016,8 @@
</div>
<!-- 결정사항 -->
<div class="section dashboard-section">
<div class="section-header">
<h3 class="section-title">✅ 결정사항</h3>
</div>
<div class="card mb-lg">
<h3 class="card-title">✅ 결정사항</h3>
<div class="decision-card">
<div class="decision-content">베타 버전 출시일: 2025년 12월 1일</div>
@@ -1033,10 +1043,8 @@
</div>
<!-- Todo 진행상황 -->
<div class="section dashboard-section">
<div class="section-header">
<h3 class="section-title">📋 Todo 진행상황</h3>
</div>
<div class="card mb-lg">
<h3 class="card-title">📋 Todo 진행상황</h3>
<!-- 전체 진행률 -->
<div style="margin-bottom: var(--space-lg);">
@@ -1182,10 +1190,8 @@
</div>
<!-- 관련회의록 -->
<div class="section dashboard-section">
<div class="section-header">
<h3 class="section-title">📚 관련회의록 (3건)</h3>
</div>
<div class="card mb-lg">
<h3 class="card-title">📚 관련회의록 (3건)</h3>
<div class="reference-item" onclick="window.open('10-회의록상세조회.html', '_blank')">
<div class="reference-header">
@@ -1729,6 +1735,20 @@
* 페이지 초기화
*/
function initPage() {
// 회의 생성자 확인 후 👑 표시
const currentUser = '김민준'; // 현재 로그인 사용자
const isCreator = checkIfUserIsCreator(CURRENT_MEETING_ID, currentUser);
if (isCreator) {
const titleContainer = document.getElementById('meeting-title-container');
const badge = titleContainer.querySelector('.badge');
const crownIcon = document.createElement('span');
crownIcon.textContent = '👑';
crownIcon.style.fontSize = '24px';
// badge 다음에 👑 삽입
badge.insertAdjacentElement('afterend', crownIcon);
}
updateTodoProgress();
updateFilterCounts();
}
+28 -5
View File
@@ -179,6 +179,7 @@ a:hover {
font-size: var(--font-h3);
font-weight: var(--font-weight-bold);
color: var(--gray-900);
margin-bottom: var(--space-md);
}
.card-body {
@@ -258,6 +259,28 @@ a:hover {
background: var(--gray-100);
}
/* Outline Button (회색 테두리) */
.btn-outline {
background: var(--white);
color: var(--gray-700);
border: 1px solid var(--gray-500);
}
.btn-outline:hover:not(:disabled) {
background: var(--gray-50);
border-color: var(--gray-600);
}
/* Neutral Button (진한 회색 배경) */
.btn-neutral {
background: #424242;
color: var(--white);
}
.btn-neutral:hover:not(:disabled) {
background: var(--gray-900);
}
/* Error Button */
.btn-error {
background: var(--error);
@@ -1238,9 +1261,9 @@ input[type="date"]::-webkit-calendar-picker-indicator {
}
/* ========================================
22. 섹션 카드 컴포넌트
22. 안건 카드 컴포넌트
======================================== */
.section {
.agenda {
background: var(--white);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
@@ -1248,14 +1271,14 @@ input[type="date"]::-webkit-calendar-picker-indicator {
margin-bottom: var(--space-lg);
}
.section-header {
.agenda-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-md);
}
.section-title {
.agenda-title {
font-size: var(--font-h3);
font-weight: var(--font-weight-bold);
color: var(--gray-900);
@@ -1264,7 +1287,7 @@ input[type="date"]::-webkit-calendar-picker-indicator {
gap: var(--space-sm);
}
.section-content {
.agenda-content {
color: var(--gray-700);
line-height: 1.6;
}