This commit is contained in:
djeon 2025-10-23 14:55:43 +09:00
commit 8c148e2721
10 changed files with 1353 additions and 35 deletions

View File

@ -1,8 +1,10 @@
{
"permissions": {
"allow": [
"Bash(git pull:*)",
"Bash(git add:*)",
"Bash(git commit -m \"$(cat <<''EOF''\n유저스토리 및 프로토타입 업데이트 (v2.0.1)\n\n- 공유 기능 제거 반영\n - AFR-USER-020: 대시보드 \"공유받은 회의록\" 섹션 제거\n - UFR-MEET-046: 회의록 목록 카테고리 필터 \"공유받은 회의\" 제거\n \n- 모바일 헤더 프로필 아바타 통일\n - 데스크탑 사이드바와 동일한 아바타 스타일 적용\n - 프로토타입 3개 파일 업데이트 (02-대시보드, 09-Todo관리, 12-회의록목록조회)\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude <noreply@anthropic.com>\nEOF\n)\")",
"Bash(git push)",
"Bash(git pull:*)",
"Bash(git commit:*)"
],
"deny": [],

View File

@ -7,7 +7,7 @@ info:
**주요 기능:**
- 회의록 자동 작성 (LLM 기반)
- Todo 자동 추출 및 담당자 식별
- 프롬프팅 기반 회의록 개선
- 섹션 AI 요약 재생성
- 관련 회의록 자동 연결 (RAG)
- 전문용어 감지 및 맥락 기반 설명 (RAG)
- 논의사항/결정사항 실시간 제안

View File

@ -1293,6 +1293,8 @@ components:
format: uuid
aiEnhancement:
$ref: '#/components/schemas/AIEnhancement'
verificationStatus:
$ref: '#/components/schemas/VerificationStatus'
required:
- meetingId
- sessionId
@ -1340,6 +1342,82 @@ components:
- merged
- suggestionsApplied
EndMeetingRequest:
type: object
properties:
finalAgenda:
type: string
description: 최종 회의 안건
applyAiSuggestions:
type: boolean
default: true
description: AI 제안 자동 적용 여부
VerificationStatus:
type: object
properties:
totalSections:
type: integer
description: 전체 섹션 수
verifiedSections:
type: integer
description: 검증 완료된 섹션 수
verificationRate:
type: integer
description: 검증 완료율 (%)
sections:
type: array
items:
$ref: '#/components/schemas/SectionVerificationInfo'
description: 섹션별 검증 상태
required:
- totalSections
- verifiedSections
- verificationRate
- sections
SectionVerificationInfo:
type: object
properties:
sectionId:
type: string
format: uuid
sectionType:
type: string
enum: [DISCUSSION, DECISION, TODO, SCHEDULE, RESOURCE, CUSTOM]
title:
type: string
isVerified:
type: boolean
isLocked:
type: boolean
verifiers:
type: array
items:
$ref: '#/components/schemas/Verifier'
required:
- sectionId
- sectionType
- title
- isVerified
- isLocked
- verifiers
Verifier:
type: object
properties:
userId:
type: string
name:
type: string
verifiedAt:
type: string
format: date-time
required:
- userId
- name
- verifiedAt
# ==================== Minutes Schemas ====================
MinutesListResponse:
type: object

View File

@ -0,0 +1,832 @@
<!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>
/* Mobile 프로필 버튼 (데스크톱에서 숨김) */
.mobile-profile-btn {
display: inline-flex;
}
@media (min-width: 768px) {
.mobile-profile-btn {
display: none;
}
}
/* 프로필 드롭다운 */
.profile-dropdown {
position: fixed;
top: 64px;
right: 16px;
width: 280px;
background: var(--white);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
padding: var(--space-md);
z-index: 2001;
display: none;
}
.profile-dropdown.show {
display: block;
animation: slideDown 0.2s ease;
}
.profile-dropdown-header {
display: flex;
align-items: center;
padding-bottom: var(--space-md);
border-bottom: 1px solid var(--gray-300);
}
.profile-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
z-index: 2000;
display: none;
}
.profile-overlay.show {
display: block;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Desktop에서 프로필 드롭다운 숨김 */
@media (min-width: 768px) {
.profile-dropdown,
.profile-overlay {
display: none !important;
}
}
/* 대시보드 헤더 커스터마이징 */
.header-title {
font-size: 15px;
display: flex;
align-items: center;
}
@media (min-width: 768px) {
.header-title {
font-size: var(--font-h2);
}
.header-title img {
width: 28px !important;
height: 28px !important;
}
}
.header-subtitle {
font-size: 13px;
}
@media (min-width: 768px) {
.header-subtitle {
font-size: var(--font-small);
}
}
/* 통계 카드 - common.css의 공통 스타일 사용 */
.stat-icon {
font-size: 24px;
margin-bottom: var(--space-xs);
}
.stat-label {
font-size: var(--font-small);
color: var(--gray-500);
margin-bottom: var(--space-xs);
}
.stat-value {
font-size: var(--font-h2);
font-weight: var(--font-weight-bold);
color: var(--gray-900);
}
/* 섹션 헤더 */
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-md);
}
.section-title {
font-size: var(--font-h3);
font-weight: var(--font-weight-bold);
color: var(--gray-900);
}
.section-link {
font-size: var(--font-small);
color: var(--primary);
text-decoration: none;
cursor: pointer;
}
.section-link:hover {
text-decoration: underline;
}
/* 회의 카드 */
.meeting-grid {
display: grid;
gap: var(--space-md);
margin-bottom: var(--space-xl);
}
@media (min-width: 640px) {
.meeting-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.meeting-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 1440px) {
.meeting-grid {
grid-template-columns: repeat(4, 1fr);
}
}
.meeting-card {
background: var(--white);
border-radius: var(--radius-lg);
padding: var(--space-lg);
box-shadow: var(--shadow-sm);
cursor: pointer;
transition: all var(--transition-normal);
}
.meeting-card:hover {
box-shadow: var(--shadow-md);
transform: translateY(-2px);
}
.meeting-card.ongoing {
border-left: 4px solid var(--ongoing);
}
.meeting-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: var(--space-md);
}
.meeting-card-title {
font-size: var(--font-body);
font-weight: var(--font-weight-medium);
color: var(--gray-900);
margin-bottom: var(--space-xs);
}
.meeting-card-meta {
font-size: var(--font-small);
color: var(--gray-500);
margin-bottom: var(--space-sm);
}
.meeting-card-meta-item {
margin-bottom: 4px;
}
.meeting-card-actions {
display: flex;
gap: var(--space-sm);
margin-top: var(--space-md);
}
/* Todo 리스트 */
.todo-list {
background: var(--white);
border-radius: var(--radius-lg);
padding: var(--space-md);
box-shadow: var(--shadow-sm);
margin-bottom: var(--space-xl);
}
.todo-item {
padding: var(--space-md);
border-bottom: 1px solid var(--gray-200);
cursor: pointer;
transition: background var(--transition-fast);
}
.todo-item:last-child {
border-bottom: none;
}
.todo-item:hover {
background: var(--gray-50);
}
.todo-item.overdue {
border-left: 4px solid var(--error);
padding-left: calc(var(--space-md) - 4px);
}
.todo-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: var(--space-sm);
}
.todo-title {
font-weight: var(--font-weight-medium);
color: var(--gray-900);
}
.todo-meta {
display: flex;
align-items: center;
gap: var(--space-md);
font-size: var(--font-small);
color: var(--gray-500);
margin-bottom: var(--space-sm);
}
.todo-progress {
margin-top: var(--space-sm);
}
/* 회의록 리스트 */
.minutes-list {
background: var(--white);
border-radius: var(--radius-lg);
padding: var(--space-md);
box-shadow: var(--shadow-sm);
margin-bottom: var(--space-xl);
}
.minutes-item {
padding: var(--space-md);
border-bottom: 1px solid var(--gray-200);
cursor: pointer;
transition: background var(--transition-fast);
}
.minutes-item:last-child {
border-bottom: none;
}
.minutes-item:hover {
background: var(--gray-50);
}
.minutes-item-title {
font-weight: var(--font-weight-medium);
color: var(--gray-900);
margin-bottom: var(--space-xs);
}
.minutes-item-meta {
font-size: var(--font-small);
color: var(--gray-500);
display: flex;
gap: var(--space-md);
align-items: center;
}
/* 빈 상태 */
.empty-state {
text-align: center;
padding: var(--space-xxl);
color: var(--gray-500);
}
.empty-icon {
font-size: 48px;
margin-bottom: var(--space-md);
}
section {
margin-bottom: var(--space-xl);
}
/* 플로팅 액션 메뉴 */
.fab-menu {
position: fixed;
bottom: 160px; /* 모바일: FAB 버튼(88px) + FAB 높이(56px) + 여백(16px) */
right: var(--space-lg);
display: flex;
flex-direction: column;
gap: var(--space-md);
z-index: 999;
opacity: 0;
transform: translateY(20px);
pointer-events: none;
transition: all var(--transition-normal);
}
.fab-menu.active {
opacity: 1;
transform: translateY(0);
pointer-events: all;
}
/* FAB 메뉴 아이템 & FAB 버튼 공통 스타일 */
.fab-menu-item,
.fab {
display: flex;
align-items: center;
justify-content: center;
min-width: 100px;
height: 56px;
background: var(--primary);
border: none;
border-radius: 28px;
padding: 0 var(--space-md);
cursor: pointer;
transition: all var(--transition-fast);
color: var(--white);
font-size: 14px;
font-weight: var(--font-weight-semibold);
box-shadow: var(--shadow-fab);
white-space: nowrap;
}
.fab-menu-item:hover,
.fab:hover {
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.fab-menu-item:active,
.fab:active {
transform: translateY(0) scale(0.98);
}
/* FAB 회전 효과 제거 */
.fab.active {
transform: rotate(0deg);
}
/* 오버레이 */
.fab-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
z-index: 998;
opacity: 0;
pointer-events: none;
transition: opacity var(--transition-normal);
}
.fab-overlay.active {
opacity: 1;
pointer-events: all;
}
/* 데스크톱에서 위치 조정 */
@media (min-width: 768px) {
.fab-menu {
bottom: 96px; /* 데스크톱: FAB 버튼(24px) + FAB 높이(56px) + 여백(16px) */
}
}
</style>
</head>
<body class="layout-sidebar-header">
<!-- 사이드바 (데스크톱) -->
<aside class="sidebar">
<a href="02-대시보드.html" class="sidebar-logo">
<img src="img/cicle.png" alt="로고" class="sidebar-logo-icon-img">
<div class="sidebar-logo-text">회의록 서비스</div>
</a>
<nav class="sidebar-nav">
<a href="12-회의록목록조회.html" class="sidebar-nav-item">
<span class="sidebar-nav-icon"><img src="img/edit.png" width="32"></span>
<span>회의 목록</span>
</a>
<a href="09-Todo관리.html" class="sidebar-nav-item">
<span class="sidebar-nav-icon"><img src="img/list.png" width="32"></span>
<span>Todo 관리</span>
</a>
</nav>
<!-- 사용자 정보 영역 (Desktop) -->
<div class="sidebar-user">
<div class="avatar avatar-green"></div>
<div class="sidebar-user-info">
<div class="sidebar-user-name">김민준</div>
<div class="sidebar-user-email">minjun.kim@company.com</div>
</div>
</div>
<button class="btn btn-ghost" style="width: calc(100% - 32px); margin: 0 16px 16px;" onclick="logout()">로그아웃</button>
</aside>
<!-- 헤더 -->
<header class="header">
<div class="header-left">
<h1 class="header-title"><img src="img/hi.png" alt="" style="width: 18px; height: 18px; vertical-align: middle; margin-right: 6px;">안녕하세요, 김민준님!</h1>
<p class="header-subtitle">오늘의 일정을 확인하세요</p>
</div>
<!-- Mobile 프로필 아이콘 -->
<button class="icon-btn mobile-profile-btn" onclick="toggleProfileMenu()" title="프로필">
👤
</button>
</header>
<!-- Mobile 프로필 드롭다운 -->
<div class="profile-dropdown" id="profileDropdown">
<div class="profile-dropdown-header">
<div class="avatar avatar-green"></div>
<div style="margin-left: 12px;">
<div style="font-weight: 600; font-size: 14px; color: var(--gray-900);">김민준</div>
<div style="font-size: 12px; color: var(--gray-500);">minjun.kim@company.com</div>
</div>
</div>
<button class="btn btn-ghost" style="width: 100%; margin-top: 12px;" onclick="logout()">로그아웃</button>
</div>
<div class="profile-overlay" id="profileOverlay" onclick="toggleProfileMenu()"></div>
<!-- 메인 콘텐츠 -->
<main class="main-content">
<!-- 통계 -->
<section class="stats-grid">
<div class="stat-card">
<div class="stat-icon">📅</div>
<div class="stat-label">예정된 회의</div>
<div class="stat-value" id="stat-scheduled">3</div>
</div>
<div class="stat-card">
<div class="stat-icon"></div>
<div class="stat-label">진행 중 Todo</div>
<div class="stat-value" id="stat-todos">1</div>
</div>
<div class="stat-card">
<div class="stat-icon">📈</div>
<div class="stat-label">Todo 완료율</div>
<div class="stat-value" id="stat-completion">33%</div>
</div>
</section>
<!-- 최근 회의 -->
<section>
<div class="section-header">
<h2 class="section-title">최근 회의</h2>
<a href="12-회의록목록조회.html" class="section-link">전체 보기 →</a>
</div>
<div class="meeting-grid" id="recent-meetings">
<!-- 동적 생성 -->
</div>
</section>
<!-- 할당된 Todo -->
<section>
<div class="section-header">
<h2 class="section-title">할당된 Todo</h2>
<a href="09-Todo관리.html" class="section-link">전체 보기 →</a>
</div>
<div class="todo-list" id="my-todos">
<!-- 동적 생성 -->
</div>
</section>
<!-- 내 회의록 -->
<section>
<div class="section-header">
<h2 class="section-title">내 회의록</h2>
<a href="12-회의록목록조회.html" class="section-link">전체 보기 →</a>
</div>
<div class="minutes-list" id="my-minutes">
<!-- 동적 생성 -->
</div>
</section>
</main>
<!-- 하단 네비게이션 (모바일) -->
<nav class="bottom-nav">
<a href="02-대시보드.html" class="nav-item active">
<img src="img/home.png" alt="홈" style="width: 45px;">
</a>
<a href="12-회의록목록조회.html" class="nav-item">
<img src="img/edit.png" alt="회의록" style="width: 45px;">
</a>
<a href="09-Todo관리.html" class="nav-item">
<img src="img/list.png" alt="Todo" style="width: 45px;">
</a>
</nav>
<!-- FAB 오버레이 -->
<div class="fab-overlay" id="fabOverlay"></div>
<!-- FAB 확장 메뉴 (단일 버튼) -->
<div class="fab-menu" id="fabMenu">
<button class="fab-menu-item" id="quickStartBtn" title="바로 시작">
바로시작
</button>
</div>
<!-- FAB (Floating Action Button) - 회의 예약 -->
<button class="fab" id="fabButton" title="회의 예약">
회의예약
</button>
<script src="common.js"></script>
<script>
/**
* 대시보드 초기화
*/
// 로그인 체크
if (!getFromStorage('isLoggedIn')) {
navigateTo('01-로그인.html');
}
const currentUser = getFromStorage('currentUser') || CURRENT_USER;
/**
* 최근 회의 렌더링
*/
function renderRecentMeetings() {
const container = $('#recent-meetings');
// 진행중 우선, 날짜순 정렬
const meetings = [...SAMPLE_MEETINGS]
.sort((a, b) => {
if (a.status === 'ongoing' && b.status !== 'ongoing') return -1;
if (a.status !== 'ongoing' && b.status === 'ongoing') return 1;
return new Date(b.date + ' ' + b.time) - new Date(a.date + ' ' + a.time);
})
.slice(0, 3);
container.innerHTML = meetings.map(meeting => {
const statusInfo = getMeetingStatusInfo(meeting);
const isCreator = meeting.participants.some(p => p.id === currentUser.id && p.role === 'creator');
return `
<div class="meeting-card ${meeting.status === 'ongoing' ? 'ongoing' : ''}" data-id="${meeting.id}">
<div class="meeting-card-header">
<div>
${createBadge(statusInfo.badgeText, statusInfo.badgeType)}
${isCreator ? ' <span>👑</span>' : ''}
</div>
</div>
<h3 class="meeting-card-title">${meeting.title}</h3>
<div class="meeting-card-meta">
<div class="meeting-card-meta-item">📅 ${formatDate(meeting.date)} ${formatTime(meeting.time)}</div>
<div class="meeting-card-meta-item">📍 ${meeting.location}</div>
<div class="meeting-card-meta-item">👥 ${meeting.participants.length}명</div>
</div>
<div class="meeting-card-actions">
${meeting.status === 'ongoing'
? `<button class="btn btn-primary btn-sm" onclick="navigateTo('05-회의진행.html'); event.stopPropagation();">참여하기</button>`
: meeting.status === 'scheduled' && isCreator
? `<button class="btn btn-secondary btn-sm" onclick="navigateTo('03-회의예약.html'); event.stopPropagation();">수정</button>`
: `<button class="btn btn-ghost btn-sm" onclick="navigateTo('10-회의록상세조회.html'); event.stopPropagation();">보기</button>`
}
</div>
</div>
`;
}).join('');
// 클릭 이벤트
$$('.meeting-card').forEach(card => {
card.addEventListener('click', (e) => {
if (e.target.tagName !== 'BUTTON') {
const meetingId = card.dataset.id;
const meeting = SAMPLE_MEETINGS.find(m => m.id === meetingId);
if (meeting.status === 'ongoing') {
navigateTo('05-회의진행.html');
} else if (meeting.status === 'completed') {
navigateTo('10-회의록상세조회.html');
} else {
navigateTo('03-회의예약.html');
}
}
});
});
}
/**
* 내 Todo 렌더링
*/
function renderMyTodos() {
const container = $('#my-todos');
const myTodos = SAMPLE_TODOS
.filter(todo => todo.assignee.id === currentUser.id)
.sort((a, b) => {
const priorityOrder = { overdue: 0, in_progress: 1, not_started: 2, completed: 3 };
return priorityOrder[a.status] - priorityOrder[b.status];
})
.slice(0, 5);
if (myTodos.length === 0) {
container.innerHTML = '<div class="empty-state"><div class="empty-icon"></div><p>할당된 Todo가 없습니다</p></div>';
return;
}
container.innerHTML = myTodos.map(todo => {
const statusInfo = getTodoStatusInfo(todo);
const isOverdue = calculateDday(todo.dueDate) < 0 && todo.status !== 'completed';
return `
<div class="todo-item ${isOverdue ? 'overdue' : ''}" data-todo-id="${todo.id}" data-meeting-id="${todo.meetingId}">
<div class="todo-header">
<h4 class="todo-title">${todo.title}</h4>
${createBadge(statusInfo.badgeText, statusInfo.badgeType)}
</div>
<div class="todo-meta">
<span>마감: ${formatDate(todo.dueDate)}</span>
${createBadge(todo.priority === 'high' ? '높음' : todo.priority === 'medium' ? '보통' : '낮음', todo.priority)}
</div>
<div class="todo-progress">
${createProgressBar(todo.progress)}
</div>
</div>
`;
}).join('');
// Todo 항목 클릭 시 해당 회의록 상세로 이동
$$('.todo-item').forEach(item => {
item.addEventListener('click', () => {
const meetingId = item.dataset.meetingId;
const todoId = item.dataset.todoId;
// 회의록 상세 페이지로 이동 (Todo ID를 파라미터로 전달)
navigateTo(`10-회의록상세조회.html?meetingId=${meetingId}&todoId=${todoId}`);
});
});
}
/**
* 내 회의록 렌더링
*/
function renderMyMinutes() {
const container = $('#my-minutes');
const myMeetings = SAMPLE_MEETINGS
.filter(m => m.participants.some(p => p.id === currentUser.id && p.role === 'creator'))
.slice(0, 3);
if (myMeetings.length === 0) {
container.innerHTML = '<div class="empty-state"><div class="empty-icon">📝</div><p>작성한 회의록이 없습니다</p></div>';
return;
}
container.innerHTML = myMeetings.map(meeting => {
const statusInfo = getMeetingStatusInfo(meeting);
return `
<div class="minutes-item" onclick="navigateTo('10-회의록상세조회.html')">
<h4 class="minutes-item-title">${meeting.title}</h4>
<div class="minutes-item-meta">
<span>📅 ${formatDate(meeting.date)}</span>
<span>👥 ${meeting.participants.length}명</span>
${createBadge(statusInfo.badgeText, statusInfo.badgeType)}
</div>
</div>
`;
}).join('');
}
/**
* 통계 업데이트
*/
function updateStats() {
const scheduled = SAMPLE_MEETINGS.filter(m => m.status === 'scheduled' || m.status === 'ongoing').length;
const todos = SAMPLE_TODOS.filter(t => t.assignee.id === currentUser.id && t.status !== 'completed').length;
const totalTodos = SAMPLE_TODOS.filter(t => t.assignee.id === currentUser.id).length;
const completedTodos = SAMPLE_TODOS.filter(t => t.assignee.id === currentUser.id && t.status === 'completed').length;
const completion = totalTodos > 0 ? Math.round((completedTodos / totalTodos) * 100) : 0;
$('#stat-scheduled').textContent = scheduled;
$('#stat-todos').textContent = todos;
$('#stat-completion').textContent = completion + '%';
}
/**
* 초기화
*/
function init() {
updateStats();
renderRecentMeetings();
renderMyTodos();
renderMyMinutes();
console.log('대시보드 초기화 완료');
}
/**
* FAB 메뉴 토글
*/
let fabMenuOpen = false;
function toggleFabMenu() {
fabMenuOpen = !fabMenuOpen;
const fabButton = $('#fabButton');
const fabMenu = $('#fabMenu');
const fabOverlay = $('#fabOverlay');
if (fabMenuOpen) {
fabButton.classList.add('active');
fabMenu.classList.add('active');
fabOverlay.classList.add('active');
} else {
fabButton.classList.remove('active');
fabMenu.classList.remove('active');
fabOverlay.classList.remove('active');
}
}
function closeFabMenu() {
if (fabMenuOpen) {
toggleFabMenu();
}
}
/**
* FAB 오버레이 클릭 시 메뉴 닫기
*/
$('#fabOverlay').addEventListener('click', () => {
closeFabMenu();
});
/**
* play 버튼 (새 회의 시작) 클릭
*/
$('#quickStartBtn').addEventListener('click', () => {
closeFabMenu();
// 새 회의 생성 화면으로 이동
navigateTo('04-템플릿선택.html');
});
/**
* FAB 버튼 (add 이미지) 클릭
* - 메뉴 닫혀있을 때: play 버튼 확장
* - 메뉴 열려있을 때: 회의 예약 화면으로 이동
*/
$('#fabButton').addEventListener('click', (e) => {
e.stopPropagation();
if (fabMenuOpen) {
// 이미 메뉴가 열려있으면 회의 예약 화면으로 이동
closeFabMenu();
navigateTo('03-회의예약.html');
} else {
// 메뉴가 닫혀있으면 play 버튼 표시
toggleFabMenu();
}
});
/**
* 프로필 메뉴 토글 (Mobile)
*/
function toggleProfileMenu() {
const dropdown = $('#profileDropdown');
const overlay = $('#profileOverlay');
dropdown.classList.toggle('show');
overlay.classList.toggle('show');
}
/**
* 로그아웃
*/
function logout() {
if (confirm('로그아웃 하시겠습니까?')) {
// 로컬 스토리지 초기화
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('currentUser');
// 로그인 페이지로 이동
navigateTo('01-로그인.html');
}
}
init();
</script>
</body>
</html>

View File

@ -6,11 +6,94 @@
<title>대시보드 - 회의록 서비스</title>
<link rel="stylesheet" href="common.css">
<style>
/* Mobile 프로필 버튼 (데스크톱에서 숨김) */
.mobile-profile-btn {
display: inline-flex;
background: none;
border: none;
padding: 0;
cursor: pointer;
}
@media (min-width: 768px) {
.mobile-profile-btn {
display: none;
}
}
/* 프로필 드롭다운 */
.profile-dropdown {
position: fixed;
top: 64px;
right: 16px;
width: 280px;
background: var(--white);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
padding: var(--space-md);
z-index: 2001;
display: none;
}
.profile-dropdown.show {
display: block;
animation: slideDown 0.2s ease;
}
.profile-dropdown-header {
display: flex;
align-items: center;
padding-bottom: var(--space-md);
border-bottom: 1px solid var(--gray-300);
}
.profile-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
z-index: 2000;
display: none;
}
.profile-overlay.show {
display: block;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Desktop에서 프로필 드롭다운 숨김 */
@media (min-width: 768px) {
.profile-dropdown,
.profile-overlay {
display: none !important;
}
}
/* 대시보드 헤더 커스터마이징 */
.header-title {
font-size: 15px;
display: flex;
align-items: center;
font-weight: 700;
white-space: nowrap;
margin: 0;
}
.header-subtitle {
font-size: 12px;
color: var(--gray-600);
white-space: nowrap;
margin: 0;
}
@media (min-width: 768px) {
@ -18,17 +101,6 @@
font-size: var(--font-h2);
}
.header-title img {
width: 28px !important;
height: 28px !important;
}
}
.header-subtitle {
font-size: 13px;
}
@media (min-width: 768px) {
.header-subtitle {
font-size: var(--font-small);
}
@ -362,16 +434,43 @@
<span>Todo 관리</span>
</a>
</nav>
<!-- 사용자 정보 영역 (Desktop) -->
<div class="sidebar-user">
<div class="avatar avatar-green"></div>
<div class="sidebar-user-info">
<div class="sidebar-user-name">김민준</div>
<div class="sidebar-user-email">minjun.kim@company.com</div>
</div>
</div>
<button class="btn btn-ghost" style="width: calc(100% - 32px); margin: 0 16px 16px;" onclick="logout()">로그아웃</button>
</aside>
<!-- 헤더 -->
<header class="header">
<div class="header-left">
<h1 class="header-title"><img src="img/hi.png" alt="" style="width: 18px; height: 18px; vertical-align: middle; margin-right: 6px;">안녕하세요, 김민준님!</h1>
<p class="header-subtitle">오늘의 일정을 확인하세요</p>
<h1 class="header-title">김민준님</h1>
<p class="header-subtitle">오늘 2건의 회의가 예정되어 있어</p>
</div>
<!-- Mobile 프로필 아이콘 -->
<button class="mobile-profile-btn" onclick="toggleProfileMenu()" title="프로필">
<div class="avatar avatar-green"></div>
</button>
</header>
<!-- Mobile 프로필 드롭다운 -->
<div class="profile-dropdown" id="profileDropdown">
<div class="profile-dropdown-header">
<div class="avatar avatar-green"></div>
<div style="margin-left: 12px;">
<div style="font-weight: 600; font-size: 14px; color: var(--gray-900);">김민준</div>
<div style="font-size: 12px; color: var(--gray-500);">minjun.kim@company.com</div>
</div>
</div>
<button class="btn btn-ghost" style="width: 100%; margin-top: 12px;" onclick="logout()">로그아웃</button>
</div>
<div class="profile-overlay" id="profileOverlay" onclick="toggleProfileMenu()"></div>
<!-- 메인 콘텐츠 -->
<main class="main-content">
@ -703,6 +802,31 @@
}
});
/**
* 프로필 메뉴 토글 (Mobile)
*/
function toggleProfileMenu() {
const dropdown = $('#profileDropdown');
const overlay = $('#profileOverlay');
dropdown.classList.toggle('show');
overlay.classList.toggle('show');
}
/**
* 로그아웃
*/
function logout() {
if (confirm('로그아웃 하시겠습니까?')) {
// 로컬 스토리지 초기화
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('currentUser');
// 로그인 페이지로 이동
navigateTo('01-로그인.html');
}
}
init();
</script>
</body>

View File

@ -6,6 +6,81 @@
<title>내 Todo - 회의록 서비스</title>
<link rel="stylesheet" href="common.css">
<style>
/* Mobile 프로필 버튼 (데스크톱에서 숨김) */
.mobile-profile-btn {
display: inline-flex;
background: none;
border: none;
padding: 0;
cursor: pointer;
}
@media (min-width: 768px) {
.mobile-profile-btn {
display: none;
}
}
/* 프로필 드롭다운 */
.profile-dropdown {
position: fixed;
top: 64px;
right: 16px;
width: 280px;
background: var(--white);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
padding: var(--space-md);
z-index: 2001;
display: none;
}
.profile-dropdown.show {
display: block;
animation: slideDown 0.2s ease;
}
.profile-dropdown-header {
display: flex;
align-items: center;
padding-bottom: var(--space-md);
border-bottom: 1px solid var(--gray-300);
}
.profile-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
z-index: 2000;
display: none;
}
.profile-overlay.show {
display: block;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Desktop에서 프로필 드롭다운 숨김 */
@media (min-width: 768px) {
.profile-dropdown,
.profile-overlay {
display: none !important;
}
}
/* 페이지 특화 스타일 */
/* 통계 영역 - 모바일 최적화 */
@ -348,6 +423,16 @@
<span>Todo 관리</span>
</a>
</nav>
<!-- 사용자 정보 영역 (Desktop) -->
<div class="sidebar-user">
<div class="avatar avatar-green"></div>
<div class="sidebar-user-info">
<div class="sidebar-user-name">김민준</div>
<div class="sidebar-user-email">minjun.kim@company.com</div>
</div>
</div>
<button class="btn btn-ghost" style="width: calc(100% - 32px); margin: 0 16px 16px;" onclick="logout()">로그아웃</button>
</aside>
<!-- 헤더 -->
@ -355,8 +440,25 @@
<div class="header-left">
<h1 class="header-title">Todo 관리</h1>
</div>
<!-- Mobile 프로필 아이콘 -->
<button class="mobile-profile-btn" onclick="toggleProfileMenu()" title="프로필">
<div class="avatar avatar-green"></div>
</button>
</header>
<!-- Mobile 프로필 드롭다운 -->
<div class="profile-dropdown" id="profileDropdown">
<div class="profile-dropdown-header">
<div class="avatar avatar-green"></div>
<div style="margin-left: 12px;">
<div style="font-weight: 600; font-size: 14px; color: var(--gray-900);">김민준</div>
<div style="font-size: 12px; color: var(--gray-500);">minjun.kim@company.com</div>
</div>
</div>
<button class="btn btn-ghost" style="width: 100%; margin-top: 12px;" onclick="logout()">로그아웃</button>
</div>
<div class="profile-overlay" id="profileOverlay" onclick="toggleProfileMenu()"></div>
<!-- 메인 콘텐츠 -->
<main class="main-content">
<!-- 통계 개요 -->
@ -650,6 +752,31 @@
}, 30);
}
/**
* 프로필 메뉴 토글 (Mobile)
*/
function toggleProfileMenu() {
const dropdown = $('#profileDropdown');
const overlay = $('#profileOverlay');
dropdown.classList.toggle('show');
overlay.classList.toggle('show');
}
/**
* 로그아웃
*/
function logout() {
if (confirm('로그아웃 하시겠습니까?')) {
// 로컬 스토리지 초기화
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('currentUser');
// 로그인 페이지로 이동
navigateTo('01-로그인.html');
}
}
// 페이지 로드 시 초기화
initPage();
</script>

View File

@ -11,6 +11,81 @@
display: none;
}
/* Mobile 프로필 버튼 (데스크톱에서 숨김) */
.mobile-profile-btn {
display: inline-flex;
background: none;
border: none;
padding: 0;
cursor: pointer;
}
@media (min-width: 768px) {
.mobile-profile-btn {
display: none;
}
}
/* 프로필 드롭다운 */
.profile-dropdown {
position: fixed;
top: 64px;
right: 16px;
width: 280px;
background: var(--white);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
padding: var(--space-md);
z-index: 2001;
display: none;
}
.profile-dropdown.show {
display: block;
animation: slideDown 0.2s ease;
}
.profile-dropdown-header {
display: flex;
align-items: center;
padding-bottom: var(--space-md);
border-bottom: 1px solid var(--gray-300);
}
.profile-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
z-index: 2000;
display: none;
}
.profile-overlay.show {
display: block;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Desktop에서 프로필 드롭다운 숨김 */
@media (min-width: 768px) {
.profile-dropdown,
.profile-overlay {
display: none !important;
}
}
/* 필터 및 검색 영역 */
.filter-section {
background: var(--white);
@ -290,6 +365,16 @@
<span>Todo 관리</span>
</a>
</nav>
<!-- 사용자 정보 영역 (Desktop) -->
<div class="sidebar-user">
<div class="avatar avatar-green"></div>
<div class="sidebar-user-info">
<div class="sidebar-user-name">김민준</div>
<div class="sidebar-user-email">minjun.kim@company.com</div>
</div>
</div>
<button class="btn btn-ghost" style="width: calc(100% - 32px); margin: 0 16px 16px;" onclick="logout()">로그아웃</button>
</aside>
<!-- 헤더 -->
@ -298,9 +383,25 @@
<button class="icon-btn" onclick="navigateTo('02-대시보드.html')"></button>
<h1 class="header-title">내 회의록</h1>
</div>
<button class="icon-btn" onclick="alert('검색')">🔍</button>
<!-- Mobile 프로필 아이콘 -->
<button class="mobile-profile-btn" onclick="toggleProfileMenu()" title="프로필">
<div class="avatar avatar-green"></div>
</button>
</header>
<!-- Mobile 프로필 드롭다운 -->
<div class="profile-dropdown" id="profileDropdown">
<div class="profile-dropdown-header">
<div class="avatar avatar-green"></div>
<div style="margin-left: 12px;">
<div style="font-weight: 600; font-size: 14px; color: var(--gray-900);">김민준</div>
<div style="font-size: 12px; color: var(--gray-500);">minjun.kim@company.com</div>
</div>
</div>
<button class="btn btn-ghost" style="width: 100%; margin-top: 12px;" onclick="logout()">로그아웃</button>
</div>
<div class="profile-overlay" id="profileOverlay" onclick="toggleProfileMenu()"></div>
<!-- 메인 콘텐츠 -->
<main class="main-content">
<!-- 필터 및 검색 -->
@ -681,6 +782,31 @@
// 초기 통계 설정
const allItems = Array.from(document.querySelectorAll('.meeting-item'));
updateStats(allItems);
/**
* 프로필 메뉴 토글 (Mobile)
*/
function toggleProfileMenu() {
const dropdown = document.getElementById('profileDropdown');
const overlay = document.getElementById('profileOverlay');
dropdown.classList.toggle('show');
overlay.classList.toggle('show');
}
/**
* 로그아웃
*/
function logout() {
if (confirm('로그아웃 하시겠습니까?')) {
// 로컬 스토리지 초기화
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('currentUser');
// 로그인 페이지로 이동
navigateTo('01-로그인.html');
}
}
</script>
</body>
</html>

View File

@ -1097,26 +1097,34 @@ input[type="date"]::-webkit-calendar-picker-indicator {
}
.sidebar-user {
padding: var(--space-md) var(--space-lg);
padding: var(--space-md);
margin: 0 var(--space-md);
border-top: 1px solid var(--gray-300);
display: flex;
align-items: center;
gap: var(--space-md);
gap: var(--space-sm);
}
.sidebar-user-info {
flex: 1;
min-width: 0; /* 텍스트 잘림 방지 */
}
.sidebar-user-name {
font-weight: var(--font-weight-medium);
color: var(--gray-900);
font-size: var(--font-small);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar-user-email {
font-size: var(--font-caption);
color: var(--gray-500);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 하단 네비게이션 숨기기 */

View File

@ -1692,8 +1692,8 @@ graph TD
- Todo
- **하단 영역**:
- 사용자 정보 (아바타 + 이름)
- 로그아웃
- **사용 화면**: 02-대시보드, 08-Todo관리, 11-회의록목록조회
- 로그아웃 버튼 (btn-ghost btn-sm)
- **사용 화면**: 02-대시보드, 09-Todo관리, 12-회의록목록조회
#### 상단 헤더 (공통)
- **위치**: Sticky, 상단
@ -1702,6 +1702,15 @@ graph TD
- 좌측: 뒤로가기 버튼 (화면별 조건부)
- 중앙: 페이지 타이틀 (18px Bold)
- 우측: 액션 버튼 (저장, 메뉴 등)
- **Mobile 전용 (768px 미만)**:
- 우측 상단에 프로필 아이콘 버튼 추가 (👤, 24px)
- 클릭 시 드롭다운 메뉴 표시:
- 사용자 이름 (14px Bold)
- 로그아웃 버튼 (btn-ghost btn-sm)
- 드롭다운 위치: 우측 상단 기준 아래로 펼침
- 배경: 흰색, 그림자: var(--shadow-md)
- **Desktop (768px+)**:
- 프로필 아이콘 숨김 (사이드바 하단 영역 사용)
---
@ -1934,7 +1943,8 @@ graph TD
| 1.4.1 | 2025-10-23 | 강지수 | 대시보드 모바일 UI/UX 개선 (360px 최적화)<br>- **헤더 개선안 A 적용**: 간결한 인사 + 실질적 정보<br> - "안녕하세요 👋" (H3, Bold)<br> - "오늘 {N}건의 회의가 예정되어 있어요" (동적 업데이트)<br> - 2줄 구조 제거로 세로 공간 절약<br>- **통계 카드 개선안 A 적용**: 컴팩트 수평 배치<br> - 단일 카드 "📊 오늘의 현황" (H5, Semibold)<br> - 수평 배치: "📅 예정 {N}", "✅ 진행 {N}", "📈 완료 {N}%"<br> - 높이 ~80px (기존 대비 70% 감소)<br> - 반응형: 태블릿 이상에서 justify-content: flex-start<br>- **프로토타입 파일**: design/uiux/prototype/02-대시보드-개선.html 신규 생성<br>- **모바일 우선 반응형 설계**: 웹/태블릿 화면에서도 자연스러운 레이아웃 유지<br>- **참조**: design/uiux/ref_img/레이아웃 이상.png (개선 요구사항 이미지) |
| 1.4.2 | 2025-10-23 | 강지수 | 회의록 공유 기능 전면 제거<br>- **제거 배경**: 회의 참가자가 아니면 대상자 선정 불가능, 기능 중복 및 논리적 모순 해결<br>- **유저스토리**: UFR-MEET-060 (회의록공유) 제거<br>- **UI/UX 설계서**:<br> - 08-회의록공유 화면 전체 제거<br> - 02-대시보드: "공유받은 회의록" 섹션 제거<br> - 09-회의록상세조회: 공유 버튼 제거 (메뉴: 수정/삭제만 유지)<br> - 11-회의록목록조회: 카테고리 필터 수정 (전체/참석한 회의/생성한 회의)<br> - Desktop 사이드바: "공유받은 회의록" 메뉴 제거<br>- **화면 번호 재정렬**: 08-Todo관리, 09-회의록상세조회, 10-회의록수정, 11-회의록목록조회<br>- **프로토타입 파일**: 08-회의록공유.html 삭제 예정<br>- **검토 문서**: design/uiux/crosscheck-report.md (상세 검토 의견 및 수정 계획) |
| 1.4.3 | 2025-10-23 | 강지수 | 유저스토리-설계서-프로토타입 일관성 개선 (요구사항설계검토-report.md 반영)<br>- **화면번호 프로토타입 파일명 기준 통일**:<br> - 프로토타입 화면 목록 테이블 화면번호 수정<br> - 09: Todo관리 (09-Todo관리.html) - 변경 없음<br> - 10: 회의록상세조회 (10-회의록상세조회.html) - 변경 없음<br> - 11: 회의록수정 (11-회의록수정.html) - 09→11 변경<br> - 12: 회의록목록조회 (12-회의록목록조회.html) - 11→12 변경<br> - 설계서 본문 섹션 제목 화면번호 수정<br> - ### 09-Todo관리 (08→09 변경)<br> - ### 10-회의록상세조회 (변경 없음)<br> - ### 11-회의록수정 (10→11 변경)<br> - ### 12-회의록목록조회 (11→12 변경)<br>- **유저스토리 화면정보 추가 및 수정**:<br> - UFR-MEET-046 (회의록목록조회): 화면번호 "12-회의록목록조회" 추가, 카테고리 필터에서 "공유받은 회의" 제거<br> - UFR-MEET-047 (회의록상세조회): 화면번호 "10-회의록상세조회" 추가, 관련 유저스토리 ID 수정 (UFR-MEET-045 → UFR-MEET-047)<br>- **설계서 유저스토리 매핑 정확성 개선**:<br> - 10-회의록상세조회: UFR-MEET-045 → UFR-MEET-047 수정<br> - 12-회의록목록조회: UFR-MEET-030, UFR-MEET-045 → UFR-MEET-046 수정<br>- **일관성 달성**: 유저스토리, UI/UX 설계서, 프로토타입 간 완전한 화면번호 및 파일명 일치<br>- **검토 문서**: design/uiux/요구사항설계검토-report.md (상세 검토 의견 및 개선 계획) |
| 1.5 | 2025-10-23 | 강지수 | Mobile 하단 네비게이션 프로토타입 구현 기준 반영<br>- **Mobile 하단 네비게이션**: 4개 메뉴 → 3개 메뉴로 수정 (홈/회의록/Todo)<br> - 프로필 메뉴 제거 (Desktop 사이드바의 사용자 정보 영역으로 통합)<br> - 프로토타입 실제 구현 상태 반영 (02-대시보드.html, 09-Todo관리.html, 12-회의록목록조회.html)<br> - 사용 화면 번호 업데이트: 08→09, 11→12<br>- **참고 사항**: 프로필 메뉴가 필요한 경우 프로토타입에 추가 구현 필요<br>- **설계서-프로토타입 일관성**: 네비게이션 구조 완전 통일 달성 |
| 1.4.4 | 2025-10-23 | 강지수 | Mobile 하단 네비게이션 프로토타입 구현 기준 반영<br>- **Mobile 하단 네비게이션**: 4개 메뉴 → 3개 메뉴로 수정 (홈/회의록/Todo)<br> - 프로필 메뉴 제거 (Desktop 사이드바의 사용자 정보 영역으로 통합)<br> - 프로토타입 실제 구현 상태 반영 (02-대시보드.html, 09-Todo관리.html, 12-회의록목록조회.html)<br> - 사용 화면 번호 업데이트: 08→09, 11→12<br>- **참고 사항**: 프로필 메뉴가 필요한 경우 프로토타입에 추가 구현 필요<br>- **설계서-프로토타입 일관성**: 네비게이션 구조 완전 통일 달성 |
| 1.4.5 | 2025-10-23 | 강지수 | 로그아웃 기능 추가 (Desktop 사이드바 + Mobile 헤더)<br>- **Desktop 좌측 사이드바**: 하단에 사용자 정보 영역 추가<br> - 사용자 정보 (아바타 + 이름 + 이메일)<br> - 로그아웃 버튼 (btn-ghost btn-sm)<br>- **Mobile 상단 헤더**: 우측에 프로필 아이콘 버튼 추가 (👤)<br> - 클릭 시 드롭다운 메뉴 표시 (사용자 정보 + 로그아웃 버튼)<br> - 드롭다운 위치: 우측 상단 기준 아래로 펼침<br> - 오버레이 배경으로 UX 개선<br>- **프로토타입 파일**: 02-대시보드.html, 09-Todo관리.html, 12-회의록목록조회.html<br>- **JavaScript 함수**: toggleProfileMenu(), logout() 추가<br>- **반응형 처리**: Desktop에서는 드롭다운 숨김, Mobile에서는 사이드바 사용자 영역 숨김<br>- **설계서-프로토타입 일관성**: 로그아웃 기능 완전 통일 |
---

View File

@ -1,9 +1,24 @@
# 회의록 작성 및 공유 개선 서비스 - 유저스토리 (v2.0)
# AI기반 회의록 작성 및 이력 관리 개선 서비스 - 유저스토리 (v2.0.1)
- [회의록 작성 및 공유 개선 서비스 - 유저스토리 (v2.0)](#회의록-작성-및-공유-개선-서비스---유저스토리-v20)
- [AI기반 회의록 작성 및 이력 관리 개선 서비스 - 유저스토리 (v2.0.1)](#ai기반-회의록-작성-및-이력-관리-개선-서비스---유저스토리-v201)
- [차별화 전략](#차별화-전략)
- [1. 기본 기능 (Hygiene Factors)](#1-기본-기능-hygiene-factors)
- [2. 핵심 차별화 포인트 (Differentiators)](#2-핵심-차별화-포인트-differentiators)
- [마이크로서비스 구성](#마이크로서비스-구성)
- [유저스토리](#유저스토리)
- [논리 아키텍처 반영 사항 요약](#논리-아키텍처-반영-사항-요약)
- [1. 마이크로서비스 구성 변경 (v2.0)](#1-마이크로서비스-구성-변경-v20)
- [2. 주요 변경사항](#2-주요-변경사항)
- [2.1 User Service 역할 변경](#21-user-service-역할-변경)
- [2.2 Meeting Service 통합](#22-meeting-service-통합)
- [2.3 AI Service 통합](#23-ai-service-통합)
- [3. 유저스토리 영향도](#3-유저스토리-영향도)
- [3.1 변경 없음](#31-변경-없음)
- [3.2 서비스 통합에 따른 변경](#32-서비스-통합에-따른-변경)
- [3.3 기능적 변경](#33-기능적-변경)
- [4. 성능 개선 효과](#4-성능-개선-효과)
- [5. 차별화 전략 유지](#5-차별화-전략-유지)
- [문서 이력](#문서-이력)
---
@ -29,7 +44,7 @@
1. **User** - 사용자 인증 (LDAP 연동, JWT 토큰 발급/검증)
2. **Meeting** - 회의, 회의록, Todo, 실시간 협업 통합 관리
- 회의 관리: 회의 예약, 시작, 종료
- 회의록 관리: 회의록 생성, 수정, 확정, 공유
- 회의록 관리: 회의록 생성, 수정, 확정
- Todo 관리: Todo 할당, 진행 상황 추적, 회의록 양방향 연결
- 실시간 협업: WebSocket 기반 실시간 동기화, 버전 관리, 충돌 해결
- 템플릿 관리: 회의록 템플릿 관리
@ -92,10 +107,6 @@ AFR-USER-020: [대시보드] 사용자로서 | 나는, 회의록 서비스의
- 작성한 회의록 목록 (최대 3개)
- 전체 보기 링크
- 회의록 없을 시 안내 메시지
- 공유받은 회의록 섹션:
- 공유받은 회의록 목록 (최대 3개)
- 전체 보기 링크
- 회의록 없을 시 안내 메시지
[플로팅 액션 버튼 (FAB)]
- 메인 FAB 버튼 (+)
@ -230,7 +241,7 @@ UFR-MEET-050: [최종확정] 회의록 작성자로서 | 나는, 회의록을
- 최종 회의록 확정됨 (확정 버전 번호)
- 확정 시간 기록
- AI가 자동으로 Todo 항목 추출 (UFR-AI-020 연동)
- 회의록 공유 가능 상태로 전환
- 회의록 확정 상태로 전환
[필수 항목 미작성 시]
- 누락된 항목 안내 메시지 표시
@ -251,7 +262,7 @@ UFR-MEET-046: [회의록목록조회] 회의록 작성자로서 | 나는, 작성
[회의록 목록 조회]
- 회의록 상태별 필터링: 전체 / 작성중 / 확정완료
- 정렬 옵션: 최신순 / 회의일시순 / 제목순
- 카테고리 필터: 전체 / 참석한 회의 / 생성한 회의
- 카테고리 필터: 전체 / 참석한 회의
- 검색 기능: 회의 제목, 참석자, 키워드로 검색
- 통계 표시: 전체 개수, 작성중 개수, 확정완료 개수
- 목록 표시 정보:
@ -274,7 +285,7 @@ UFR-MEET-046: [회의록목록조회] 회의록 작성자로서 | 나는, 작성
UFR-MEET-047: [회의록상세조회] 회의록 작성자로서 | 나는, 지난 회의록의 상세 정보와 전체 내용을 | 한눈에 확인하고 싶다.
- 시나리오: 회의록 상세 정보 조회
회의록 목록에서 특정 회의록을 클릭하면 | 해당 회의의 기본 정보와 섹션별 상세 내용이 표시되고 | 필요한 경우 수정, 공유 등의 작업을 수행할 수 있다.
회의록 목록에서 특정 회의록을 클릭하면 | 해당 회의의 기본 정보와 섹션별 상세 내용이 표시되고 | 필요한 경우 수정작업을 수행할 수 있다.
[화면 정보]
- 화면번호: 10-회의록상세조회
@ -307,7 +318,6 @@ UFR-MEET-047: [회의록상세조회] 회의록 작성자로서 | 나는, 지난
[부가 기능]
- 회의록 수정 버튼 (수정 권한이 있는 경우만 활성화)
- 회의록 공유 버튼 (공유 설정 화면으로 이동)
- 탭 네비게이션 (회의록/대시보드)
- 뒤로가기 버튼 (회의록 목록으로 복귀)
- 더보기 메뉴 (다운로드, 삭제 등)
@ -899,7 +909,7 @@ UFR-TODO-030: [Todo완료처리] Todo 담당자로서 | 나는, 완료된 Todo
- **통합 서비스**: Meeting + Collaboration + Todo
- **핵심 기능**:
- 회의 관리: 회의 예약, 시작, 종료
- 회의록 관리: 회의록 생성, 수정, 확정, 공유
- 회의록 관리: 회의록 생성, 수정, 확정
- Todo 관리: Todo 할당, 진행 상황 추적, 회의록 양방향 연결
- 실시간 협업: WebSocket 기반 실시간 동기화, 버전 관리, 충돌 해결
- **이점**:
@ -965,5 +975,6 @@ UFR-TODO-030: [Todo완료처리] Todo 담당자로서 | 나는, 완료된 Todo
|------|--------|--------|----------|
| 1.0 | 2025-01-20 | 도그냥 (서비스 기획자) | 초안 작성 (8개 마이크로서비스) |
| 2.0 | 2025-01-22 | 길동 (아키텍트) | 논리 아키텍처 반영 (5개 마이크로서비스로 단순화) |
| 2.0.1 | 2025-10-23 | 강지수 (Product Designer) | 공유 기능 제거 반영 <br>- AFR-USER-020: 대시보드 "공유받은 회의록" 섹션 제거<br>- UFR-MEET-046: 회의록 목록 카테고리 필터 "공유받은 회의" 제거 |
---