main 브랜치 최신 변경사항 병합

- 유저스토리 v2.3.0 업데이트 반영
- UI/UX 프로토타입 개선사항 반영
- Meeting 서비스 기능 추가 및 개선
- Notification 서비스 개선
- User 서비스 LDAP 인증 추가
- 공통 모듈 에러 코드 추가

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Minseo-Jo
2025-10-27 13:18:45 +09:00
143 changed files with 34105 additions and 13358 deletions
+54 -229
View File
@@ -13,6 +13,32 @@
padding-bottom: 80px;
}
/* 템플릿 그리드 */
.template-list {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--space-md);
}
/* 템플릿 카드 높이 균등 */
.template-list .card {
display: flex;
flex-direction: column;
height: 100%;
}
.template-list .card-body {
flex: 1;
}
/* 모바일: 작은 간격 */
@media (max-width: 767px) {
.template-list {
gap: var(--space-sm);
}
}
/* 데스크톱: 메인 콘텐츠 조정 */
@media (min-width: 768px) {
.main-content {
@@ -41,18 +67,16 @@
<p class="text-muted">회의 유형에 맞는 템플릿을 선택하세요</p>
</div>
<!-- Template Cards -->
<div class="template-list" style="display: flex; flex-direction: column; gap: var(--space-md);">
<!-- Template Cards (2x2 grid) -->
<div class="template-list">
<!-- 일반 회의 템플릿 -->
<div class="card" style="cursor: pointer;" onclick="selectTemplate('general')">
<div class="card-header">
<div style="display: flex; align-items: center; gap: var(--space-md);">
<div style="font-size: 32px;">📋</div>
<div>
<h3 class="card-title">일반 회의</h3>
<p class="text-muted text-small">기본 회의록 형식</p>
</div>
<div class="card-header" style="display: block !important;">
<div style="margin-bottom: 4px;">
<span style="font-size: 20px;">📋</span>
<span style="font-size: 14px; font-weight: 600; margin-left: 4px;">일반 회의</span>
</div>
<div style="font-size: 11px; color: var(--text-muted);">기본 회의록 형식</div>
</div>
<div class="card-body">
<div class="text-small text-muted">
@@ -63,21 +87,18 @@
</div>
</div>
<div class="card-footer">
<button class="btn btn-secondary btn-sm" onclick="previewTemplate(event, 'general')">미리보기</button>
<button class="btn btn-primary btn-sm" onclick="selectTemplate('general')">선택</button>
<button class="btn btn-primary btn-sm" onclick="selectTemplate('general')" style="width: 100%;">선택</button>
</div>
</div>
<!-- 스크럼 회의 템플릿 -->
<div class="card" style="cursor: pointer;" onclick="selectTemplate('scrum')">
<div class="card-header">
<div style="display: flex; align-items: center; gap: var(--space-md);">
<div style="font-size: 32px;">🏃</div>
<div>
<h3 class="card-title">스크럼 회의</h3>
<p class="text-muted text-small">데일리 스탠드업 형식</p>
</div>
<div class="card-header" style="display: block !important;">
<div style="margin-bottom: 4px;">
<span style="font-size: 20px;">🏃</span>
<span style="font-size: 14px; font-weight: 600; margin-left: 4px;">스크럼 회의</span>
</div>
<div style="font-size: 11px; color: var(--text-muted);">데일리 스탠드업 형식</div>
</div>
<div class="card-body">
<div class="text-small text-muted">
@@ -87,21 +108,18 @@
</div>
</div>
<div class="card-footer">
<button class="btn btn-secondary btn-sm" onclick="previewTemplate(event, 'scrum')">미리보기</button>
<button class="btn btn-primary btn-sm" onclick="selectTemplate('scrum')">선택</button>
<button class="btn btn-primary btn-sm" onclick="selectTemplate('scrum')" style="width: 100%;">선택</button>
</div>
</div>
<!-- 킥오프 회의 템플릿 -->
<div class="card" style="cursor: pointer;" onclick="selectTemplate('kickoff')">
<div class="card-header">
<div style="display: flex; align-items: center; gap: var(--space-md);">
<div style="font-size: 32px;">🚀</div>
<div>
<h3 class="card-title">킥오프 회의</h3>
<p class="text-muted text-small">프로젝트 시작 회의</p>
</div>
<div class="card-header" style="display: block !important;">
<div style="margin-bottom: 4px;">
<span style="font-size: 20px;">🚀</span>
<span style="font-size: 14px; font-weight: 600; margin-left: 4px;">킥오프 회의</span>
</div>
<div style="font-size: 11px; color: var(--text-muted);">프로젝트 시작 회의</div>
</div>
<div class="card-body">
<div class="text-small text-muted">
@@ -112,21 +130,18 @@
</div>
</div>
<div class="card-footer">
<button class="btn btn-secondary btn-sm" onclick="previewTemplate(event, 'kickoff')">미리보기</button>
<button class="btn btn-primary btn-sm" onclick="selectTemplate('kickoff')">선택</button>
<button class="btn btn-primary btn-sm" onclick="selectTemplate('kickoff')" style="width: 100%;">선택</button>
</div>
</div>
<!-- 주간 회의 템플릿 -->
<div class="card" style="cursor: pointer;" onclick="selectTemplate('weekly')">
<div class="card-header">
<div style="display: flex; align-items: center; gap: var(--space-md);">
<div style="font-size: 32px;">📅</div>
<div>
<h3 class="card-title">주간 회의</h3>
<p class="text-muted text-small">주간 리뷰 및 계획</p>
</div>
<div class="card-header" style="display: block !important;">
<div style="margin-bottom: 4px;">
<span style="font-size: 20px;">📅</span>
<span style="font-size: 14px; font-weight: 600; margin-left: 4px;">주간 회의</span>
</div>
<div style="font-size: 11px; color: var(--text-muted);">주간 리뷰 및 계획</div>
</div>
<div class="card-body">
<div class="text-small text-muted">
@@ -137,72 +152,12 @@
</div>
</div>
<div class="card-footer">
<button class="btn btn-secondary btn-sm" onclick="previewTemplate(event, 'weekly')">미리보기</button>
<button class="btn btn-primary btn-sm" onclick="selectTemplate('weekly')">선택</button>
<button class="btn btn-primary btn-sm" onclick="selectTemplate('weekly')" style="width: 100%;">선택</button>
</div>
</div>
</div>
</main>
<!-- Template Preview Modal -->
<div id="previewModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h2 class="modal-title" id="previewTitle">템플릿 미리보기</h2>
<button class="modal-close" onclick="closeModal('previewModal')"></button>
</div>
<div class="modal-body">
<div id="previewContent" style="max-height: 400px; overflow-y: auto;">
<!-- 섹션 리스트가 여기에 표시됨 -->
</div>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="closeModal('previewModal')">닫기</button>
<button class="btn btn-primary" onclick="customizeTemplate()">커스터마이징</button>
</div>
</div>
</div>
<!-- Template Customization Modal -->
<div id="customizeModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h2 class="modal-title">템플릿 커스터마이징</h2>
<button class="modal-close" onclick="closeModal('customizeModal')"></button>
</div>
<div class="modal-body">
<p class="text-small text-muted mb-md">섹션을 드래그하여 순서를 변경하거나 삭제할 수 있습니다</p>
<div id="sectionList" style="display: flex; flex-direction: column; gap: var(--space-sm);">
<!-- 섹션 목록이 여기에 표시됨 -->
</div>
<button class="btn btn-ghost mt-md" onclick="addSection()" style="width: 100%;">+ 섹션 추가</button>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="closeModal('customizeModal')">취소</button>
<button class="btn btn-primary" onclick="startMeeting()">이 템플릿으로 시작</button>
</div>
</div>
</div>
<!-- Add Section Modal -->
<div id="addSectionModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h2 class="modal-title">섹션 추가</h2>
<button class="modal-close" onclick="closeModal('addSectionModal')"></button>
</div>
<div class="modal-body">
<div class="form-group">
<label class="form-label">섹션 이름</label>
<input type="text" class="form-control" id="newSectionName" placeholder="예: 기술 검토">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="closeModal('addSectionModal')">취소</button>
<button class="btn btn-primary" onclick="confirmAddSection()">추가</button>
</div>
</div>
</div>
<script src="common.js"></script>
<script>
@@ -230,35 +185,13 @@
}
};
let selectedTemplate = null;
let customSections = [];
// 템플릿 미리보기
function previewTemplate(event, templateId) {
event.stopPropagation();
const template = templates[templateId];
$('#previewTitle').textContent = template.name + ' 미리보기';
const content = template.sections.map((section, index) => `
<div class="list-item">
<span class="text-muted text-small">${index + 1}.</span>
<span class="list-item-title">${section}</span>
</div>
`).join('');
$('#previewContent').innerHTML = content;
openModal('previewModal');
}
// 템플릿 선택
function selectTemplate(templateId) {
selectedTemplate = templateId;
customSections = [...templates[templateId].sections];
const templateSections = [...templates[templateId].sections];
// 회의 진행 화면으로 이동
saveToStorage('selectedTemplate', templateId);
saveToStorage('templateSections', customSections);
saveToStorage('templateSections', templateSections);
navigateTo('05-회의진행.html');
}
@@ -276,114 +209,6 @@
$('#skip-btn')?.addEventListener('click', () => {
skipTemplate();
});
// 커스터마이징 모달 열기
function customizeTemplate() {
closeModal('previewModal');
renderSectionList();
openModal('customizeModal');
}
// 섹션 리스트 렌더링
function renderSectionList() {
const sectionList = $('#sectionList');
sectionList.innerHTML = customSections.map((section, index) => `
<div class="list-item" draggable="true" data-index="${index}">
<span class="text-muted">☰</span>
<span class="list-item-title" style="flex: 1;">${section}</span>
<button class="btn btn-ghost btn-sm" onclick="removeSection(${index})">
<span style="color: var(--error);">✕</span>
</button>
</div>
`).join('');
// 드래그 이벤트 설정
setupDragAndDrop();
}
// 드래그 앤 드롭 설정
function setupDragAndDrop() {
const items = $$('#sectionList .list-item');
let draggedItem = null;
items.forEach(item => {
item.addEventListener('dragstart', function() {
draggedItem = this;
this.style.opacity = '0.5';
});
item.addEventListener('dragend', function() {
this.style.opacity = '1';
});
item.addEventListener('dragover', function(e) {
e.preventDefault();
});
item.addEventListener('drop', function(e) {
e.preventDefault();
if (draggedItem !== this) {
const draggedIndex = parseInt(draggedItem.dataset.index);
const targetIndex = parseInt(this.dataset.index);
const temp = customSections[draggedIndex];
customSections.splice(draggedIndex, 1);
customSections.splice(targetIndex, 0, temp);
renderSectionList();
}
});
});
}
// 섹션 삭제
function removeSection(index) {
if (customSections.length <= 1) {
showToast('최소 1개의 섹션이 필요합니다', 'error');
return;
}
customSections.splice(index, 1);
renderSectionList();
}
// 섹션 추가 모달 열기
function addSection() {
openModal('addSectionModal');
$('#newSectionName').value = '';
$('#newSectionName').focus();
}
// 섹션 추가 확인
function confirmAddSection() {
const name = $('#newSectionName').value.trim();
if (!name) {
showToast('섹션 이름을 입력해주세요', 'error');
return;
}
customSections.push(name);
renderSectionList();
closeModal('addSectionModal');
showToast('섹션이 추가되었습니다', 'success');
}
// 회의 시작
function startMeeting() {
if (customSections.length === 0) {
showToast('최소 1개의 섹션이 필요합니다', 'error');
return;
}
saveToStorage('selectedTemplate', selectedTemplate);
saveToStorage('templateSections', customSections);
navigateTo('05-회의진행.html');
}
// Enter 키로 섹션 추가
$('#addSectionModal')?.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
confirmAddSection();
}
});
</script>
</body>
</html>
+299 -151
View File
@@ -94,6 +94,11 @@
50% { opacity: 0.3; }
}
.recording-indicator.paused .recording-dot,
.recording-indicator.paused .waveform-bar {
animation-play-state: paused;
}
.recording-time {
font-size: var(--font-small);
font-weight: var(--font-weight-medium);
@@ -129,18 +134,23 @@
flex: 1;
overflow-y: auto;
background: var(--gray-100);
padding: 0 var(--space-md) 10px;
padding: var(--space-md);
padding-bottom: 88px; /* 하단 버튼 영역 확보 */
max-width: none !important; /* common.css의 max-width: 900px 오버라이드 */
margin: 0 !important; /* common.css의 auto margin 제거 */
}
@media (min-width: 768px) {
.main-content {
padding: 0 var(--space-lg) 88px;
padding: var(--space-lg);
padding-bottom: 88px;
}
}
@media (min-width: 1024px) {
.main-content {
padding: 0 var(--space-xl) 88px;
padding: var(--space-xl);
padding-bottom: 88px;
}
}
@@ -151,6 +161,8 @@
padding: var(--space-md);
margin-bottom: var(--space-md);
box-shadow: var(--shadow-md);
width: 100%;
box-sizing: border-box;
}
.meeting-info-grid {
@@ -192,6 +204,11 @@
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
margin-bottom: var(--space-md);
width: 100%;
max-width: 100%;
min-width: 0;
box-sizing: border-box;
display: block;
}
.tabs-header {
@@ -248,22 +265,19 @@
.tab-content {
display: none;
padding: var(--space-md);
width: 100%;
box-sizing: border-box;
}
.tab-content.active {
display: block;
}
@media (min-width: 768px) {
.tab-content {
padding: var(--space-lg);
}
}
@media (min-width: 1024px) {
.tab-content {
padding: var(--space-xl);
}
/* 모든 탭 콘텐츠 내부 요소 텍스트 줄바꿈 강제 */
.tab-content * {
max-width: 100%;
word-wrap: break-word;
overflow-wrap: break-word;
}
/* 참석자 탭 */
@@ -437,6 +451,26 @@
line-height: 1.5;
}
/* 용어 검색 폼 */
.term-search-form {
display: flex;
gap: var(--space-xs);
margin-bottom: var(--space-sm);
}
.term-search-input {
flex: 1;
font-size: var(--font-small);
padding: var(--space-sm);
}
.term-search-btn {
padding: var(--space-sm) var(--space-md);
font-size: var(--font-small);
white-space: nowrap;
flex-shrink: 0;
}
/* 용어 사전 카드 */
.term-item {
background: #FAFAFA;
@@ -564,6 +598,11 @@
height: 32px;
}
.rec-icon {
width: 32px;
height: 32px;
}
.end-meeting-btn {
flex: 1;
font-size: var(--font-body);
@@ -580,7 +619,7 @@
</div>
<div class="recording-status-bar">
<div class="recording-indicator">
<div class="recording-indicator" id="recordingIndicator">
<span class="recording-dot"></span>
<span class="recording-time" id="recordingTime">00:15:51</span>
<div class="waveform">
@@ -708,13 +747,11 @@
</div>
<h4 class="ai-suggestion-list-title">
💬 AI가 실시간으로 분석한 제안사항
💬 AI가 실시간으로 분석한 주요 내용
</h4>
<div id="aiSuggestionList">
<!-- AI 제안사항이 실시간으로 추가됩니다 -->
<!-- 백업용 정적 샘플 데이터 (SSE 연결 실패 시 표시)
<!-- AI 제안 1 -->
<div class="ai-suggestion-card" id="suggestion-1">
<div class="ai-suggestion-header">
<span class="ai-suggestion-time">00:05:23</span>
@@ -727,6 +764,7 @@
</div>
</div>
<!-- AI 제안 2 -->
<div class="ai-suggestion-card" id="suggestion-2">
<div class="ai-suggestion-header">
<span class="ai-suggestion-time">00:08:45</span>
@@ -739,6 +777,7 @@
</div>
</div>
<!-- AI 제안 3 -->
<div class="ai-suggestion-card" id="suggestion-3">
<div class="ai-suggestion-header">
<span class="ai-suggestion-time">00:12:18</span>
@@ -750,7 +789,6 @@
마케팅 예산 배분에 대해 SNS 광고 60%, 인플루언서 마케팅 40%로 의견이 나왔으나 추가 검토 필요
</div>
</div>
-->
</div>
</div>
@@ -758,8 +796,21 @@
<div class="tab-content" id="tab-terms">
<h3 class="text-small font-bold mb-md">용어 사전</h3>
<!-- 용어 검색 폼 -->
<div class="term-search-form">
<input
type="text"
class="form-control term-search-input"
id="termSearchInput"
placeholder="용어 검색..."
>
<button class="btn btn-primary btn-sm term-search-btn" onclick="searchTerm()">
검색
</button>
</div>
<div id="termsList">
<div class="term-item" onclick="showTermDetail('MVP')">
<div class="term-item highlight" onclick="showTermDetail('MVP')">
<div class="term-name">
MVP
<span class="term-badge">기획</span>
@@ -770,7 +821,7 @@
<div class="term-context">신제품 기획 회의에서 언급</div>
</div>
<div class="term-item" onclick="showTermDetail('B2C')">
<div class="term-item highlight" onclick="showTermDetail('B2C')">
<div class="term-name">
B2C
<span class="term-badge">비즈니스</span>
@@ -781,7 +832,7 @@
<div class="term-context">타겟 고객 분석 시 사용</div>
</div>
<div class="term-item" onclick="showTermDetail('PMF')">
<div class="term-item highlight" onclick="showTermDetail('PMF')">
<div class="term-name">
PMF
<span class="term-badge">전략</span>
@@ -792,7 +843,7 @@
<div class="term-context">제품 전략 논의 중 언급</div>
</div>
<div class="term-item" onclick="showTermDetail('CAC')">
<div class="term-item highlight" onclick="showTermDetail('CAC')">
<div class="term-name">
CAC
<span class="term-badge">마케팅</span>
@@ -802,6 +853,102 @@
</div>
<div class="term-context">마케팅 예산 논의에서 사용</div>
</div>
</div>
<div id="termList">
<div class="term-item" onclick="showTermDetail('Mobile First')">
<div class="term-name">
Mobile First
<span class="term-badge">설계 방법론</span>
<span class="term-mention-icon">💬</span>
</div>
<div class="term-definition">
모바일 환경을 우선적으로 고려하여 디자인하고, 이후 더 큰 화면으로 확장하는 설계 방법론입니다.
</div>
<div class="term-context">회의에서 언급됨 (14:23)</div>
</div>
<div class="term-item" onclick="showTermDetail('AI')">
<div class="term-name">
AI
<span class="term-badge">기술</span>
<span class="term-mention-icon">💬</span>
</div>
<div class="term-definition">
Artificial Intelligence의 약자로, 인공지능을 의미합니다. 이 프로젝트에서는 회의록 자동 작성에 활용됩니다.
</div>
<div class="term-context">회의에서 5회 언급됨</div>
</div>
<div class="term-item" onclick="showTermDetail('API')">
<div class="term-name">
API
<span class="term-badge">기술</span>
<span class="term-mention-icon">💬</span>
</div>
<div class="term-definition">
Application Programming Interface의 약자로, 소프트웨어 간 상호작용을 위한 인터페이스입니다.
</div>
<div class="term-context">회의에서 3회 언급됨</div>
</div>
<div class="term-item" onclick="showTermDetail('API Gateway')">
<div class="term-name">
API Gateway
<span class="term-badge">아키텍처</span>
<span class="term-mention-icon">💬</span>
</div>
<div class="term-definition">
클라이언트와 백엔드 마이크로서비스 사이의 단일 진입점 역할을 하는 서버. 요청 라우팅, 인증, 속도 제한, 로드 밸런싱 등을 처리합니다.
</div>
<div class="term-context">API 설계 리뷰 회의 (2024-09-28)에서 AWS API Gateway 채택 결정</div>
</div>
<div class="term-item" onclick="showTermDetail('마이크로서비스')">
<div class="term-name">
마이크로서비스
<span class="term-badge">아키텍처</span>
<span class="term-mention-icon">💬</span>
</div>
<div class="term-definition">
애플리케이션을 작고 독립적인 서비스들로 분리하여 개발하고 배포하는 아키텍처 패턴입니다.
</div>
<div class="term-context">회의에서 언급됨</div>
</div>
<div class="term-item " onclick="showTermDetail('MVP')">
<div class="term-name">
MVP
<span class="term-badge">방법론</span>
<span class="term-mention-icon">💬</span>
</div>
<div class="term-definition">
Minimum Viable Product의 약자. 최소한의 기능만 갖춘 제품으로, 시장 반응을 빠르게 확인하기 위해 개발합니다.
</div>
<div class="term-context">개발 일정 논의에서 언급</div>
</div>
<div class="term-item" onclick="showTermDetail('RESTful API')">
<div class="term-name">
RESTful API
<span class="term-badge">기술</span>
</div>
<div class="term-definition">
REST(Representational State Transfer) 아키텍처 스타일을 따르는 웹 서비스 API 설계 방식입니다.
</div>
<div class="term-context">API 설계 리뷰 회의 참조</div>
</div>
<div class="term-item" onclick="showTermDetail('JWT')">
<div class="term-name">
JWT
<span class="term-badge">보안</span>
</div>
<div class="term-definition">
JSON Web Token의 약자. 사용자 인증 정보를 안전하게 전송하기 위한 토큰 기반 인증 방식입니다.
</div>
<div class="term-context">API Gateway 보안 정책에서 채택</div>
</div>
</div>
</div>
@@ -853,11 +1000,12 @@
<!-- 하단 고정 버튼 영역 -->
<div class="bottom-action-bar">
<button class="btn btn-ghost pause-btn" onclick="pauseRecording()" id="pauseBtn">
<svg class="pause-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<button class="btn btn-ghost pause-btn" onclick="toggleRecording()" id="pauseBtn">
<svg class="pause-icon" id="pauseIcon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="6" y="4" width="4" height="16" rx="1"></rect>
<rect x="14" y="4" width="4" height="16" rx="1"></rect>
</svg>
<img class="rec-icon" id="recIcon" src="img/rec.png" alt="녹음 재개" style="display: none;">
</button>
<button class="btn btn-error end-meeting-btn" onclick="endMeeting()">
회의 종료
@@ -980,9 +1128,104 @@
// });
}
// 녹음 일시정지
function pauseRecording() {
alert('녹음이 일시정지되었습니다');
// 용어 검색
function searchTerm() {
const searchInput = document.getElementById('termSearchInput');
const searchText = searchInput.value.trim().toLowerCase();
if (!searchText) {
alert('검색할 용어를 입력해주세요');
return;
}
const termItems = document.querySelectorAll('.term-item');
let foundCount = 0;
termItems.forEach(item => {
const termName = item.querySelector('.term-name').textContent.toLowerCase();
const termDefinition = item.querySelector('.term-definition').textContent.toLowerCase();
// 용어명 또는 정의에 검색어가 포함되어 있는지 확인
if (termName.includes(searchText) || termDefinition.includes(searchText)) {
item.style.display = '';
item.classList.add('highlight');
foundCount++;
} else {
item.style.display = 'none';
item.classList.remove('highlight');
}
});
if (foundCount === 0) {
alert('검색 결과가 없습니다');
// 모든 항목 다시 표시
termItems.forEach(item => {
item.style.display = '';
item.classList.remove('highlight');
});
searchInput.value = '';
}
}
// Enter 키로 검색 실행
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('termSearchInput');
if (searchInput) {
searchInput.addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
searchTerm();
}
});
// 입력값이 비워지면 전체 표시
searchInput.addEventListener('input', function() {
if (this.value.trim() === '') {
const termItems = document.querySelectorAll('.term-item');
termItems.forEach(item => {
item.style.display = '';
item.classList.remove('highlight');
});
}
});
}
});
// 녹음 상태 관리
let isRecording = true;
let timerInterval = null;
// 녹음 일시정지/재개 토글
function toggleRecording() {
const pauseIcon = document.getElementById('pauseIcon');
const recIcon = document.getElementById('recIcon');
const recordingIndicator = document.getElementById('recordingIndicator');
if (isRecording) {
// 일시정지
isRecording = false;
pauseIcon.style.display = 'none';
recIcon.style.display = 'block';
recordingIndicator.classList.add('paused');
// 타이머 정지
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
}
alert('녹음이 일시정지되었습니다');
} else {
// 재개
isRecording = true;
pauseIcon.style.display = 'block';
recIcon.style.display = 'none';
recordingIndicator.classList.remove('paused');
// 타이머 재시작
startTimer();
alert('녹음이 재개되었습니다');
}
}
// 회의 종료
@@ -991,19 +1234,27 @@
alert('회의가 종료되었습니다. AI가 회의록을 생성 중입니다...');
setTimeout(() => {
window.location.href = '10-회의록상세조회.html';
window.location.href = '07-회의종료.html';
}, 2000);
}
}
// 타이머 업데이트
function updateTimer() {
// 타이머 시작 함수
function startTimer() {
const timerElement = document.getElementById('recordingTime');
let seconds = 51;
let minutes = 15;
let hours = 0;
setInterval(() => {
// 이미 타이머가 실행 중이면 중복 실행 방지
if (timerInterval) {
return;
}
timerInterval = setInterval(() => {
// 현재 시간을 파싱
const currentTime = timerElement.textContent.split(':');
let hours = parseInt(currentTime[0]);
let minutes = parseInt(currentTime[1]);
let seconds = parseInt(currentTime[2]);
seconds++;
if (seconds === 60) {
seconds = 0;
@@ -1023,131 +1274,28 @@
}, 1000);
}
// SSE로 실시간 AI 제안사항 수신
let eventSource = null;
const meetingId = '550e8400-e29b-41d4-a716-446655440000'; // 테스트용 회의 ID
function connectAiSuggestionStream() {
// EventSource를 사용하여 SSE 연결
const apiUrl = `http://localhost:8083/api/suggestions/meetings/${meetingId}/stream`;
console.log('[DEBUG] SSE 연결 시작:', apiUrl);
eventSource = new EventSource(apiUrl);
// 연결 성공
eventSource.onopen = function(event) {
console.log('[SUCCESS] SSE 연결 성공!', event);
};
// 모든 이벤트 수신 (디버깅용)
eventSource.onmessage = function(event) {
console.log('[DEBUG] 일반 메시지 수신:', event.data);
};
// ai-suggestion 이벤트 수신
eventSource.addEventListener('ai-suggestion', function(event) {
console.log('[SUCCESS] AI 제안사항 수신:', event.data);
try {
const data = JSON.parse(event.data);
const suggestions = data.suggestions;
console.log('[DEBUG] 파싱된 데이터:', data);
console.log('[DEBUG] 제안사항 개수:', suggestions ? suggestions.length : 0);
if (suggestions && suggestions.length > 0) {
suggestions.forEach(suggestion => {
console.log('[DEBUG] 제안사항 추가 중:', suggestion);
addAiSuggestionToUI(suggestion);
});
} else {
console.warn('[WARNING] 제안사항이 비어있음');
}
} catch (error) {
console.error('[ERROR] AI 제안사항 파싱 오류:', error);
console.error('[ERROR] 원본 데이터:', event.data);
}
});
// 에러 처리
eventSource.onerror = function(error) {
console.error('[ERROR] SSE 연결 오류:', error);
console.error('[ERROR] ReadyState:', eventSource.readyState);
// ReadyState: 0=CONNECTING, 1=OPEN, 2=CLOSED
if (eventSource.readyState === EventSource.CLOSED) {
console.error('[ERROR] SSE 연결이 닫혔습니다');
} else if (eventSource.readyState === EventSource.CONNECTING) {
console.warn('[WARNING] SSE 재연결 시도 중...');
}
// 에러 발생 시 닫기
eventSource.close();
};
console.log('[INFO] AI 제안사항 SSE 스트림 연결 요청 완료');
// 타이머 초기화 함수
function updateTimer() {
startTimer();
}
// AI 제안사항을 UI에 추가
function addAiSuggestionToUI(suggestion) {
const listContainer = document.getElementById('aiSuggestionList');
// 관련 회의록 열기
function openRelatedDoc(docId) {
// 새 탭으로 회의록 상세조회 화면 열기
window.open('10-회의록상세조회.html', '_blank');
// 고유 ID 생성 (이미 추가된 제안인지 확인용)
const cardId = `suggestion-${suggestion.id}`;
// 이미 존재하는 제안이면 추가하지 않음
if (document.getElementById(cardId)) {
return;
}
// AI 제안 카드 HTML 생성
const cardHtml = `
<div class="ai-suggestion-card" id="${cardId}">
<div class="ai-suggestion-header">
<span class="ai-suggestion-time">${suggestion.timestamp}</span>
<button class="ai-suggestion-add-btn"
onclick="addToMemo('${escapeHtml(suggestion.content)}', document.getElementById('${cardId}'))"
title="메모에 추가">
</button>
</div>
<div class="ai-suggestion-text">
${escapeHtml(suggestion.content)}
</div>
</div>
`;
// 리스트에 추가
listContainer.insertAdjacentHTML('beforeend', cardHtml);
console.log('AI 제안사항 추가됨:', suggestion.content);
// 기본 동작(링크 이동) 방지
return false;
}
// HTML 이스케이프 (XSS 방지)
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, m => map[m]);
// 용어 상세 정보 보기
function showTermDetail(termName) {
alert(`"${termName}" 용어에 대한 상세 정보를 표시합니다.`);
}
// 페이지 로드 시 타이머 시작 및 SSE 연결
// 페이지 로드 시 타이머 시작
document.addEventListener('DOMContentLoaded', function() {
updateTimer();
connectAiSuggestionStream();
});
// 페이지 종료 시 SSE 연결 해제
window.addEventListener('beforeunload', function() {
if (eventSource) {
eventSource.close();
console.log('SSE 연결 종료');
}
});
</script>
</body>
-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();
}
@@ -604,7 +604,7 @@
const statusBadge = minute.status === 'complete' ?
'<span class="badge badge-complete">확정완료</span>' :
'<span class="badge badge-draft">작성중</span>';
const crownEmoji = isCreator ? '<span style="font-size: 16px; flex-shrink: 0;" title="생성자">👑</span>' : '';
const creatorBadge = isCreator ? '<span class="creator-badge" title="생성자">👑</span>' : '';
// 검증완료율 실시간 계산 (작성중 상태일 때만 표시)
const completionRate = minute.status === 'draft'
@@ -615,7 +615,7 @@
<div class="meeting-item" data-status="${minute.status}" data-type="${participationType}" data-date="${minute.date}" onclick="navigateTo('10-회의록상세조회.html')">
<div class="meeting-header">
${statusBadge}
${crownEmoji}
${creatorBadge}
<h3 class="meeting-title">${minute.title}</h3>
</div>
<div class="meeting-meta">
+43 -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);
@@ -372,6 +395,21 @@ a:hover {
color: var(--gray-700);
}
/* Creator Badge (생성자 표시) */
.creator-badge {
font-size: 16px;
flex-shrink: 0;
margin-left: 4px;
cursor: default;
display: inline-flex;
align-items: center;
vertical-align: middle;
}
.creator-badge[title] {
cursor: help;
}
/* Priority Badges */
.badge-high {
background: #FFEBEE;
@@ -1238,9 +1276,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 +1286,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 +1302,7 @@ input[type="date"]::-webkit-calendar-picker-indicator {
gap: var(--space-sm);
}
.section-content {
.agenda-content {
color: var(--gray-700);
line-height: 1.6;
}
+35
View File
@@ -333,6 +333,41 @@
}
```
### 생성자 배지
회의 생성자를 나타내는 크라운 아이콘 배지입니다.
```css
/* 생성자 배지 (👑 아이콘) */
.creator-badge {
font-size: 16px;
flex-shrink: 0;
margin-left: 4px;
cursor: default;
display: inline-flex;
align-items: center;
vertical-align: middle;
}
/* 툴팁 제공 시 */
.creator-badge[title] {
cursor: help;
}
```
**사용 예시**:
```html
<div class="meeting-header">
<span class="badge badge-complete">확정완료</span>
<span class="creator-badge" title="생성자">👑</span>
<h3 class="meeting-title">2024년 4분기 제품 기획 회의</h3>
</div>
```
**사용 위치**:
- 12-회의록목록조회: 회의록 카드 헤더
- 02-대시보드: 최근 회의 카드, 내 회의록 카드
- 10-회의록상세조회: 회의록 정보 섹션
### D-day 배지
Todo 마감일 표시를 위한 D-day 배지 스타일입니다.
+135 -162
View File
@@ -2,9 +2,9 @@
## 문서 정보
- **작성일**: 2025-10-21
- **최종 수정일**: 2025-10-24
- **최종 수정일**: 2025-10-25
- **작성자**: 이미준 (서비스 기획자)
- **버전**: 1.4.16
- **버전**: 1.4.20
- **설계 철학**: Mobile First Design
---
@@ -628,51 +628,30 @@ graph TD
#### 개요
- **목적**: 실시간 회의 진행 및 AI 기반 회의록 자동 작성
- **관련 유저스토리**: UFR-MEET-030, UFR-STT-010/020, UFR-AI-010, UFR-AI-040, UFR-COLLAB-010, UFR-RAG-010/020
- **관련 유저스토리**: UFR-MEET-030, UFR-STT-010/020, UFR-AI-010, UFR-AI-040, UFR-COLLAB-010, UFR-RAG-010/020, UFR-PART-010/020/030, UFR-HOST-010/020, UFR-TERM-010/020
- **비즈니스 중요도**: 높음 (핵심 화면)
- **접근 경로**: 템플릿선택 → "이 템플릿으로 시작"
- **권한**:
- 회의 시작/종료: 회의 생성자 전용
- 회의록 편집: 모든 참석자
- **접근 경로**: 대시보드 → "참여하기" 버튼 (페이지 전환)
- **권한** (MVP 개선):
- **회의 생성자 전용**: 회의 종료, 녹음 제어 (일시정지/재개/종료)
- **모든 참석자**: 회의 참여, AI 주요 내용 체크, 용어 확인, 관련 회의록 확인, 중도 퇴장
#### 주요 기능
1. 음성 녹음 및 실시간 텍스트 변환 (STT)
2. AI 자동 회의록 작성 (구조화)
3. **AI 기반 회의 내용 요약 자동 생성** (섹션별)
4. 실시간 협업 (여러 참석자 동시 편집)
5. 전문용어 자동 감지 및 맥락 기반 설명
6. **참고자료 자동 연결** (이전 회의록, 관련 회의록)
7. 수동 편집 및 섹션별 작성
8. 회의 진행 시간 표시
3. **AI 기반 주요 메모 항목 실시간 제안** (UFR-MEET-030)
4. 전문용어 자동 감지 및 맥락 기반 설명
5. **참고자료 자동 연결** (이전 회의록, 관련 회의록)
6. 참석자 관리 및 초대 기능
7. 회의 진행 시간 표시
#### UI 구성요소
**전체 레이아웃 (2열 구조)**
**전체 레이아웃**
- **헤더** (Fixed, 상단)
- 좌측: "회의 진행 중" 제목 + 경과시간 배지 (빨강, 01:03)
- 우측: "회의 종료" 버튼 (민트 그린 테두리)
- **왼쪽 영역: 회의 내용 작성** (60-70% 너비)
- **텍스트 에디터 툴바**
- B (Bold), I (Italic), U (Underline)
- 색상 선택, 링크 추가
- **편집 영역** (contentEditable, 스크롤 가능)
- 실시간 입력 텍스트: "회의 내용을 작성하거나 AI가 자동으로 작성합니다..."
- 섹션 구조:
```
# 참석자
- 김민준
- 박서연
- 이준호
# 안건
1. 신규 기능 개발 일정 논의
2. 예산 편성 검토
```
- 자동 저장 (30초 간격)
- **오른쪽 영역: 정보 패널** (30-40% 너비, 탭 구조)
- **메인 콘텐츠 영역: 정보 패널** (탭 구조)
- **탭 네비게이션** (4개 탭)
- 참석자 (3명)
- AI 제안
@@ -709,30 +688,6 @@ graph TD
- 본문 폰트: 14px, gray-700
- 구조: 헤더 + 본문 텍스트 + 액션 버튼
- **논의사항 제안 카드**
- 헤더: "💬 논의사항 제안" (아이콘 + 제목, 16px bold, 민트 그린)
- 내용: "AI 모델 정확도 향상 방안" (strong 태그, 14px)
- 현재 STT 정확도: 92% (14px 일반, gray-700)
- 목표 정확도: 95% 이상
- 도메인 특화 학습 데이터 확보 필요
- 액션 버튼: "논의사항에 적용" (btn-primary btn-sm) + "수정" (btn-ghost btn-sm)
- **결정사항 제안 카드**
- 헤더: "✅ 결정사항 제안" (아이콘 + 제목, 16px bold, 민트 그린)
- 내용: "개발 일정 최종 확정" (strong 태그, 14px)
- 설계: 2주 (11/1~11/14) (14px 일반, gray-700)
- 개발: 10주 (11/15~1/23)
- 테스트 및 배포: 2주 (1/24~2/6)
- 액션 버튼: "결정사항에 적용" (btn-primary btn-sm) + "수정" (btn-ghost btn-sm)
- **액션아이템 제안 카드**
- 헤더: "📋 액션 아이템(Todo) 자동 추출" (아이콘 + 제목, 16px bold, 민트 그린)
- 추출된 Todo 목록 (14px 일반, gray-700):
1. API 명세서 작성 (이준호, 10/23까지)
2. UI 프로토타입 디자인 (최유진, 10/28까지)
3. AI 모델 성능 테스트 (박서연, 10/25까지)
- 액션 버튼: "3개 Todo 생성" (btn-primary btn-sm) + "수정" (btn-ghost btn-sm)
- **용어 사전 탭**
- 제목: "용어 사전"
- 용어 검색 입력 필드 (placeholder: "용어 검색...")
@@ -805,32 +760,22 @@ graph TD
- 카드 클릭 시: **새 탭으로 열기** (target="_blank")
**Mobile (320px~768px)**
- **2열 구조를 1열로 전환**
- 왼쪽 영역: 메인 콘텐츠 (전체 너비)
- 오른쪽 탭 패널: 하단 시트로 표시
-버튼 클릭 시 바텀시트 슬라이드업
- 오버레이 + 닫기 버튼
**반응형 디자인**
- **Mobile (320px~768px)**
- 헤더: 고정 상단, 좁은 너비
- 메인 콘텐츠: 전체 너비 사용
-콘텐츠: 세로 스크롤
- 하단 버튼 영역: 고정 하단
**Desktop (768px+)**
- 2열 고정 레이아웃
- 왼쪽: 편집 영역
- 오른쪽: 탭 패널 (고정)
- **Desktop (768px+)**
- 헤더: 고정 상단, 넓은 너비
- 메인 콘텐츠: 최대 너비 제한 없이 반응형
- 탭 콘텐츠: 더 넓은 영역 활용
- 하단 버튼 영역: 고정 하단
#### 인터랙션
1. **텍스트 편집 (왼쪽 영역)**
- **편집 모드**: contentEditable 영역 클릭하여 즉시 편집 시작
- **자동 저장**: 편집 중 30초 간격 자동 저장
- **툴바 사용**:
- B (Bold): 선택된 텍스트를 굵게
- I (Italic): 선택된 텍스트를 이탤릭체로
- U (Underline): 선택된 텍스트에 밑줄
- 색상 선택: 텍스트 강조 색상 변경
- 링크 추가: URL 입력 모달 표시
- **실시간 동기화**: WebSocket 통해 모든 참석자에게 편집 내용 동기화
- **충돌 감지**: 동시 편집 시 충돌 감지 및 병합 옵션 제공
2. **탭 전환 (오른쪽 영역)**
1. **탭 전환**
- **참석자 탭**: 현재 회의 참석자 목록 표시 (4명) 및 참석자 추가 기능
- **참석자 추가 폼** (상단):
- 이메일 입력 필드 (form-control 스타일, placeholder: "이메일 주소 입력")
@@ -847,51 +792,28 @@ graph TD
- 상태 표시 없음 (발언 중/온라인 등 제거)
- 참석자 수 동적 업데이트 (초대 성공 시)
- **AI 제안 탭**: AI가 생성한 회의록 개선 제안 (3가지 유형)
- **논의사항 제안 카드**: 제안 내용 + "논의사항에 적용" 버튼
- 제안 구조:
- 제목: "AI 모델 정확도 향상 방안" (strong)
- 내용: 3-5개의 구체적인 논의 포인트 (bullet points)
- "논의사항에 적용" 클릭 시:
1. 논의사항 섹션(section-1)의 content-1 영역에 제안 내용 추가
2. 기존 내용 하단에 `<br>` 태그로 구분하여 추가
3. 제목은 `<strong>` 태그, 내용은 `<p>` 태그로 구조화
4. 성공 토스트 표시: "논의사항에 AI 제안이 추가되었습니다"
5. 자동으로 논의사항 탭(섹션 1)으로 전환 (switchSection(1))
6. 제안 카드 숨김 처리 (display: none)
- "수정" 버튼: 제안을 거부하고 카드 숨김
- **결정사항 제안 카드**: 제안 내용 + "결정사항에 적용" 버튼
- 제안 구조:
- 제목: "개발 일정 최종 확정" (strong)
- 내용: 확정된 결정사항 (bullet points)
- "결정사항에 적용" 클릭 시:
1. 결정사항 섹션(section-2)의 content-2 영역에 제안 내용 추가
2. 기존 내용 하단에 `<br>` 태그로 구분하여 추가
3. 제목은 `<strong>✓` 접두어 포함, 내용은 `<p>` 태그로 구조화
4. 성공 토스트 표시: "결정사항에 AI 제안이 추가되었습니다"
5. 자동으로 결정사항 탭(섹션 2)으로 전환 (switchSection(2))
6. 제안 카드 숨김 처리 (display: none)
- "수정" 버튼: 제안을 거부하고 카드 숨김
- **액션아이템 제안 카드**: 제안 내용 + "3개 Todo 생성" 버튼
- 제안 구조:
- 헤더: "📋 액션 아이템(Todo) 자동 추출"
- 내용: 3개의 Todo 항목 (제목, 담당자, 마감일)
- "3개 Todo 생성" 클릭 시:
1. 액션아이템 섹션(section-3)의 content-3 영역에 Todo 항목 추가
2. **중복 체크**: 기존 Todo 목록에서 동일한 제목이 있는지 확인
3. 중복되지 않은 Todo만 추가 (Set 자료구조 활용)
4. Todo HTML 구조: checkbox + 제목 + 담당자/마감일 + 우선순위 배지
5. 성공 토스트 표시: "N개의 액션아이템이 추가되었습니다 (중복 제외)"
6. 중복된 항목이 있으면: "모든 항목이 이미 존재합니다" (info 토스트)
7. 자동으로 액션아이템 탭(섹션 3)으로 전환 (switchSection(3))
8. 제안 카드 숨김 처리 (display: none)
- "수정" 버튼: 제안을 거부하고 카드 숨김
- **AI 제안 탭**: AI가 생성한 주요 메모 항목 제안 (UFR-MEET-030)
- **실시간 주요 메모 추천**:
- 음성→텍스트 변환 후 AI가 실시간으로 회의 내용 분석
- **중요한 내용으로 판단된 경우에만** 주요 메모 항목 제안
- 논의항목/결정사항 등의 구분 없이 중요 내용을 주요 메모로 제안
- 추천 빈도는 중요 내용 발생에 따라 가변적 (고정 간격 아님)
- 각 제안 항목에 "주요 메모에 추가" 버튼 제공
- 클릭 시 해당 안건의 주요 메모에 자동 저장
- 실시간 업데이트: 새로운 제안은 상단에 표시
- **용어 사전 탭**: 회의에서 언급된 전문용어 설명
- 용어 카드 (민트 그린 배경): 용어명 + 간단한 정의
- **용어 검색 기능**:
- 검색 입력창 (placeholder: "용어 검색...", form-control 스타일)
- 검색 버튼 (btn btn-primary btn-sm)
- Enter 키 지원
- 검색 동작:
1. 용어명과 정의 모두 검색
2. 일치하는 용어만 표시, 나머지는 숨김
3. 검색 결과에 하이라이트 효과 적용
4. 검색 결과 없으면 전체 목록 다시 표시
5. 입력창이 비어있으면 전체 목록 표시
- 용어 카드: 용어명 + 카테고리 배지 + 간단한 정의
- 카드 클릭 → 확장하여 상세 설명 표시
- 상세 설명: 이 회의에서의 의미, 관련 회의록 링크
@@ -900,41 +822,42 @@ graph TD
- **녹음 중인 페이지 이탈 방지**: 모든 링크는 새 탭으로 열림
- 관련도 표시: 퍼센트 또는 별점으로 시각화
3. **회의 종료**
2. **회의 종료**
- 헤더의 "회의 종료" 버튼 클릭
- 확인 다이얼로그 표시: "회의를 종료하시겠습니까?"
- 확인 → 회의 종료 처리 및 07-회의종료.html로 이동
4. **실시간 업데이트**
3. **실시간 업데이트**
- STT 음성 인식 결과 실시간 반영 (3-5초 주기)
- 모든 참석자의 편집 내용 실시간 동기화
- 수정 사항 하이라이트 표시 (3초간)
- AI 제안 실시간 업데이트
- 용어 사전 자동 업데이트 (새로운 전문용어 감지 시)
- 관련 회의록 목록 동적 갱신
#### 데이터 요구사항
- **입력**:
- 회의 ID
- 오디오 스트림 (실시간 STT용)
- 사용자 편집 내용 (텍스트 입력)
- 참석자 초대 이메일
- **출력**:
- 실시간 텍스트 변환 결과 (STT)
- 편집된 회의록 내용
- **AI 제안 목록** (회의록 개선 제안)
- **AI 제안 목록** (주요 메모 항목 제안)
- **전문용어 및 설명** (용어 사전)
- **관련 회의록 목록** (32건, 관련도 포함)
- 참석자 목록 및 상태
- 참석자 목록
- **연동**:
- STT 서비스 (UFR-AI-010)
- AI 서비스 (AI 제안 생성, UFR-AI-040)
- RAG 서비스 (관련 회의록 검색)
- AI 서비스 (주요 메모 제안 생성, UFR-AI-040)
- RAG 서비스 (관련 회의록 검색, 전문용어 자동 감지)
- Collaboration 서비스 (실시간 동기화)
#### 에러 처리
- **마이크 권한 거부**: "마이크 권한이 필요합니다" 토스트 + 설정 안내 링크
- **STT 실패**: "음성 인식에 실패했습니다. 수동으로 입력해주세요" 토스트
- **AI 제안 생성 실패**: "AI 제안을 불러올 수 없습니다" 토스트 (편집은 계속 가능)
- **STT 실패**: "음성 인식에 실패했습니다" 토스트 + 재시도 안내
- **AI 제안 생성 실패**: "AI 제안을 불러올 수 없습니다" 토스트
- **용어 사전 로드 실패**: "용어 사전을 불러올 수 없습니다" 메시지 표시
- **관련 자료 검색 실패**: "관련 회의록을 찾을 수 없습니다" 메시지 표시
- **동기화 실패**: "네트워크 연결을 확인해주세요. 내용은 로컬에 저장됩니다" 토스트
- **편집 충돌**: "다른 참석자가 동일한 부분을 수정 중입니다" 다이얼로그 + 병합 옵션
- **참석자 초대 실패**: "초대 링크 전송에 실패했습니다" 토스트 + 재시도 버튼
- **동기화 실패**: "네트워크 연결을 확인해주세요" 토스트
- **회의 종료 실패**: "회의 종료 중 오류가 발생했습니다" 토스트 + 재시도 버튼
---
@@ -976,7 +899,6 @@ graph TD
- 회의 총 시간
- 참석자 수
- 주요 키워드 (태그 클라우드)
- 발언 통계 (화자별 발언 횟수 및 시간 - 바 차트)
- **안건별 AI 요약 섹션** (신규)
- **안건 카드** (안건 개수만큼 반복):
@@ -986,7 +908,6 @@ graph TD
- 🔒 "편집 불가" 아이콘 표시
- 민트 그린 좌측 액센트 라인
- **상세 요약 정리** (읽기 전용)
- 논의 주제
- 발언자별 의견
- 결정 사항
- 보류 사항
@@ -1034,15 +955,21 @@ graph TD
- 11-회의록수정.html로 이동
- URL 파라미터: meetingId
- 회의록 상태: 작성중
- **옵션 2: 바로 최종 확정**
- 확인 다이얼로그 표시
- **옵션 2: 바로 최종 확정** (UFR-MEET-050 시나리오 2)
- 확인 다이얼로그 표시: "바로 최종 확정하시겠습니까? AI가 정리한 내용 그대로 확정됩니다."
- 확인 시:
- 모든 안건 검증률 100% 자동 설정
- 회의록 상태: 확정완료
- 안건별 검증완료 처리
- 회의록 상태: "작성중" → "확정완료"로 변경
- 확정 시간 기록
- 참석자에게 확정 알림 발송
- 성공 토스트: "회의록이 최종 확정되었습니다"
- 02-대시보드.html로 이동
- 10-회의록상세조회.html로 이동
- **시나리오 2 특징 (바로 확정)**:
- 회의록 수정 단계를 건너뜀
- AI 생성 내용을 그대로 확정
- 모든 안건이 자동으로 검증완료 처리됨
- 확정 후에도 회의 생성자는 수정 가능 (잠금 해제 필요)
- **옵션 3: 대시보드로 이동**
- 회의록 상태: 작성중
- 02-대시보드.html로 이동
@@ -1237,9 +1164,9 @@ graph TD
#### 주요 기능
1. 회의 기본 정보 표시
2. **섹션별 AI 요약 표시** (섹션 최상단)
3. 섹션별 상세 내용 표시
4. **참고자료 표시** (섹션 하단)
2. **안건별 AI 요약 표시** (안건 최상단)
3. 안건별 상세 내용 표시
4. **참고자료 표시** (안건 하단)
5. Todo 항목 및 진행 상황 표시
6. 첨부파일 다운로드
7. 회의록 수정/공유 액션
@@ -1263,17 +1190,17 @@ graph TD
- "대시보드" 탭 (기본 활성)
- "회의록" 탭
- **회의록 탭 콘텐츠** (섹션별 구조)
- 각 섹션:
- 섹션 제목
- **회의록 탭 콘텐츠** (안건별 구조)
- 각 안건:
- 안건 제목
- 검증 완료 배지 (검증된 경우)
- **AI 회의 내용 요약 영역** (섹션 최상단, 강조 박스)
- **AI 회의 내용 요약 영역** (안건 최상단, 강조 박스)
- 요약 아이콘 (💡)
- AI 자동 생성 요약 (2-3문장)
- 요약 생성/수정 시간
- "수정" 버튼 (권한 있는 경우)
- 섹션 내용 (마크다운 렌더링)
- **참고자료 영역** (섹션 하단, 별도 영역)
- 안건 내용 (마크다운 렌더링)
- **참고자료 영역** (안건 하단, 별도 영역)
- "참고자료" 라벨
- 관련 회의록 링크 리스트 (최대 3개):
- 링크 아이콘 (📄)
@@ -1331,17 +1258,17 @@ graph TD
- 대시보드 (기본 활성)
- 회의록
- **메인 영역**:
- 회의록 탭: 전체 회의록 내용 (섹션별 구조)
- 회의록 탭: 전체 회의록 내용 (안건별 구조)
- 대시보드 탭: 핵심내용, 결정사항, Todo 진행상황, 참고자료 (11-회의록대시보드.html 구조 참조)
#### 인터랙션
1. **탭 전환**
- "회의록" 탭: 전체 회의록 내용 표시 (섹션별 구조)
- "회의록" 탭: 전체 회의록 내용 표시 (안건별 구조)
- "대시보드" 탭: 핵심내용, 결정사항, Todo, 참고자료 요약 표시
- 탭 전환 시 URL 변경 없이 클라이언트 사이드 렌더링
2. **회의록 탭 인터랙션**
- **섹션 네비게이션**: 섹션 제목 클릭 → 해당 섹션으로 스크롤
- **안건 네비게이션**: 안건 제목 클릭 → 해당 안건으로 스크롤
- **접기/펼치기**: 긴 내용은 초기 접힌 상태, 클릭으로 펼침
- **AI 요약 편집**:
- "수정" 버튼 클릭 (권한 있는 경우) → 인라인 편집 모드
@@ -1354,10 +1281,10 @@ graph TD
3. **대시보드 탭 인터랙션**
- **핵심내용 섹션**:
- 키워드 태그 클릭 → 해당 키워드 관련 섹션으로 스크롤
- 키워드 태그 클릭 → 해당 키워드 관련 안건으로 스크롤
- 통계 항목 클릭 → 상세 정보 툴팁 표시
- **결정사항 섹션**:
- 결정사항 카드 클릭 → 회의록 탭의 해당 섹션으로 이동
- 결정사항 카드 클릭 → 회의록 탭의 해당 안건으로 이동
- 배경 설명 접기/펼치기
- **Todo 진행상황**:
- 필터 탭 클릭 → 해당 상태의 Todo만 표시
@@ -1384,8 +1311,8 @@ graph TD
- **입력**: 회의록 ID, 활성 탭 (회의록/대시보드/타임라인)
- **출력**:
- **회의 기본 정보**: 제목, 일시, 참석자, 장소, 상태, 작성자, 수정 시간
- **섹션별 AI 요약**: 자동 생성 요약, 수정 이력
- **섹션별 내용**: 마크다운 형식
- **안건별 AI 요약**: 자동 생성 요약, 수정 이력
- **안건별 내용**: 마크다운 형식
- **참고자료 목록**:
- 관련 회의록 (제목, 날짜, 관련도, 요약)
- 프로젝트 문서 (제목, 작성자, 관련도)
@@ -1403,7 +1330,7 @@ graph TD
#### 에러 처리
- **회의록 로딩 실패**: "회의록을 불러올 수 없습니다" + 재시도 버튼
- **AI 요약 로딩 실패**: "요약을 불러올 수 없습니다" (섹션 내용은 정상 표시)
- **AI 요약 로딩 실패**: "요약을 불러올 수 없습니다" (안건 내용은 정상 표시)
- **참고자료 로딩 실패**: "참고자료를 불러올 수 없습니다" (빈 상태 표시)
- **대시보드 데이터 로딩 실패**: "대시보드를 불러올 수 없습니다" + 재시도 버튼
- **권한 없음**: "수정" 버튼 비활성화, "조회 권한만 있습니다" 메시지
@@ -1468,6 +1395,9 @@ graph TD
- **안건 헤더**
- 안건 제목 (H4, Bold)
- 검증 상태 배지 (검증완료/미검증)
- 편집 중 표시 (동시 편집 시)
- 다른 사용자 아바타 + 이름
- 예: "김민준님 편집 중" (아이콘 + 텍스트)
- **AI 한줄 요약** (편집 불가, UFR-AI-036) - 신규
- 🔒 아이콘 + 30자 이내 한줄 요약
- 읽기 전용 (회색 배경, 민트 그린 좌측 액센트 라인)
@@ -1599,6 +1529,38 @@ graph TD
- 확정완료 회의록 수정 시: 자동으로 "작성중" 상태로 변경
- 모든 안건 검증 완료 시: "확정완료"로 변경 제안
9. **안건 기반 충돌 해결 (UFR-COLLAB-020)**
- **안건 기반 충돌 방지 메커니즘**:
- **다른 안건 동시 편집**: 충돌 없음
- 참석자 A가 안건 1 편집 중
- 참석자 B가 안건 2 편집 가능
- 양쪽 모두 정상 저장 및 동기화
- **동일 안건 내 다른 필드 편집**: 자동 병합
- 참석자 A가 안건 1의 "상세 요약" 편집
- 참석자 B가 안건 1의 "관련회의록" 편집
- 양쪽 변경 사항 자동 병합
- **동일 필드 동시 수정**: Last Write Wins
- 마지막에 저장된 변경 사항이 적용
- 덮어쓰기 경고: "다른 사용자가 이미 수정했습니다. 최신 내용을 확인하세요"
- 선택 옵션: 최신 내용 확인 / 내 변경 사항 유지
- **편집 중 표시**:
- 다른 사용자가 편집 중인 안건 표시
- 편집자 아바타 + 이름 실시간 표시
- 예: "김민준님이 이 안건을 편집 중입니다" + 아바타
- 편집 시작 시 해당 안건에 브로드캐스트
- 편집 종료 시 표시 제거
- **충돌 경고 모달**:
- 제목: "동시 수정 감지"
- 메시지: "다른 사용자가 이미 이 내용을 수정했습니다"
- 옵션 버튼:
- "최신 내용 보기" (Primary): 다른 사용자 변경사항 로드
- "내 변경사항 유지" (Secondary): 현재 내용 유지 (덮어쓰기)
#### 데이터 요구사항
- **입력**:
- 회의록 ID (조회)
@@ -1621,7 +1583,11 @@ graph TD
- **자동 저장 실패**: "네트워크 연결을 확인해주세요. 로컬에 임시 저장됩니다"
- **AI 요약 재생성 실패**: "요약 생성에 실패했습니다. 수동으로 작성해주세요"
- **참고자료 검색 실패**: "회의록을 검색할 수 없습니다"
- **충돌 발생**: "다른 참석자가 동일한 부분을 수정했습니다" + 병합 옵션
- **충돌 발생**:
- 안건 기반 충돌 방지로 최소화
- 동일 필드 동시 수정 시: "다른 사용자가 이미 수정했습니다" 경고 모달
- 선택 옵션: 최신 내용 확인 / 내 변경사항 유지
- 병합 실패 시: "병합 중 오류가 발생했습니다" 에러 메시지
- **삭제 실패**: "회의록 삭제에 실패했습니다"
---
@@ -1708,8 +1674,10 @@ graph TD
- 각 회의록 항목 (meeting-item):
- **좌측 영역**:
- 회의 제목 (H5, 볼드)
- **생성자 표시**: 현재 사용자가 회의 생성자인 경우 👑 아이콘 표시 (16px, title="생성자")
- 메타정보 (Caption, 회색):
- 회의 일시 (날짜 + 시간) · 참석자 수
- 검증완료율 (작성중 상태일 때만): "✓ {completionRate}% 검증완료" 배지
- 최종 수정 시간 (Caption, 회색):
- 상대 시간 표시 ("1시간 전", "어제", "3일 전")
- **우측 영역**:
@@ -2100,6 +2068,7 @@ graph TD
| 버전 | 날짜 | 작성자 | 변경 내용 |
|------|------|--------|----------|
| 1.4.20 | 2025-10-25 | 이미준, 강지수 | 유저스토리 v2.3.0 반영<br>- 회의 종료 화면 정책 명확화 (확인 전용, 바로 최종 확정 옵션 상세화)<br>- UFR-MEET-050: 최종 확정 2가지 시나리오 설명 추가<br>- UFR-COLLAB-020: 안건 기반 충돌 해결 메커니즘 상세 추가<br>- 실시간 협업 충돌 방지 정책 강화 |
| 1.0 | 2025-10-21 | 이미준 | 최초 작성 - 11개 화면 설계 완료 |
| 1.1 | 2025-10-21 | 이미준 | AI 요약 및 참고자료 기능 추가<br>- 05-회의진행: AI 회의 내용 요약 자동 생성 및 참고자료 자동 연결 추가<br>- 10-회의록상세조회: 섹션별 AI 요약 표시 및 참고자료 영역 추가<br>- 11-회의록수정: AI 요약 수정 및 참고자료 편집 기능 추가<br>- 관련 유저스토리: UFR-AI-040 (관련 회의록 자동 연결) |
| 1.1.1 | 2025-10-21 | 이미준 | 회의록 상세 화면 구조 개선 (프로토타입 기반)<br>- 10-회의록상세조회: 탭 기반 네비게이션 추가 (회의록/대시보드)<br>- 대시보드 탭 추가: 핵심내용, 결정사항, Todo 진행상황, 참고자료 섹션<br>- 참고자료 관련도 점수 표시 (백분율 + 색상 코딩)<br>- 참고자료 카테고리 탭 (관련 회의록/프로젝트 문서/이슈 트래커/위키 페이지)<br>- 참조: design-gappa/uiux/prototype 파일 (11-회의록대시보드.html, 05-회의진행.html) |
@@ -2130,6 +2099,10 @@ graph TD
| 1.4.14 | 2025-10-24 | 이미준 | 12-회의록목록조회 화면 데이터 아키텍처 문서화<br>- **데이터 아키텍처 섹션 추가**: 데이터/뷰 레이어 분리 구조 설명<br> - 데이터 레이어: common.js → SAMPLE_MINUTES 배열 (30개 샘플)<br> - 뷰 레이어: 12-회의록목록조회.html → renderMeetings(), createMeetingCard() 함수<br> - 렌더링 방식: 동적 렌더링, 초기 10개 표시, "10개 더보기" 버튼으로 추가 로딩<br>- **정렬 옵션 레이블 변경**: "최신순" → "최근수정순", "회의일시순" → "최근회의순"<br>- **페이지네이션 기능 문서화**: 초기 10개 표시, "10개 더보기" 버튼 기능 설명<br>- **샘플 데이터 분포 명시**: 총 30개 (작성중 13개, 확정완료 17개)<br>- **프로토타입 파일 경로 추가**: design/uiux/prototype/12-회의록목록조회.html<br>- **스타일 가이드 버전 동기화**: v1.2.4 |
| 1.4.15 | 2025-10-24 | 이미준 | 06-검증완료 화면 삭제 (유저스토리 v2.1.2 변경사항 반영)<br>- **화면 삭제**: 06-검증완료 화면 전체 삭제<br> - 안건별 검증 기능이 11-회의록수정 화면으로 통합됨<br> - 섹션별 검증 방식에서 안건별 검증 방식으로 변경 (유저스토리 UFR-COLLAB-030 → 안건 기반 구조로 전환)<br>- **유저스토리 매핑 업데이트**:<br> - Collaboration 서비스: UFR-COLLAB-010 ~ UFR-COLLAB-030 → UFR-COLLAB-010 ~ UFR-COLLAB-020로 변경<br> - 프로토타입 화면 목록 테이블에서 06-검증완료 행 제거<br>- **화면 번호 유지**: 다른 화면 번호는 변경하지 않음 (프로토타입 파일명 유지)<br> - 07-회의종료, 09-Todo관리, 10-회의록상세조회, 11-회의록수정, 12-회의록목록조회 번호 유지<br>- **변경 이력**: 과거 버전의 UFR-COLLAB-030 언급은 역사적 맥락으로 유지 |
| 1.4.16 | 2025-10-24 | 이미준 | 사용자 역할 용어 통일 (유저스토리 v2.1.2 반영)<br>- **용어 정의 명확화**: "회의 생성자"와 "회의 참석자" 용어로 통일<br> - 설계 목표: "회의록 작성자" → "회의 참석자"로 수정<br>- **화면별 권한 정보 추가**:<br> - 03-회의예약: 모든 사용자 (예약 생성 시 자동으로 회의 생성자가 됨)<br> - 04-템플릿선택: 회의 생성자 전용<br> - 05-회의진행: 회의 시작/종료는 회의 생성자 전용, 회의록 편집은 모든 참석자<br> - 07-회의종료: 회의 생성자 전용<br> - 09-Todo관리: 모든 회의 참석자 (본인이 담당자인 Todo만 조회/수정 가능)<br> - 10-회의록상세조회: 모든 회의 참석자 (조회 전용)<br> - 11-회의록수정: 검증완료 전(모든 참석자), 검증완료 후(회의 생성자만) - 기존 권한 제어 유지<br> - 12-회의록목록조회: 모든 회의 참석자 (본인이 참석한 회의록만 조회)<br>- **스타일 가이드 동기화**: design/uiux/style-guide.md v1.2.5 (용어 정의 섹션 추가)<br>- **통일성 달성**: 유저스토리, 화면설계서, 스타일 가이드 간 용어 완전 통일 |
|| 1.4.17 | 2025-10-24 | 강지수 | 07-회의종료 화면 STT 한계 반영 (유저스토리 v2.1.2)<br>- **STT 화자 식별 불가 반영**: STT는 화자를 식별할 수 없으므로 화자 관련 기능 제거<br> - 발언 통계 섹션 삭제<br> - 안건별 "발언자별 의견" 섹션 삭제<br>- **통계 영역 디자인 개선**: 정보성 디자인으로 명확화<br> - 배경색: var(--white) → var(--gray-50)<br> - 숫자 색상: var(--primary) → var(--gray-900)<br> - 라벨 색상: var(--gray-500) → var(--gray-600)<br> - 정보 표시 전용으로 시각적 구분 명확화<br>- **안건 섹션 구분 개선**:<br> - 안건 간 하단 보더 추가 (1px solid var(--gray-200))<br> - 섹션 제목에 primary 색상 세로 바 추가 (::before pseudo-element)<br> - 콘텐츠 영역 좌측 패딩 추가로 계층 구조 명확화<br>- **연관 문서 업데이트**:<br> - 유저스토리 UFR-MEET-040: "발언 횟수 (화자별)" 항목 제거<br> - UI/UX 설계서 07-회의종료: 발언 통계 및 발언자별 의견 항목 제거 |
| 1.4.18 | 2025-10-24 | 강지수 | 05-회의진행 실시간 주요 메모 추천 기능 명확화 (유저스토리 v2.1.1)<br>- **AI 제안 탭 기능 상세화**: 실시간 주요 메모 추천 기능 명시 추가<br> - UFR-MEET-030: 실시간 AI 주요 메모 추천<br> - 음성→텍스트 변환 후 AI가 실시간 분석<br> - **중요한 내용으로 판단된 경우에만** 주요 메모 항목 추천<br> - 추천 빈도는 중요 내용 발생에 따라 가변적 (3-5초 고정 간격 아님)<br> - 각 추천 항목에 "주요 메모에 추가" 버튼 제공<br> - 실시간 업데이트: 새로운 추천은 상단에 표시<br>- **프로토타입 확인**: 05-회의진행.html의 AI 제안 탭이 실시간 주요 메모 추천 기능을 포함하고 있음을 확인<br>- **참조**: design/uiux/요구사항설계검토-report-V1.2.md (실시간 주요 메모 추천 명시 부족 개선) |
| 1.4.19 | 2025-10-24 | 강지수 | 05-회의진행 화면 설계서 프로토타입 기준 전면 수정<br>- **레이아웃 구조 변경**: "2열 구조" 표현 제거, "메인 콘텐츠 영역: 정보 패널 (탭 구조)"로 단순화<br> - 텍스트 편집 영역 관련 내용 모두 제거 (왼쪽 영역, 에디터 툴바, contentEditable 등)<br> - 현재 프로토타입은 헤더 + 탭 콘텐츠 구조만 보유<br>- **반응형 디자인 명확화**: Mobile/Desktop 모두 동일한 구조에 너비만 반응형<br> - "2열 구조를 1열로 전환", "바텀시트" 표현 제거<br> - Mobile: 전체 너비 사용, Desktop: 최대 너비 제한 없이 반응형<br>- **AI 제안 탭 기능 명확화**: 논의항목/결정사항 구분 제거<br> - "논의항목/결정사항 등의 구분 없이 중요 내용을 주요 메모로 제안" 명시<br> - AI는 단순히 중요한 내용을 주요 메모 항목으로 제안하는 역할만 수행<br>- **용어 사전 검색 기능 추가**: 검색 입력창 + 검색 버튼<br> - Enter 키 지원, 용어명과 정의 모두 검색<br> - 검색 동작 상세 설명: 일치하는 용어만 표시, 하이라이트 효과, 결과 없으면 전체 목록 표시<br>- **인터랙션 섹션 정리**: 텍스트 편집, 툴바 사용, 충돌 감지 등 편집 관련 내용 모두 제거<br> - 탭 전환, 회의 종료, 실시간 업데이트만 유지<br> - 실시간 업데이트 항목을 현재 화면에 맞게 수정 (AI 제안, 용어 사전, 관련 회의록)<br>- **데이터 요구사항 업데이트**: 사용자 편집 내용 제거, 참석자 초대 이메일 추가<br> - AI 제안을 "주요 메모 항목 제안"으로 명확히 표현<br>- **에러 처리 업데이트**: 편집 충돌 에러 제거, 용어 사전 로드 실패/참석자 초대 실패 추가<br>- **주요 기능 목록 정리**: 실시간 협업/수동 편집 제거, AI 주요 메모 제안/참석자 관리 추가<br>- **권한 항목 수정**: "회의록 편집: 모든 참석자" → "참석자 초대: 모든 참석자"<br>- **프로토타입 기준 반영**: 05-회의진행.html 실제 구현 상태 100% 반영 |
---
@@ -0,0 +1,766 @@
# 유저스토리 v2.1.2 vs UI/UX 설계 크로스 체크 리포트
**작성일**: 2025-10-24
**작성자**: AI Assistant (Claude)
**버전**: 1.0
---
## 1. 주요 발견사항 요약
### 전체 요약
- **분석 대상**: 유저스토리 v2.1.2, UI/UX 설계서 v1.4.14, 프로토타입 파일 13개
- **불일치 항목 수**: 총 12개 (🔴 높음 4개, 🟡 중간 5개, 🟢 낮음 3개)
- **주요 이슈**: 07-회의종료 화면 기능 불일치, 06-검증완료 화면 존재 여부, 용어 사용 불일치
### 중요도별 분류
#### 🔴 높음 (즉시 수정 필요)
1. **07-회의종료 화면 편집 불가 정책 미반영**
2. **07-회의종료 화면 3가지 선택 옵션 미반영**
3. **07-회의종료 화면 안건별 AI 요약 표시 미반영**
4. **06-검증완료 화면 삭제 필요**
#### 🟡 중간 (우선 수정 권장)
5. **11-회의록수정 화면 안건 기반 구조 미반영**
6. **11-회의록수정 화면 안건별 AI 한줄 요약 미표시**
7. **11-회의록수정 화면 안건별 검증 UI 미구현**
8. **용어 통일 필요: "작성자" → "생성자"/"참석자"**
9. **회의록목록조회 화면 "생성자" 표시 미반영**
#### 🟢 낮음 (검토 후 수정)
10. **05-회의진행 화면 실시간 주요 메모 추천 기능 명시 부족**
11. **10-회의록상세조회 화면 안건별 표시 명시 부족**
12. **스타일 가이드 안건 관련 컴포넌트 누락**
---
## 2. 화면별 상세 분석
### 07-회의종료
#### 유저스토리 v2.1.2 요구사항
- **UFR-MEET-040 (회의종료)**
- 회의 종료 화면은 **확인 전용 (편집 불가)**
- **안건별 AI 요약 전체 표시**:
- 안건별 AI 한줄 요약 (편집 불가)
- 안건별 상세 요약 (확인만 가능)
- Todo 자동 추출 결과 (확인만 가능)
- 사용자에게 **3가지 선택 옵션** 제공:
- 옵션 1: 회의록 수정 화면으로 이동
- 옵션 2: 바로 최종 확정 (모든 안건 자동 검증 완료)
- 옵션 3: 대시보드로 이동
- 회의록 상태: 옵션 1, 3 선택 시 "작성중", 옵션 2 선택 시 "확정완료"
#### UI/UX 설계서 내용
- **화면 목적**: 회의 통계 표시 및 최종 회의록 확정
- 주요 기능:
1. 회의 통계 표시
2. 주요 키워드 클라우드
3. AI 자동 추출된 Todo 항목 확인
4. **최종 회의록 확정** ← 편집 불가 정책 미반영
5. 다음 액션 선택 (공유, 수정, 대시보드 복귀) ← 3가지 옵션 불일치
- **AI Todo 추출 결과**:
- "AI가 추출한 Todo" 섹션
- Todo 항목 리스트 (담당자, 마감일)
- **"Todo 수정" 버튼** ← 편집 불가 정책 위반
- **하단 액션**:
- "회의록 공유하기" 버튼 ← v2.1.2에 없음
- "회의록 수정하기" 버튼
- "대시보드로 돌아가기" 버튼
#### 프로토타입 구현 상태 (07-회의종료.html)
```html
<!-- AI Todo 추출 결과 -->
<button class="btn btn-ghost btn-sm" onclick="openModal('todoEditModal')">수정</button>
<!-- ← 편집 불가 정책 위반 -->
<!-- 하단 액션 바 -->
<button class="btn btn-secondary" onclick="navigateTo('11-회의록수정.html')">수정</button>
<button class="btn btn-primary" onclick="navigateTo('02-대시보드.html')">대시보드로 이동</button>
<!-- ← 3가지 옵션 미구현, "바로 최종 확정" 옵션 없음 -->
```
#### 불일치 사항
1. 🔴 **편집 불가 정책 미반영**
- 유저스토리: 확인 전용, 편집 불가
- UI/UX 설계서 & 프로토타입: "Todo 수정" 버튼 존재
- **수정 필요**: "수정" 버튼 제거, 확인만 가능하도록 변경
2. 🔴 **3가지 선택 옵션 미반영**
- 유저스토리: 회의록 수정 / 바로 최종 확정 / 대시보드 이동
- 프로토타입: 수정 / 대시보드 이동만 있음
- **수정 필요**: "바로 최종 확정" 버튼 추가, 선택 시 모든 안건 자동 검증 처리
3. 🔴 **안건별 AI 요약 표시 미반영**
- 유저스토리: 안건별 AI 한줄 요약 + 상세 요약 전체 표시
- UI/UX 설계서 & 프로토타입: Todo만 표시, 안건 구조 없음
- **수정 필요**: 안건별 섹션으로 구조화, 각 안건의 AI 요약 표시
4. 🟢 **"회의록 공유하기" 버튼 존재**
- UI/UX 설계서: "회의록 공유하기" 버튼 있음
- 유저스토리 v2.1.2: 공유 기능 제거됨 (v2.0.1에서)
- **수정 필요**: "공유" 버튼 제거
#### 권장 수정사항
```
[하단 액션 바 수정안]
- "회의록 수정" 버튼 (옵션 1)
- "바로 최종 확정" 버튼 (옵션 2, Primary)
- "대시보드로 이동" 버튼 (옵션 3)
[AI Todo 추출 결과 섹션 수정안]
- "Todo 수정" 버튼 제거
- 확인만 가능하도록 readonly 처리
[안건별 AI 요약 표시 추가]
- 각 안건별 카드로 표시
- 안건 제목
- AI 한줄 요약 (읽기 전용)
- 상세 요약 (읽기 전용)
- Todo 목록 (읽기 전용)
```
---
### 11-회의록수정
#### 유저스토리 v2.1.2 요구사항
- **UFR-MEET-055 (회의록수정)**
- 진입 경로: 10-회의록상세조회 → "수정" 버튼 클릭
- **안건 기반 회의록 구조**:
- 각 안건별 섹션
- 안건별 AI 한줄 요약 (편집 불가)
- 안건별 상세 요약 (편집 가능)
- 안건별 검증 상태 (체크박스)
- 수정 가능 항목:
- ✅ 회의 제목
- ❌ 회의 일시/장소 (readonly)
- ✅ 참석자 목록 (회의 생성자만)
- ✅ 안건별 AI 요약 (AI 재생성)
- ✅ 안건별 내용
- ✅ 관련회의록
- 검증완료 안건: 회의 생성자만 잠금 해제 후 수정 가능
- **UFR-AI-036 (AI한줄요약)**
- 각 안건마다 편집 불가능한 AI 한줄 요약 제공
- 30자 이내 간결한 표현
- 회의 종료 시 1회 생성, 생성 후 편집 불가
- **UFR-COLLAB-030 (검증완료)**
- 안건별 검증 완료 처리
- 11-회의록수정 화면에서 안건별 검증 처리
- 별도의 06-검증완료 화면 불필요
#### UI/UX 설계서 내용
- **주요 기능**:
1. 회의 기본 정보 표시 및 수정
2. 회의록 내용 수정 **(섹션별)** ← 안건별이 아닌 섹션별
3. AI 요약 수정 (섹션별)
4. 참고자료 편집
5. Todo 수정 (회의 생성자만)
6. 자동 저장
- **섹션 구조**:
- 섹션 1 편집: "1. 신제품 기획 방향"
- 섹션 2 편집: "2. 개발 일정 및 리소스"
- 섹션 3 편집: "3. 마케팅 전략"
- ← "섹션" 용어 사용, "안건" 용어 없음
- **AI 요약 편집**:
- AI 요약 텍스트 필드 (편집 가능)
- "AI 재생성" 버튼
- ← AI 한줄 요약 (편집 불가) 항목 없음
- **검증 완료 표시**:
- 체크박스 (검증 완료, disabled)
- 🔒 읽기 전용 배지
- ← 섹션별 검증, 안건별 검증 아님
#### 프로토타입 구현 상태 (11-회의록수정.html)
```html
<!-- 섹션 1 편집 -->
<div class="section">
<div class="section-header">
<h3 class="section-title">
1. 신제품 기획 방향
<span class="badge badge-complete">검증완료</span>
</h3>
</div>
<!-- AI 요약 편집 -->
<div class="ai-summary-edit">
<div class="ai-summary-header">
<span class="ai-summary-label">💡 AI 요약</span>
<button class="btn-secondary btn-sm" onclick="regenerateSummary(1)">AI 재생성</button>
</div>
<textarea class="ai-summary-textarea" readonly>
신제품은 AI 기반 회의록 자동화 서비스로 결정...
</textarea>
<!-- ← AI 한줄 요약 (편집 불가) 항목 없음 -->
</div>
<!-- 검증 완료 (읽기 전용) -->
<div class="verification-lock">
<input type="checkbox" class="checkbox" id="verify-1" checked disabled>
<label for="verify-1">
<span class="font-medium">검증 완료</span>
<span class="text-caption text-muted"> (잠금됨 · 회의 생성자만 수정 가능)</span>
</label>
<span class="readonly-badge">🔒 읽기 전용</span>
</div>
</div>
```
#### 불일치 사항
1. 🟡 **안건 기반 구조 미반영**
- 유저스토리: "안건별" 회의록 구조
- UI/UX 설계서 & 프로토타입: "섹션별" 구조
- **수정 필요**: "섹션" 용어를 "안건"으로 통일
2. 🟡 **안건별 AI 한줄 요약 미표시**
- 유저스토리: 각 안건마다 편집 불가능한 AI 한줄 요약 (30자 이내)
- 프로토타입: AI 요약은 있지만 "한줄 요약"과 "상세 요약" 구분 없음
- **수정 필요**:
- AI 한줄 요약 (읽기 전용, 30자) 추가
- 기존 AI 요약을 "상세 요약"으로 명칭 변경
3. 🟡 **안건별 검증 UI 구현 상태**
- 유저스토리: 11-회의록수정 화면에서 안건별 검증 처리
- 프로토타입: 검증완료 체크박스 있으나 disabled (수정 불가)
- **검토 필요**:
- 회의 생성자일 때 체크박스 활성화 필요
- 잠금 해제 버튼 추가 고려
#### 권장 수정사항
```
[안건 구조 수정안]
<!-- 안건 1 편집 -->
<div class="agenda-section">
<div class="agenda-header">
<h3 class="agenda-title">
안건 1. 신제품 기획 방향
<span class="badge badge-complete">검증완료</span>
</h3>
</div>
<!-- AI 한줄 요약 (편집 불가) -->
<div class="ai-oneline-summary-readonly">
<span class="ai-icon">✨</span>
<span class="summary-text">AI 기반 회의록 서비스 개발 방향 결정</span>
</div>
<!-- AI 상세 요약 (편집 가능) -->
<div class="ai-summary-edit">
<div class="ai-summary-header">
<span class="ai-summary-label">💡 AI 상세 요약</span>
<button class="btn-secondary btn-sm">AI 재생성</button>
</div>
<textarea class="ai-summary-textarea">...</textarea>
</div>
<!-- 안건별 검증 (회의 생성자만 활성화) -->
<div class="agenda-verification">
<input type="checkbox" id="verify-agenda-1" checked>
<label>안건 검증 완료</label>
<button class="btn-ghost btn-sm" v-if="isCreator && isLocked">잠금 해제</button>
</div>
</div>
```
---
### 06-검증완료
#### 유저스토리 v2.1.2 요구사항
- **UFR-COLLAB-030 (검증완료)**:
- 11-회의록수정 화면에서 안건별 검증 처리
- **별도의 06-검증완료 화면 불필요**
#### UI/UX 설계서 내용
- **화면 존재**: 06-검증완료 화면 정의됨
- 주요 기능:
1. 섹션별 검증 상태 표시
2. 검증 완료 체크 (참석자별)
3. 미검증 섹션 안내
4. 섹션 잠금 (회의 생성자만)
#### 프로토타입 구현 상태
- **파일 존재**: `06-검증완료.html` (528줄)
- 주요 기능 구현:
- 섹션별 검증 카드
- 검증 완료 버튼
- 잠금 해제 버튼
- 진행률 표시
#### 불일치 사항
1. 🔴 **06-검증완료 화면 삭제 필요**
- 유저스토리: 별도 화면 불필요, 11-회의록수정에 통합
- UI/UX 설계서 & 프로토타입: 06-검증완료 화면 존재
- **수정 필요**:
- 06-검증완료.html 파일 삭제
- UI/UX 설계서에서 해당 화면 설명 제거
- 모든 링크 및 내비게이션에서 제거
#### 권장 수정사항
```
1. 프로토타입 파일 삭제
- design/uiux/prototype/06-검증완료.html 삭제
2. UI/UX 설계서 수정
- "### 06-검증완료" 섹션 전체 삭제
- 프로토타입 화면 목록 테이블에서 제거
3. 11-회의록수정 화면에 검증 기능 통합
- 각 안건별 검증 체크박스 추가
- 회의 생성자는 검증 상태 변경 가능
- 참석자는 자신의 검증만 처리 가능
```
---
### 10-회의록상세조회
#### 유저스토리 v2.1.2 요구사항
- **UFR-MEET-047 (회의록상세조회)**
- 회의 기본 정보 표시 (제목, 일시, 참석자, 장소, 상태)
- **안건별 상세 내용 표시** (섹션별 → 안건별)
- AI 요약 섹션 (안건별)
- 상세 내용 섹션 (논의 사항, 결정 사항 등)
- 관련 회의록 섹션
#### UI/UX 설계서 내용
- 탭 구성: 대시보드 / 회의록 (기본: 대시보드)
- **대시보드 탭**:
- 핵심내용 카드 (AI 요약)
- 결정사항 카드
- Todo 진행상황 카드
- 참고자료 카드
- **회의록 탭**:
- 회의 기본 정보
- 섹션별 AI 요약 및 내용 ← 안건별이 아님
#### 프로토타입 구현 상태 (10-회의록상세조회.html)
- 탭: 대시보드 / 회의록
- 섹션 구조 (회의록 탭):
```html
<div class="section">
<h3 class="section-title">1. 신제품 기획 방향</h3>
<div class="ai-summary">...</div>
<div class="section-content">...</div>
</div>
```
#### 불일치 사항
1. 🟢 **안건별 표시 명시 부족**
- 유저스토리: "안건별 상세 내용 표시"
- UI/UX 설계서 & 프로토타입: "섹션별" 용어 사용
- **수정 필요**: "섹션"을 "안건"으로 명칭 변경
#### 권장 수정사항
```
[UI/UX 설계서 수정]
- "섹션별 상세 내용 표시" → "안건별 상세 내용 표시"
- "섹션별 AI 요약" → "안건별 AI 요약"
[프로토타입 수정]
- class="section" → class="agenda"
- class="section-title" → class="agenda-title"
- HTML 주석 및 변수명 일괄 변경
```
---
### 05-회의진행
#### 유저스토리 v2.1.2 요구사항
- **UFR-AI-010 (회의록자동작성) - 시나리오 1**:
- **실시간 AI 주요 메모 작성**
- 텍스트 변환되면 자동으로 주요 메모 항목 추천
- 실시간 업데이트 (3-5초 간격)
- 참석자가 필요한 항목만 선택하여 저장
#### UI/UX 설계서 내용
- 주요 기능:
1. 음성 녹음 및 STT
2. 회의록 실시간 편집
3. 참석자 목록 관리
4. AI 제안 기능 (우측 탭)
- **데이터 출력**:
- 실시간 텍스트 변환 결과 (STT)
- 편집된 회의록 내용
- AI 제안 목록 (회의록 개선 제안) ← 주요 메모 추천과 차이
#### 프로토타입 구현 상태 (05-회의진행.html)
- 우측 탭:
- 참석자
- AI 제안 (논의사항 제안, 결정사항 제안, 액션아이템 제안)
- 용어 사전
- 관련 자료
- ← 실시간 주요 메모 추천 기능 명시 없음
#### 불일치 사항
1. 🟢 **실시간 주요 메모 추천 기능 명시 부족**
- 유저스토리: 실시간 AI 주요 메모 항목 추천 (3-5초 간격)
- UI/UX 설계서: "AI 제안 목록 (회의록 개선 제안)"
- 프로토타입: 논의사항/결정사항/액션아이템 제안만 있음
- **검토 필요**:
- 실시간 주요 메모 추천 기능이 "AI 제안"에 포함된 것인지 명확화
- 별도 UI 필요 여부 검토
#### 권장 수정사항
```
[UI/UX 설계서 명확화]
- "AI 제안 기능" 섹션에 다음 추가:
"실시간 AI 주요 메모 추천:
- 텍스트 변환 후 3-5초 간격으로 주요 메모 항목 자동 추천
- 참석자가 선택하여 저장
- 우측 'AI 제안' 탭에서 확인 가능"
[프로토타입 검토]
- 현재 AI 제안 탭 기능이 실시간 주요 메모 추천인지 확인
- 필요 시 별도 UI 컴포넌트 추가
```
---
### 12-회의록목록조회
#### 유저스토리 v2.1.2 요구사항
- **UFR-MEET-046 (회의록목록조회)**
- 필터: 참여 유형(참석한/생성한), 상태(전체/작성중/확정완료)
- 목록 표시 정보:
- 회의 제목
- 회의 일시
- 참석자 수
- 회의록 상태
- 검증 완료율
- **생성자 표시 (👑 아이콘)** ← v2.1.2에서 추가됨
- 마지막 수정 시간
#### UI/UX 설계서 내용
- 필터 및 정렬:
- 참여 유형: 참석한 회의 / 생성한 회의
- 상태: 전체 / 작성중 / 확정완료
- 목록 카드 정보:
- 회의 제목
- 날짜/시간
- 참석자 수
- 상태 배지
- 검증률 (작성중인 경우)
- ← 생성자 표시 명시 없음
#### 프로토타입 구현 상태 (12-회의록목록조회.html)
- 필터 및 정렬 구현됨
- 회의록 카드:
```javascript
// common.js - renderMinuteCard 함수
<div class="minute-card">
<div class="minute-title">${minute.title}</div>
<!-- 생성자 표시 로직 없음 -->
</div>
```
#### 불일치 사항
1. 🟡 **생성자 표시 미반영**
- 유저스토리: 생성자 표시 (👑 아이콘)
- UI/UX 설계서: 명시 없음
- 프로토타입: 구현 없음
- **수정 필요**:
- 생성자 표시 UI 추가
- 현재 사용자가 생성자일 경우 👑 아이콘 표시
#### 권장 수정사항
```
[UI/UX 설계서 수정]
"목록 표시 정보" 섹션에 추가:
- 생성자 표시: 현재 사용자가 회의 생성자인 경우 👑 아이콘 표시
[프로토타입 수정 - common.js]
function renderMinuteCard(minute, currentUserId) {
const isCreator = minute.creatorId === currentUserId;
return `
<div class="minute-card">
<div class="minute-header">
<h3 class="minute-title">
${minute.title}
${isCreator ? '<span class="creator-badge">👑</span>' : ''}
</h3>
</div>
...
</div>
`;
}
```
---
## 3. 용어 사용 불일치
### "작성자" vs "생성자"/"참석자"
#### 유저스토리 v2.1.2 용어 정책
- **v2.1.2 주요 변경사항**: 역할 용어 통일
- "작성자" → "회의 생성자" 또는 "참석자"
- 회의를 만든 사람: **회의 생성자** (creator)
- 회의에 참여한 사람: **참석자** (attendee)
#### UI/UX 설계서 용어 사용 현황
- **일관성 있는 곳**: 대부분 "회의 생성자", "참석자" 사용
- **"작성자" 사용 위치**:
1. UFR-TODO-020 (Todo완료): "Todo 작성자에게 완료 알림 발송"
2. UFR-TODO-040 (Todo관리): "담당자 본인 OR 회의 작성자인 경우에만 노출"
3. 일부 화면 설명에서 혼용
#### 프로토타입 용어 사용 현황
- 대부분 "회의 생성자" 사용
- 일부 주석에서 "작성자" 사용
#### 불일치 사항
1. 🟡 **용어 통일 필요**
- 유저스토리: "회의 생성자" 일관 사용
- UI/UX 설계서: 일부 "작성자" 혼재
- **수정 필요**: 모든 "작성자"를 "회의 생성자"로 변경
#### 권장 수정사항
```
[UI/UX 설계서 일괄 변경]
1. 검색 및 치환:
- "Todo 작성자" → "Todo 담당자" 또는 "회의 생성자"
- "회의 작성자" → "회의 생성자"
- "회의록 작성자" → "회의 생성자"
2. 컨텍스트별 명확화:
- 회의를 만든 사람: "회의 생성자"
- Todo를 만든 사람: "Todo 담당자"
- 회의에 참여한 사람: "참석자"
[프로토타입 수정]
- 주석 및 변수명에서 "작성자" → "생성자" 변경
- 예: creator, isCreator 등으로 통일
```
---
## 4. 권장 수정사항 우선순위
### Phase 1: 즉시 수정 (🔴 높음)
#### 1.1 07-회의종료 화면 전면 개편
**담당**: 강지수 (Product Designer)
**공수**: 2일
**작업 내역**:
1. 프로토타입 수정 (07-회의종료.html):
- 안건별 AI 요약 표시 추가
- "Todo 수정" 버튼 제거
- 하단 액션 3가지 옵션 구현:
- "회의록 수정" (옵션 1)
- "바로 최종 확정" (옵션 2, Primary)
- "대시보드로 이동" (옵션 3)
- "바로 최종 확정" 버튼 클릭 시 모든 안건 자동 검증 처리 로직 추가
2. UI/UX 설계서 수정 (uiux.md):
- "07-회의종료" 섹션 전체 재작성
- 편집 불가 정책 명시
- 안건별 AI 요약 표시 설명 추가
- 3가지 선택 옵션 상세 설명
- 각 옵션별 회의록 상태 변경 로직 명시
#### 1.2 06-검증완료 화면 삭제
**담당**: 강지수 (Product Designer)
**공수**: 0.5일
**작업 내역**:
1. 파일 삭제:
- design/uiux/prototype/06-검증완료.html 삭제
2. UI/UX 설계서 수정:
- "### 06-검증완료" 섹션 전체 삭제
- 프로토타입 화면 목록 테이블에서 제거
- 모든 화면에서 06-검증완료 링크 제거
3. 11-회의록수정 화면에 검증 기능 통합:
- 각 안건별 검증 체크박스 UI 추가
- 권한별 활성화 로직 구현
---
### Phase 2: 우선 수정 (🟡 중간)
#### 2.1 11-회의록수정 화면 안건 기반 구조 전환
**담당**: 강지수 (Product Designer)
**공수**: 3일
**작업 내역**:
1. 프로토타입 수정 (11-회의록수정.html):
- "섹션" → "안건" 용어 변경
- AI 한줄 요약 (읽기 전용) UI 추가
- AI 상세 요약 (편집 가능) 명칭 변경
- 안건별 검증 체크박스 추가
- 잠금 해제 버튼 추가 (회의 생성자만)
2. UI/UX 설계서 수정:
- "### 11-회의록수정" 섹션 재작성
- 안건 기반 구조 설명 추가
- AI 한줄 요약 vs 상세 요약 구분 설명
- 안건별 검증 UI 설명 추가
3. 스타일 가이드 업데이트 (style-guide.md):
- 안건 카드 컴포넌트 추가
- AI 한줄 요약 스타일 정의
- 안건별 검증 UI 스타일 정의
#### 2.2 용어 통일 ("작성자" → "생성자")
**담당**: 강지수 (Product Designer)
**공수**: 1일
**작업 내역**:
1. UI/UX 설계서 일괄 변경:
- "작성자" 검색 및 컨텍스트 확인
- "회의 생성자" 또는 "Todo 담당자"로 변경
2. 프로토타입 수정:
- 주석 및 변수명 일괄 변경
- creator, isCreator 등으로 통일
3. 용어 사전 추가 (uiux.md):
- "회의 생성자": 회의를 생성한 사람
- "참석자": 회의에 참여한 사람
- "Todo 담당자": Todo를 담당하는 사람
#### 2.3 회의록목록조회 생성자 표시 추가
**담당**: 강지수 (Product Designer)
**공수**: 0.5일
**작업 내역**:
1. 프로토타입 수정 (common.js):
- renderMinuteCard 함수에 생성자 표시 로직 추가
- 👑 아이콘 추가
2. UI/UX 설계서 수정:
- 목록 표시 정보에 "생성자 표시" 항목 추가
---
### Phase 3: 검토 후 수정 (🟢 낮음)
#### 3.1 05-회의진행 화면 실시간 주요 메모 추천 명확화
**담당**: 강지수, 도그냥
**공수**: 1일
**작업 내역**:
1. 현재 구현 검토:
- AI 제안 탭 기능이 실시간 주요 메모 추천인지 확인
- 유저스토리와 일치하는지 검증
2. UI/UX 설계서 명확화:
- 실시간 주요 메모 추천 기능 설명 추가
- AI 제안 탭과의 관계 명시
3. 필요 시 프로토타입 수정:
- 별도 UI 컴포넌트 추가
- 3-5초 간격 업데이트 로직 구현
#### 3.2 10-회의록상세조회 안건별 표시 명칭 변경
**담당**: 강지수
**공수**: 0.5일
**작업 내역**:
1. UI/UX 설계서 수정:
- "섹션별" → "안건별" 용어 변경
2. 프로토타입 수정:
- class 명칭 변경
- 주석 및 변수명 변경
#### 3.3 스타일 가이드 안건 컴포넌트 추가
**담당**: 강지수
**공수**: 1일
**작업 내역**:
1. style-guide.md 수정:
- 안건 카드 컴포넌트 스타일 정의
- AI 한줄 요약 스타일 추가
- 안건별 검증 UI 스타일 추가
- 예시 코드 작성
---
## 5. 총 작업 공수 및 일정
### 공수 요약
- **Phase 1 (즉시 수정)**: 2.5일
- 07-회의종료 화면 전면 개편: 2일
- 06-검증완료 화면 삭제: 0.5일
- **Phase 2 (우선 수정)**: 4.5일
- 11-회의록수정 안건 기반 구조 전환: 3일
- 용어 통일: 1일
- 생성자 표시 추가: 0.5일
- **Phase 3 (검토 후 수정)**: 2.5일
- 실시간 주요 메모 추천 명확화: 1일
- 안건별 표시 명칭 변경: 0.5일
- 스타일 가이드 업데이트: 1일
**총 공수**: 9.5일
### 권장 일정
- **Week 1 (5일)**: Phase 1 완료 + Phase 2 시작
- Day 1-2: 07-회의종료 전면 개편
- Day 3: 06-검증완료 삭제 + 용어 통일
- Day 4-5: 11-회의록수정 안건 구조 전환 (50%)
- **Week 2 (4.5일)**: Phase 2 완료 + Phase 3
- Day 6-7: 11-회의록수정 안건 구조 전환 완료
- Day 8: 생성자 표시 추가 + Phase 3 검토
- Day 9-10: Phase 3 수정 작업
---
## 6. 체크리스트
### Phase 1 완료 체크리스트
- [ ] 07-회의종료.html 안건별 AI 요약 표시 구현
- [ ] 07-회의종료.html "Todo 수정" 버튼 제거
- [ ] 07-회의종료.html 3가지 선택 옵션 구현
- [ ] 07-회의종료.html "바로 최종 확정" 로직 구현
- [ ] uiux.md "07-회의종료" 섹션 재작성
- [ ] 06-검증완료.html 파일 삭제
- [ ] uiux.md "06-검증완료" 섹션 삭제
- [ ] 프로토타입 화면 목록 테이블 업데이트
### Phase 2 완료 체크리스트
- [ ] 11-회의록수정.html "섹션" → "안건" 용어 변경
- [ ] 11-회의록수정.html AI 한줄 요약 UI 추가
- [ ] 11-회의록수정.html 안건별 검증 체크박스 추가
- [ ] uiux.md "11-회의록수정" 섹션 재작성
- [ ] uiux.md 전체 "작성자" → "생성자" 변경
- [ ] 프로토타입 용어 통일 (creator, isCreator)
- [ ] common.js 생성자 표시 로직 추가
- [ ] uiux.md "12-회의록목록조회" 생성자 표시 설명 추가
### Phase 3 완료 체크리스트
- [ ] 05-회의진행 실시간 주요 메모 추천 검토 완료
- [ ] uiux.md 실시간 주요 메모 추천 설명 추가
- [ ] 10-회의록상세조회 "섹션" → "안건" 변경
- [ ] style-guide.md 안건 컴포넌트 스타일 추가
---
## 7. 결론
### 주요 발견사항
1. 유저스토리 v2.1.2의 핵심 변경사항인 **"안건 기반 회의록 구조"**가 UI/UX 설계서와 프로토타입에 충분히 반영되지 않았습니다.
2. **07-회의종료 화면**의 "확인 전용" 정책과 "3가지 선택 옵션"이 구현되지 않아, 사용자 경험에 큰 영향을 미칠 수 있습니다.
3. **06-검증완료 화면**이 여전히 존재하여, 유저스토리의 "11-회의록수정 통합" 방침과 불일치합니다.
4. **용어 사용**이 일부 혼재되어 있어, 전체적인 일관성 개선이 필요합니다.
### 권장사항
1. **Phase 1 (즉시 수정)** 항목을 최우선으로 처리하여 핵심 사용자 플로우를 유저스토리와 일치시켜야 합니다.
2. **Phase 2 (우선 수정)** 항목은 전체적인 일관성과 정확성을 위해 2주 내 완료를 권장합니다.
3. **Phase 3 (검토 후 수정)** 항목은 검토 과정에서 실제 불일치 여부를 확인한 후 수정 여부를 결정하시기 바랍니다.
4. 모든 수정 작업 후 **통합 테스트**를 통해 유저스토리, UI/UX 설계서, 프로토타입 간 완전한 일관성을 확보해야 합니다.
---
**보고서 종료**