Merge branch 'main' into feature/stt-ai - 로그 파일 충돌 해결

This commit is contained in:
Minseo-Jo
2025-10-28 09:33:19 +09:00
295 changed files with 26295 additions and 26368 deletions
+41 -187
View File
@@ -150,6 +150,7 @@
}
/* 회의 카드 */
/* 최근 회의는 최대 3개만 표시하므로 3열로 제한 */
.meeting-grid {
display: grid;
gap: var(--space-md);
@@ -168,12 +169,6 @@
}
}
@media (min-width: 1440px) {
.meeting-grid {
grid-template-columns: repeat(4, 1fr);
}
}
.meeting-card {
background: var(--white);
border-radius: var(--radius-lg);
@@ -238,7 +233,7 @@
/* Todo 카드 스타일은 common.css에서 공통 관리 */
/* 통계 영역 - 정보 표시용 (클릭 불가) */
/* 통계 영역 - 정보 표시용 (클릭 불가) - UFR-USER-020: 2개 항목 표시 */
.stats-overview {
display: grid;
grid-template-columns: repeat(2, 1fr);
@@ -513,15 +508,12 @@
<div class="sidebar-logo-text">회의록 서비스</div>
</a>
<!-- MVP 스코프 축소: Todo 관리 메뉴 제거 -->
<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) -->
@@ -559,7 +551,7 @@
<!-- 메인 콘텐츠 -->
<main class="main-content">
<!-- 통계 개요 -->
<!-- 통계 개요 (UFR-USER-020) -->
<div class="stats-overview">
<div class="stat-box stat-meeting">
<div class="stat-icon">📅</div>
@@ -567,9 +559,9 @@
<div class="stat-text">예정된 회의</div>
</div>
<div class="stat-box stat-todo">
<div class="stat-icon"></div>
<div class="stat-number" id="stat-todos">0</div>
<div class="stat-text">나의 Todo</div>
<div class="stat-icon">📝</div>
<div class="stat-number" id="stat-drafts">0</div>
<div class="stat-text">작성중 회의록</div>
</div>
</div>
@@ -583,16 +575,7 @@
</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>
<!-- MVP 스코프 축소: "나의 Todo" 섹션 제거 -->
<!-- 나의 회의록 -->
<section>
@@ -607,7 +590,7 @@
</main>
<!-- 하단 네비게이션 (모바일) -->
<!-- 하단 네비게이션 (모바일) - MVP 스코프 축소: Todo 관리 메뉴 제거 -->
<nav class="bottom-nav">
<a href="02-대시보드.html" class="nav-item active">
<img src="img/home.png" alt="홈" style="width: 45px;">
@@ -615,9 +598,6 @@
<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 오버레이 -->
@@ -704,8 +684,17 @@
const statusInfo = getMeetingStatusInfo(meeting);
const isCreator = meeting.participants.some(p => p.id === currentUser.id && p.role === 'creator');
// 버튼 표시 규칙
// - ongoing: 참여하기 버튼 표시
// - scheduled: 버튼 없음 (카드 클릭으로 수정 화면 이동)
// - draft/complete: 버튼 없음 (카드 클릭으로 상세조회 이동)
let actionButton = '';
if (meeting.status === 'ongoing') {
actionButton = `<button class="btn btn-primary btn-sm" onclick="navigateTo('05-회의진행.html'); event.stopPropagation();">참여하기</button>`;
}
return `
<div class="meeting-card ${meeting.status === 'ongoing' ? 'ongoing' : ''}" data-id="${meeting.id}">
<div class="meeting-card ${meeting.status === 'ongoing' ? 'ongoing' : ''}" data-id="${meeting.id}" data-status="${meeting.status}">
<div class="meeting-card-header">
${createBadge(statusInfo.badgeText, statusInfo.badgeType)}
<h3 class="meeting-card-title">${meeting.title}${isCreator ? ' <span style="font-size: 16px;" title="생성자">👑</span>' : ''}</h3>
@@ -714,14 +703,7 @@
<div class="meeting-card-meta-item">📅 ${formatDate(meeting.date)} ${formatTime(meeting.time)} 👥 ${meeting.participants.length}명</div>
<div class="meeting-card-meta-item">📍 ${meeting.location}</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>
${actionButton ? `<div class="meeting-card-actions">${actionButton}</div>` : ''}
</div>
`;
}).join('');
@@ -731,12 +713,14 @@
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') {
const meetingStatus = card.dataset.status;
// 상태에 따른 이동 처리
if (meetingStatus === 'ongoing') {
navigateTo('05-회의진행.html');
} else if (meeting.status === 'completed') {
} else if (meetingStatus === 'draft' || meetingStatus === 'complete' || meetingStatus === 'completed') {
navigateTo('10-회의록상세조회.html');
} else {
} else if (meetingStatus === 'scheduled') {
navigateTo('03-회의예약.html');
}
}
@@ -745,104 +729,9 @@
}
/**
* 내 Todo 렌더링 (09-Todo관리.html과 동일한 정렬 기준)
* MVP 스코프 축소: renderMyTodos() 함수 제거됨
* 대시보드에서 Todo 위젯이 제거되어 더 이상 사용되지 않음
*/
function renderMyTodos() {
const container = $('#my-todos');
const myTodos = SAMPLE_TODOS
.filter(todo => todo.assignee.id === currentUser.id)
.sort((a, b) => {
// 09-Todo관리.html과 동일한 정렬: 완료되지 않은 것 우선, 마감일 순
if (a.status === 'completed' && b.status !== 'completed') return 1;
if (a.status !== 'completed' && b.status === 'completed') return -1;
return new Date(a.dueDate) - new Date(b.dueDate);
})
.slice(0, 3); // 상위 3개만 표시
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 dday = calculateDday(todo.dueDate);
const isCompleted = todo.status === 'completed';
const isOverdue = dday < 0 && !isCompleted;
// D-day 배지
let ddayBadge = '';
let ddayClass = '';
if (isCompleted) {
ddayBadge = '완료';
ddayClass = 'badge-complete';
} else if (isOverdue) {
ddayBadge = `D+${Math.abs(dday)} (지연)`;
ddayClass = 'badge-overdue';
} else if (dday === 0) {
ddayBadge = 'D-DAY';
ddayClass = 'badge-warning';
} else if (dday <= 3) {
ddayBadge = `D-${dday}`;
ddayClass = 'badge-warning';
} else if (dday <= 7) {
ddayBadge = `D-${dday}`;
ddayClass = 'badge-primary';
} else {
ddayBadge = `D-${dday}`;
ddayClass = 'badge-secondary';
}
// 우선순위 배지
const priorityText = todo.priority === 'high' ? '높음' : todo.priority === 'medium' ? '보통' : '낮음';
const priorityClass = `badge-${todo.priority}`;
return `
<div class="todo-card ${isCompleted ? 'completed' : ''}" data-todo-id="${todo.id}" data-meeting-id="${todo.meetingId}">
<div class="todo-top">
<div class="todo-checkbox-wrapper">
<input type="checkbox" class="todo-checkbox" id="check-${todo.id}"
${isCompleted ? 'checked' : ''}
onchange="toggleTodoComplete('${todo.id}', this.checked)">
</div>
<div class="todo-content-wrapper">
<div class="todo-badges">
<span class="badge ${ddayClass}">${ddayBadge}</span>
<span class="badge ${priorityClass}">${priorityText}</span>
</div>
<div class="todo-title">${todo.title}</div>
<div class="todo-meta-row">
<a class="todo-meeting-link" onclick="navigateTo('10-회의록상세조회.html'); event.stopPropagation();">
🔗 ${todo.meetingTitle}
</a>
<span>${formatDate(todo.dueDate)}</span>
</div>
</div>
${!isCompleted ? `
<div class="todo-actions">
<button class="icon-btn" onclick="editTodo('${todo.id}')" title="편집">✏️</button>
</div>
` : ''}
</div>
</div>
`;
}).join('');
// Todo 카드 클릭 시 해당 회의록 상세로 이동 (체크박스와 버튼 제외)
$$('.todo-card').forEach(card => {
card.addEventListener('click', (e) => {
// 체크박스나 버튼 클릭은 무시
if (e.target.classList.contains('todo-checkbox') ||
e.target.classList.contains('icon-btn') ||
e.target.closest('.icon-btn')) {
return;
}
const meetingId = card.dataset.meetingId;
const todoId = card.dataset.todoId;
navigateTo(`10-회의록상세조회.html?meetingId=${meetingId}&todoId=${todoId}`);
});
});
}
/**
* 나의 회의록 렌더링 (참여자 또는 생성자로 등록된 회의록, 최신순 정렬)
@@ -887,31 +776,33 @@
}
/**
* 통계 업데이트
* 통계 업데이트 (UFR-USER-020)
*/
function updateStats() {
// 예정된 회의 개수 (예정 + 진행중)
const scheduled = SAMPLE_MEETINGS.filter(m => m.status === 'scheduled' || m.status === 'ongoing').length;
// 나의 Todo 개수 (전체)
const myTodos = SAMPLE_TODOS.filter(t => t.assignee.id === currentUser.id).length;
// 작성중 회의록 개수 (내가 참석한 회의 중 '작성중' 상태)
const drafts = SAMPLE_MINUTES.filter(m =>
m.status === 'draft' &&
m.participants.some(p => p.id === currentUser.id)
).length;
$('#stat-scheduled').textContent = scheduled;
$('#stat-todos').textContent = myTodos;
$('#stat-drafts').textContent = drafts;
}
/**
* 초기화
* 초기화 - MVP 스코프 축소: renderMyTodos() 제거
*/
function init() {
renderSidebarUser();
renderHeader();
updateStats();
renderRecentMeetings();
renderMyTodos();
renderMyMinutes();
console.log('대시보드 초기화 완료');
console.log('대시보드 초기화 완료 (MVP 스코프 축소)');
}
/**
@@ -1002,48 +893,11 @@
}
/**
* Todo 완료 토글
* @param {string} todoId - Todo ID
* @param {boolean} isChecked - 체크박스 상태
* MVP 스코프 축소: Todo 관련 함수 제거됨
* - toggleTodoComplete()
* - editTodo()
* 대시보드에서 Todo 위젯이 제거되어 더 이상 사용되지 않음
*/
function toggleTodoComplete(todoId, isChecked) {
if (isChecked) {
// 완료 처리
if (confirm('완료 처리하시겠습니까?')) {
const todo = SAMPLE_TODOS.find(t => t.id === todoId);
if (todo) {
todo.status = 'completed';
showToast('Todo가 완료되었습니다', 'success');
updateStats();
renderMyTodos();
}
} else {
event.target.checked = false;
}
} else {
// 미완료로 되돌리기
if (confirm('미완료로 변경하시겠습니까?')) {
const todo = SAMPLE_TODOS.find(t => t.id === todoId);
if (todo) {
todo.status = 'incomplete';
showToast('미완료로 변경되었습니다', 'info');
updateStats();
renderMyTodos();
}
} else {
event.target.checked = true;
}
}
}
/**
* Todo 편집 (간이 버전 - 09-Todo관리.html로 이동)
* @param {string} todoId - Todo ID
*/
function editTodo(todoId) {
// Todo 관리 화면으로 이동하여 편집
navigateTo(`09-Todo관리.html?todoId=${todoId}`);
}
init();
</script>
+50 -36
View File
@@ -60,7 +60,7 @@
}
.meeting-title {
font-size: var(--font-large);
font-size: var(--font-h2);
font-weight: var(--font-weight-bold);
color: var(--gray-800);
margin: 0;
@@ -100,7 +100,7 @@
}
.recording-time {
font-size: var(--font-small);
font-size: var(--font-body);
font-weight: var(--font-weight-medium);
}
@@ -173,9 +173,9 @@
.info-row {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--space-sm);
padding: var(--space-xs) 0;
align-items: flex-start;
gap: 0;
padding: var(--space-sm) 0;
border-bottom: 1px solid var(--gray-300);
}
@@ -186,14 +186,28 @@
.info-label {
font-weight: var(--font-weight-medium);
color: var(--gray-600);
font-size: var(--font-caption);
min-width: 60px;
font-size: var(--font-small);
width: 70px;
min-width: 70px;
max-width: 70px;
flex-shrink: 0;
padding-right: var(--space-sm);
border-right: 1px solid var(--gray-300);
margin-right: var(--space-sm);
}
/* 데스크톱에서 라벨 폭 확장 */
@media (min-width: 768px) {
.info-label {
width: 100px;
min-width: 100px;
max-width: 100px;
}
}
.info-value {
color: var(--gray-800);
font-size: var(--font-small);
font-size: var(--font-body);
line-height: 1.5;
flex: 1;
}
@@ -229,7 +243,7 @@
background: transparent;
border: none;
cursor: pointer;
font-size: var(--font-small);
font-size: var(--font-body);
font-weight: var(--font-weight-medium);
color: var(--gray-600);
transition: all var(--transition-fast);
@@ -289,13 +303,13 @@
}
.participant-section-title {
font-size: var(--font-small);
font-size: var(--font-body);
font-weight: var(--font-weight-bold);
margin: 0;
}
.participant-count {
font-size: var(--font-caption);
font-size: var(--font-small);
color: var(--gray-600);
}
@@ -307,13 +321,13 @@
.invite-input {
flex: 1;
font-size: var(--font-small);
font-size: var(--font-body);
padding: var(--space-sm);
}
.invite-btn {
padding: var(--space-sm) var(--space-md);
font-size: var(--font-small);
font-size: var(--font-body);
white-space: nowrap;
flex-shrink: 0;
}
@@ -337,13 +351,13 @@
}
.participant-name {
font-size: var(--font-small);
font-size: var(--font-body);
font-weight: var(--font-weight-medium);
color: var(--gray-800);
}
.participant-email {
font-size: var(--font-caption);
font-size: var(--font-small);
color: var(--gray-500);
margin-top: 2px;
overflow: hidden;
@@ -351,7 +365,7 @@
white-space: nowrap;
}
/* AI 제안 탭 */
/* AI 기반 메모 탭 - MVP 스코프 축소 v1.5.1 */
.memo-input-section {
background: var(--gray-50);
border-radius: var(--radius-md);
@@ -360,7 +374,7 @@
}
.memo-input-label {
font-size: var(--font-small);
font-size: var(--font-body);
font-weight: var(--font-weight-medium);
color: var(--gray-700);
margin-bottom: var(--space-xs);
@@ -373,7 +387,7 @@
padding: var(--space-sm);
border: 1px solid var(--gray-300);
border-radius: var(--radius-md);
font-size: var(--font-small);
font-size: var(--font-body);
font-family: inherit;
resize: vertical;
line-height: 1.5;
@@ -388,17 +402,17 @@
width: 100%;
margin-top: var(--space-xs);
padding: var(--space-sm);
font-size: var(--font-small);
font-size: var(--font-body);
}
.ai-suggestion-list-title {
font-size: var(--font-body);
font-size: var(--font-h3);
font-weight: var(--font-weight-bold);
color: var(--gray-900);
margin-bottom: var(--space-sm);
}
/* AI 제안 카드 */
/* AI 주요 내용 카드 */
.ai-suggestion-card {
background: #FAFAFA;
border: 1px dashed #D0D0D0;
@@ -416,7 +430,7 @@
}
.ai-suggestion-time {
font-size: var(--font-caption);
font-size: var(--font-small);
color: var(--gray-500);
font-weight: var(--font-weight-regular);
}
@@ -446,7 +460,7 @@
}
.ai-suggestion-text {
font-size: var(--font-small);
font-size: var(--font-body);
color: var(--gray-700);
line-height: 1.5;
}
@@ -460,13 +474,13 @@
.term-search-input {
flex: 1;
font-size: var(--font-small);
font-size: var(--font-body);
padding: var(--space-sm);
}
.term-search-btn {
padding: var(--space-sm) var(--space-md);
font-size: var(--font-small);
font-size: var(--font-body);
white-space: nowrap;
flex-shrink: 0;
}
@@ -493,7 +507,7 @@
}
.term-name {
font-size: var(--font-small);
font-size: var(--font-body);
font-weight: var(--font-weight-bold);
color: var(--primary);
display: flex;
@@ -512,14 +526,14 @@
}
.term-definition {
font-size: var(--font-small);
font-size: var(--font-body);
color: var(--gray-700);
line-height: 1.5;
margin-bottom: var(--space-xs);
}
.term-context {
font-size: 11px;
font-size: var(--font-small);
color: var(--gray-500);
padding-top: var(--space-xs);
border-top: 1px dashed #D0D0D0;
@@ -546,7 +560,7 @@
}
.related-doc-title {
font-size: var(--font-small);
font-size: var(--font-body);
font-weight: var(--font-weight-bold);
color: var(--primary);
margin-bottom: var(--space-xs);
@@ -555,7 +569,7 @@
.related-doc-meta {
display: flex;
gap: var(--space-xs);
font-size: var(--font-caption);
font-size: var(--font-small);
color: var(--gray-600);
margin-bottom: var(--space-xs);
}
@@ -566,7 +580,7 @@
}
.related-doc-text {
font-size: var(--font-small);
font-size: var(--font-body);
color: var(--gray-700);
line-height: 1.5;
}
@@ -668,7 +682,7 @@
참석자
</button>
<button class="tab-button" onclick="switchTab('ai-suggestions')">
AI 제안
AI 메모
</button>
<button class="tab-button" onclick="switchTab('terms')">
용어사전
@@ -732,7 +746,7 @@
</div>
</div>
<!-- AI 제안 탭 -->
<!-- AI 기반 메모 탭 (MVP 스코프 축소 v1.5.1) -->
<div class="tab-content" id="tab-ai-suggestions">
<div class="memo-input-section">
<label class="memo-input-label">📝 회의 메모</label>
@@ -1053,7 +1067,7 @@
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// AI 제안을 메모에 추가
// AI가 감지한 주요 내용을 메모에 추가 (MVP 스코프 축소 v1.5.1)
function addToMemo(suggestionText, cardElement) {
const memoTextarea = document.getElementById('meetingMemo');
const currentMemo = memoTextarea.value;
@@ -1062,7 +1076,7 @@
const recordingTime = document.getElementById('recordingTime').textContent;
const timePrefix = '[' + recordingTime.substring(0, 5) + '] '; // HH:MM만 추출
// 시간 정보 + 제안 내용
// 시간 정보 + 주요 내용
const memoWithTime = timePrefix + suggestionText;
if (currentMemo) {
@@ -1071,7 +1085,7 @@
memoTextarea.value = memoWithTime;
}
// AI 제안 카드 삭제
// AI 주요 내용 카드 삭제 (선택 후 제거)
if (cardElement) {
cardElement.remove();
}
File diff suppressed because it is too large Load Diff
@@ -107,11 +107,41 @@
margin-bottom: var(--space-md);
}
/* 회의 제목 컨테이너 */
.meeting-title-container {
display: flex;
flex-direction: column;
gap: var(--space-sm);
margin-bottom: var(--space-md);
}
/* 배지 영역 (배지 + 크라운) */
.meeting-badges {
display: flex;
align-items: center;
gap: var(--space-xs);
}
/* 회의 제목 */
.meeting-basic-info h2 {
font-size: var(--font-h2);
font-weight: var(--font-weight-bold);
color: var(--gray-900);
margin-bottom: var(--space-sm);
margin: 0;
line-height: 1.3;
}
/* 데스크톱: 기존 가로 배치 유지 */
@media (min-width: 768px) {
.meeting-title-container {
flex-direction: row;
align-items: center;
gap: var(--space-sm);
}
.meeting-basic-info h2 {
margin: 0;
}
}
.info-row {
@@ -165,23 +195,6 @@
.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);
}
}
/* 회의록 섹션 */
@@ -279,22 +292,6 @@
margin-bottom: var(--space-md);
}
.reference-item {
background: var(--white);
border-radius: var(--radius-md);
padding: var(--space-sm);
margin-bottom: var(--space-sm);
cursor: pointer;
transition: all var(--transition-fast);
}
.reference-item:hover {
box-shadow: var(--shadow-sm);
}
.reference-item:last-child {
margin-bottom: 0;
}
.reference-header {
display: flex;
@@ -310,7 +307,7 @@
.reference-title {
flex: 1;
font-size: var(--font-small);
font-size: var(--font-body);
font-weight: var(--font-weight-medium);
color: var(--gray-900);
}
@@ -338,7 +335,7 @@
}
.reference-meta {
font-size: var(--font-caption);
font-size: var(--font-small);
color: var(--gray-500);
margin-bottom: var(--space-xs);
}
@@ -405,10 +402,11 @@
color: var(--white);
}
/* 통계 그리드 - 모바일 기본 (2x2) */
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-md);
grid-template-columns: repeat(2, 1fr);
gap: var(--space-sm);
margin-top: var(--space-md);
}
@@ -431,6 +429,14 @@
color: var(--gray-500);
}
/* 데스크톱: 1x4 그리드 */
@media (min-width: 768px) {
.stats-grid {
grid-template-columns: repeat(4, 1fr);
gap: var(--space-md);
}
}
/* 결정사항 카드 */
.decision-card {
background: var(--white);
@@ -462,7 +468,7 @@
border-top: 1px solid var(--gray-300);
}
/* Todo 진행상황 */
/* Todo 리스트 */
.todo-filters {
display: flex;
gap: var(--space-sm);
@@ -523,7 +529,64 @@
margin-bottom: var(--space-xs);
}
/* Todo 진행상황 - 09-Todo관리 스타일 적용 */
/* Todo 리스트 - 단순 조회 스타일 */
.simple-todo-list {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.simple-todo-item {
position: relative;
padding: var(--space-md);
background: var(--white);
border-radius: var(--radius-md);
border: 1px solid var(--gray-300);
}
.simple-todo-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: var(--space-sm);
margin-bottom: var(--space-xs);
}
.simple-todo-title {
flex: 1;
font-size: var(--font-body);
font-weight: var(--font-weight-medium);
color: var(--gray-900);
min-width: 0;
}
.simple-todo-edit-btn {
background: transparent;
border: none;
font-size: 20px;
cursor: pointer;
padding: 4px;
color: var(--gray-500);
transition: all var(--transition-fast);
flex-shrink: 0;
border-radius: 4px;
}
.simple-todo-edit-btn:hover {
color: var(--primary);
background: var(--primary-light);
transform: scale(1.1);
}
.simple-todo-meta {
font-size: var(--font-small);
color: var(--gray-600);
display: flex;
gap: var(--space-md);
align-items: center;
}
/* Todo 리스트 - 09-Todo관리 스타일 적용 */
.todo-filters {
display: flex;
gap: var(--space-sm);
@@ -688,6 +751,24 @@
display: block;
}
/* 대시보드 탭의 관련회의록 카드 스타일 강화 */
.card .reference-item {
border: 1px solid var(--gray-200) !important;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important;
margin-bottom: var(--space-sm) !important;
}
.card .reference-item:hover {
border-color: var(--primary) !important;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12) !important;
transform: translateY(-1px) !important;
}
.card .reference-item:active {
transform: translateY(0) !important;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08) !important;
}
/* 모바일 화면에서 관련회의록 왼쪽 정렬 */
@media (max-width: 600px) {
.reference-item {
@@ -738,10 +819,14 @@
<!-- 기본 정보 카드 -->
<div class="info-card">
<div class="meeting-basic-info">
<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 class="meeting-title-container">
<!-- 배지 + 크라운 영역 -->
<div class="meeting-badges" id="meeting-badges">
<span class="badge badge-complete">확정완료</span>
<!-- 생성자일 경우 👑 아이콘이 JavaScript로 추가됨 -->
</div>
<!-- 회의 제목 -->
<h2>2025년 1분기 제품 기획 회의</h2>
</div>
<div class="info-row">
<span class="info-icon">📅</span>
@@ -757,7 +842,7 @@
<div class="participant">
<div class="avatar avatar-green"></div>
<span class="participant-name">김민준</span>
<span class="role-badge">성자</span>
<span class="role-badge">성자</span>
</div>
<div class="participant">
<div class="avatar avatar-blue"></div>
@@ -1042,148 +1127,70 @@
</div>
</div>
<!-- Todo 진행상황 -->
<!-- Todo 단순 조회 (MVP 스코프 축소 v1.5.1) -->
<div class="card mb-lg">
<h3 class="card-title">📋 Todo 진행상황</h3>
<!-- 전체 진행률 -->
<div style="margin-bottom: var(--space-lg);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--space-xs);">
<span style="font-size: var(--font-body); font-weight: var(--font-weight-medium); color: var(--gray-900);">전체 진행률</span>
<span style="font-size: var(--font-body); font-weight: var(--font-weight-bold); color: var(--primary);">40%</span>
</div>
<div style="width: 100%; height: 8px; background: var(--gray-200); border: 1px solid var(--gray-300); border-radius: 4px; overflow: hidden;">
<div style="width: 40%; height: 100%; background: var(--primary); transition: width 0.3s ease;"></div>
</div>
<div style="font-size: var(--font-caption); color: var(--gray-600); margin-top: var(--space-xs);">
2 / 5 완료
</div>
<div class="section-header">
<h3 class="card-title">📋 Todo 리스트</h3>
<button class="btn btn-primary btn-sm" onclick="openModal('addTodoModal')">추가</button>
</div>
<p style="font-size: var(--font-small); color: var(--gray-600); margin-bottom: var(--space-md);">
Todo 항목은 조회만 가능합니다. 제목, 담당자, 마감일 정보만 표시됩니다.
</p>
<div class="todo-filters">
<button class="filter-btn active" data-filter="all" onclick="filterTodos('all')">
전체 (<span id="filterAllCount">5</span>)
</button>
<button class="filter-btn" data-filter="overdue" onclick="filterTodos('overdue')">
지연 (<span id="filterOverdueCount">1</span>)
</button>
<button class="filter-btn" data-filter="urgent" onclick="filterTodos('urgent')">
마감 임박 (<span id="filterUrgentCount">2</span>)
</button>
<button class="filter-btn" data-filter="completed" onclick="filterTodos('completed')">
완료 (<span id="filterCompletedCount">2</span>)
</button>
</div>
<!-- Todo 카드 리스트 -->
<div class="todo-card">
<div class="todo-top">
<div class="todo-checkbox-wrapper">
<input type="checkbox" class="todo-checkbox" id="check-todo-002"
onchange="toggleTodoComplete('todo-002', this.checked)">
<!-- Todo 단순 조회 리스트 (제목 + 담당자 + 마감일 + 수정 버튼) -->
<div class="simple-todo-list">
<div class="simple-todo-item">
<div class="simple-todo-header">
<div class="simple-todo-title">데이터베이스 스키마 설계</div>
<button class="simple-todo-edit-btn creator-only" onclick="editTodo(1)" title="수정">✏️</button>
</div>
<div class="todo-content-wrapper">
<div class="todo-badges">
<span class="badge badge-overdue">D+1 (지연)</span>
<span class="badge badge-high">높음</span>
</div>
<div class="todo-title">데이터베이스 스키마 설계</div>
<div class="todo-meta-row">
<span>담당자: 이준호</span>
<span>2025-10-20 마감</span>
</div>
</div>
<div class="todo-actions">
<button class="icon-btn" onclick="editTodo('todo-002')" title="편집">✏️</button>
<div class="simple-todo-meta">
<span>👤 이준호</span>
<span>📅 2025-10-20</span>
</div>
</div>
</div>
<div class="todo-card">
<div class="todo-top">
<div class="todo-checkbox-wrapper">
<input type="checkbox" class="todo-checkbox" id="check-todo-001"
onchange="toggleTodoComplete('todo-001', this.checked)">
<div class="simple-todo-item">
<div class="simple-todo-header">
<div class="simple-todo-title">API 명세서 작성</div>
<button class="simple-todo-edit-btn creator-only" onclick="editTodo(2)" title="수정">✏️</button>
</div>
<div class="todo-content-wrapper">
<div class="todo-badges">
<span class="badge badge-warning">D-2</span>
<span class="badge badge-high">높음</span>
</div>
<div class="todo-title">API 명세서 작성</div>
<div class="todo-meta-row">
<span>담당자: 이준호</span>
<span>2025-10-23 마감</span>
</div>
</div>
<div class="todo-actions">
<button class="icon-btn" onclick="editTodo('todo-001')" title="편집">✏️</button>
<div class="simple-todo-meta">
<span>👤 이준호</span>
<span>📅 2025-10-23</span>
</div>
</div>
</div>
<div class="todo-card">
<div class="todo-top">
<div class="todo-checkbox-wrapper">
<input type="checkbox" class="todo-checkbox" id="check-todo-005"
onchange="toggleTodoComplete('todo-005', this.checked)">
<div class="simple-todo-item">
<div class="simple-todo-header">
<div class="simple-todo-title">예산 편성안 검토</div>
<button class="simple-todo-edit-btn creator-only" onclick="editTodo(3)" title="수정">✏️</button>
</div>
<div class="todo-content-wrapper">
<div class="todo-badges">
<span class="badge badge-warning">D-1</span>
<span class="badge badge-high">높음</span>
</div>
<div class="todo-title">예산 편성안 검토</div>
<div class="todo-meta-row">
<span>담당자: 김민준</span>
<span>2025-10-22 마감</span>
</div>
</div>
<div class="todo-actions">
<button class="icon-btn" onclick="editTodo('todo-005')" title="편집">✏️</button>
<div class="simple-todo-meta">
<span>👤 김민준</span>
<span>📅 2025-10-22</span>
</div>
</div>
</div>
<div class="todo-card">
<div class="todo-top">
<div class="todo-checkbox-wrapper">
<input type="checkbox" class="todo-checkbox" id="check-todo-003"
onchange="toggleTodoComplete('todo-003', this.checked)">
<div class="simple-todo-item">
<div class="simple-todo-header">
<div class="simple-todo-title">UI 프로토타입 디자인</div>
<button class="simple-todo-edit-btn creator-only" onclick="editTodo(4)" title="수정">✏️</button>
</div>
<div class="todo-content-wrapper">
<div class="todo-badges">
<span class="badge badge-primary">D-7</span>
<span class="badge badge-medium">보통</span>
</div>
<div class="todo-title">UI 프로토타입 디자인</div>
<div class="todo-meta-row">
<span>담당자: 최유진</span>
<span>2025-10-28 마감</span>
</div>
</div>
<div class="todo-actions">
<button class="icon-btn" onclick="editTodo('todo-003')" title="편집">✏️</button>
<div class="simple-todo-meta">
<span>👤 최유진</span>
<span>📅 2025-10-28</span>
</div>
</div>
</div>
<div class="todo-card completed">
<div class="todo-top">
<div class="todo-checkbox-wrapper">
<input type="checkbox" class="todo-checkbox" id="check-todo-004" checked
onchange="toggleTodoComplete('todo-004', this.checked)">
<div class="simple-todo-item">
<div class="simple-todo-header">
<div class="simple-todo-title">사용자 피드백 분석</div>
<button class="simple-todo-edit-btn creator-only" onclick="editTodo(5)" title="수정">✏️</button>
</div>
<div class="todo-content-wrapper">
<div class="todo-badges">
<span class="badge badge-complete">완료</span>
<span class="badge badge-medium">보통</span>
</div>
<div class="todo-title">사용자 피드백 분석</div>
<div class="todo-meta-row">
<span>담당자: 김민준</span>
<span>2025-10-19 마감</span>
</div>
<div class="simple-todo-meta">
<span>👤 김민준</span>
<span>📅 2025-10-19</span>
</div>
</div>
</div>
@@ -1199,7 +1206,7 @@
<span class="reference-title">AI 기능 개선 회의</span>
<span class="relevance-badge relevance-high">92%</span>
</div>
<div class="reference-meta">2025-10-23 15:00 · 이준호</div>
<div class="reference-meta">이준호 · 2025-10-23 15:00</div>
<div class="reference-summary">
AI 요약 정확도 개선 방안 논의. BERT 모델 도입 및 학습 데이터 확보 계획 수립.
</div>
@@ -1211,7 +1218,7 @@
<span class="reference-title">개발 리소스 계획 회의</span>
<span class="relevance-badge relevance-medium">88%</span>
</div>
<div class="reference-meta">2025-10-22 11:00 · 김민준</div>
<div class="reference-meta">김민준 · 2025-10-22 11:00</div>
<div class="reference-summary">
Q4 개발 리소스 현황 및 배분 계획. 신규 프로젝트 우선순위 협의.
</div>
@@ -1223,7 +1230,7 @@
<span class="reference-title">경쟁사 분석 회의</span>
<span class="relevance-badge relevance-medium">78%</span>
</div>
<div class="reference-meta">2025-10-20 10:00 · 박서연</div>
<div class="reference-meta">박서연 · 2025-10-20 10:00</div>
<div class="reference-summary">
경쟁사 A, B, C 분석 결과. 우리의 차별점은 실시간 협업 및 검증 기능.
</div>
@@ -1237,6 +1244,43 @@
<button class="btn btn-secondary" onclick="navigateTo('11-회의록수정.html')">수정</button>
</div>
<!-- Todo 추가 모달 -->
<div class="modal-overlay" id="addTodoModal">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">Todo 추가</h3>
<button class="modal-close" onclick="closeModal('addTodoModal')">×</button>
</div>
<div class="modal-body">
<div class="form-group">
<label class="form-label">Todo 내용 <span class="text-error">*</span></label>
<input type="text" id="addTodoTitle" class="form-control" placeholder="할 일을 입력하세요">
</div>
<div class="form-group">
<label class="form-label">담당자 <span class="text-error">*</span></label>
<select id="addTodoAssignee" class="form-control">
<option value="">담당자를 선택하세요</option>
<option value="김민준">김민준</option>
<option value="이준호">이준호</option>
<option value="박서연">박서연</option>
<option value="최유진">최유진</option>
</select>
</div>
<div class="form-group">
<label class="form-label">마감일 <span class="text-error">*</span></label>
<div class="date-input-wrapper">
<input type="date" id="addTodoDueDate" class="form-control">
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="closeModal('addTodoModal')">취소</button>
<button class="btn btn-primary" onclick="addTodo()">추가</button>
</div>
</div>
</div>
<!-- Todo 편집 모달 -->
<div class="modal-overlay" id="editTodoModal">
<div class="modal">
@@ -1245,6 +1289,7 @@
<button class="modal-close" onclick="closeModal('editTodoModal')">×</button>
</div>
<div class="modal-body">
<input type="hidden" id="editTodoId">
<div class="form-group">
<label class="form-label">Todo 제목 <span class="text-error">*</span></label>
<input type="text" id="editTodoTitle" class="form-control" placeholder="할 일을 입력하세요">
@@ -1258,30 +1303,12 @@
<option value="박서연">박서연</option>
<option value="최유진">최유진</option>
</select>
<p class="form-hint">👤 담당자 변경 시 이전/새 담당자에게 알림이 전송됩니다</p>
</div>
<div class="form-group">
<label class="form-label">마감일 <span class="text-error">*</span></label>
<div class="date-input-wrapper">
<input type="date" id="editTodoDueDate" class="form-control">
</div>
<p class="form-hint">📅 마감일 변경 시 캘린더가 자동 업데이트됩니다</p>
</div>
<div class="form-group">
<label class="form-label">우선순위 <span class="text-error">*</span></label>
<select id="editTodoPriority" class="form-control">
<option value="high">높음</option>
<option value="medium">보통</option>
<option value="low">낮음</option>
</select>
</div>
<!-- 권한 안내 (동적 메시지) -->
<div class="alert alert-info" id="editTodoPermissionInfo">
<span class="material-icons" style="font-size: 20px;">info</span>
<div>
<strong>권한 안내</strong>
<p style="margin: 4px 0 0 0; font-size: 14px;" id="editTodoPermissionText">회의 생성자로서 모든 항목을 수정할 수 있습니다.</p>
</div>
</div>
</div>
<div class="modal-footer">
@@ -1323,95 +1350,39 @@
z-index: 2;
}
/* Todo 편집 모달 - 모바일 전체화면 */
/* Todo 추가/편집 모달 - 바텀시트 스타일 */
#addTodoModal .modal,
#editTodoModal .modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100vh;
max-width: none;
max-height: none;
margin: 0;
border-radius: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
#editTodoModal .modal-header {
flex-shrink: 0;
padding: var(--space-lg) var(--space-md);
border-bottom: 1px solid var(--gray-200);
display: flex;
align-items: center;
justify-content: space-between;
}
#editTodoModal .modal-title {
font-size: var(--font-h3);
font-weight: var(--font-weight-bold);
margin: 0;
}
#editTodoModal .modal-close {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
background: none;
border: none;
cursor: pointer;
color: var(--gray-600);
border-radius: var(--radius-md);
transition: all var(--transition-fast);
}
#editTodoModal .modal-close:hover {
background: var(--gray-100);
color: var(--gray-900);
}
#editTodoModal .modal-body {
flex: 1;
max-height: 90vh;
overflow-y: auto;
padding: var(--space-lg) var(--space-md);
-webkit-overflow-scrolling: touch;
}
#addTodoModal .modal-footer,
#editTodoModal .modal-footer {
flex-shrink: 0;
padding: var(--space-md);
border-top: 1px solid var(--gray-200);
display: flex;
gap: var(--space-sm);
background: var(--white);
justify-content: flex-end;
margin-top: var(--space-md);
}
#addTodoModal .modal-footer .btn,
#editTodoModal .modal-footer .btn {
flex: 1;
}
/* 데스크톱에서는 중앙 모달로 복원 */
/* 데스크톱에서는 중앙 모달 */
@media (min-width: 768px) {
#addTodoModal .modal,
#editTodoModal .modal {
position: relative;
top: auto;
left: auto;
right: auto;
bottom: auto;
width: 90%;
max-width: 600px;
height: auto;
max-height: 90vh;
margin: 0 auto;
border-radius: var(--radius-lg);
}
}
/* 회의 생성자 전용 요소 숨김 */
.creator-only.hidden {
display: none !important;
}
</style>
<script src="common.js"></script>
@@ -1460,24 +1431,17 @@
}
/**
* Todo 필터링 (09-Todo관리와 동일)
* MVP 스코프 축소 v1.5.1: Todo 필터링/편집 함수 제거됨
* Todo는 단순 조회만 가능하므로 더 이상 사용되지 않음
*/
/*
function filterTodos(filter) {
currentFilter = filter;
// 탭 활성화
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.remove('active');
});
document.querySelector(`.filter-btn[data-filter="${filter}"]`).classList.add('active');
renderTodoList();
// 제거됨: Todo 필터링 기능
}
*/
/**
* Todo 편집 모달 열기
* @param {string} todoId - 편집할 Todo ID
*/
/*
function editTodo(todoId) {
const todo = meetingTodos.find(t => t.id === todoId);
if (!todo) return;
@@ -1610,39 +1574,13 @@
* @param {string} todoId - Todo ID
* @param {boolean} isChecked - 체크박스 상태
*/
/*
function toggleTodoComplete(todoId, isChecked) {
if (isChecked) {
// 완료 처리
if (confirm('완료 처리하시겠습니까?')) {
const todo = meetingTodos.find(t => t.id === todoId);
if (todo) {
todo.status = 'completed';
showToast('Todo가 완료되었습니다', 'success');
updateTodoProgress();
renderTodoList();
}
} else {
event.target.checked = false;
}
} else {
// 미완료로 되돌리기
if (confirm('미완료로 변경하시겠습니까?')) {
const todo = meetingTodos.find(t => t.id === todoId);
if (todo) {
todo.status = 'incomplete';
showToast('미완료로 변경되었습니다', 'info');
updateTodoProgress();
renderTodoList();
}
} else {
event.target.checked = true;
}
}
// 제거됨: Todo 완료 토글 기능
}
*/
/**
* Todo 진행률 업데이트
*/
/*
function updateTodoProgress() {
const total = meetingTodos.length;
const completed = meetingTodos.filter(t => t.status === 'completed').length;
@@ -1731,6 +1669,191 @@
if (filterCompletedCount) filterCompletedCount.textContent = completed;
}
/**
* 회의 생성자 여부 확인
* @param {string} meetingId - 회의 ID
* @param {string} userName - 사용자 이름
* @returns {boolean} 회의 생성자 여부
*/
function checkIfUserIsCreator(meetingId, userName) {
// 실제로는 서버 API를 호출하여 확인
// 프로토타입에서는 샘플 데이터로 시뮬레이션
const meetingCreators = {
'meeting-001': '김민준',
'meeting-002': '이서연',
'meeting-003': '박준호'
};
return meetingCreators[meetingId] === userName;
}
/**
* Todo 수정 함수 (index 기반)
* @param {number} index - Todo 인덱스 (1-based)
*/
function editTodo(index) {
// 1-based index를 0-based로 변환
const todo = meetingTodos[index - 1];
if (!todo) {
showToast('Todo를 찾을 수 없습니다', 'error');
return;
}
// 모달에 데이터 채우기
document.getElementById('editTodoId').value = index;
document.getElementById('editTodoTitle').value = todo.title;
// 담당자 처리 (assignee가 객체인 경우 name 속성 사용)
const assigneeName = typeof todo.assignee === 'object' ? todo.assignee.name : todo.assignee;
document.getElementById('editTodoAssignee').value = assigneeName;
document.getElementById('editTodoDueDate').value = todo.dueDate;
// 회의 생성자 여부 확인
const currentUser = '김민준'; // 현재 로그인 사용자
const isCreator = checkIfUserIsCreator(CURRENT_MEETING_ID, currentUser);
// 담당자 필드 표시 여부 결정 (회의 생성자만 표시)
const assigneeGroup = document.getElementById('editTodoAssigneeGroup');
if (isCreator) {
assigneeGroup.style.display = 'block';
} else {
assigneeGroup.style.display = 'none';
}
// 모달 열기
openModal('editTodoModal');
}
/**
* Todo 수정 저장 (index 기반)
*/
function saveTodoEdit() {
const index = parseInt(document.getElementById('editTodoId').value);
const title = document.getElementById('editTodoTitle').value.trim();
const assignee = document.getElementById('editTodoAssignee').value.trim();
const dueDate = document.getElementById('editTodoDueDate').value;
if (!title) {
showToast('제목을 입력해주세요', 'error');
return;
}
if (!assignee) {
showToast('담당자를 입력해주세요', 'error');
return;
}
if (!dueDate) {
showToast('마감일을 선택해주세요', 'error');
return;
}
// 1-based index를 0-based로 변환하여 Todo 업데이트
const todoIndex = index - 1;
if (todoIndex >= 0 && todoIndex < meetingTodos.length) {
const todo = meetingTodos[todoIndex];
const oldAssignee = typeof todo.assignee === 'object' ? todo.assignee.name : todo.assignee;
const oldDueDate = todo.dueDate;
// Todo 업데이트 (실제로는 API 호출)
todo.title = title;
// assignee가 객체인 경우 name 속성만 업데이트
if (typeof todo.assignee === 'object') {
todo.assignee.name = assignee;
} else {
todo.assignee = assignee;
}
todo.dueDate = dueDate;
showToast('Todo가 수정되었습니다', 'success');
// 담당자 변경 시 알림
if (oldAssignee !== assignee) {
setTimeout(() => {
showToast(`${oldAssignee}${assignee}에게 알림이 전송되었습니다`, 'info');
}, 1000);
}
// 마감일 변경 시 알림
if (oldDueDate !== dueDate) {
setTimeout(() => {
showToast('캘린더가 업데이트되었습니다', 'info');
}, oldAssignee !== assignee ? 2000 : 1000);
}
}
closeModal('editTodoModal');
// 페이지 새로고침 (실제로는 해당 Todo 항목만 업데이트)
setTimeout(() => {
location.reload();
}, 1500);
}
/**
* Todo 추가 함수
*/
function addTodo() {
const title = document.getElementById('addTodoTitle').value.trim();
const assignee = document.getElementById('addTodoAssignee').value.trim();
const dueDate = document.getElementById('addTodoDueDate').value;
// 유효성 검사
if (!title) {
showToast('Todo 내용을 입력해주세요', 'error');
return;
}
if (!assignee) {
showToast('담당자를 선택해주세요', 'error');
return;
}
if (!dueDate) {
showToast('마감일을 선택해주세요', 'error');
return;
}
// 새 Todo 추가 (실제로는 API 호출)
const newTodo = {
id: 'todo-' + Date.now(),
meetingId: CURRENT_MEETING_ID,
title: title,
assignee: assignee,
dueDate: dueDate,
status: 'pending'
};
meetingTodos.push(newTodo);
showToast('Todo가 추가되었습니다', 'success');
// 담당자에게 알림 전송
setTimeout(() => {
showToast(`${assignee}에게 알림이 전송되었습니다`, 'info');
}, 1000);
// 캘린더 업데이트 알림
setTimeout(() => {
showToast('캘린더가 업데이트되었습니다', 'info');
}, 2000);
closeModal('addTodoModal');
// 입력 필드 초기화
document.getElementById('addTodoTitle').value = '';
document.getElementById('addTodoAssignee').value = '';
document.getElementById('addTodoDueDate').value = '';
// 페이지 새로고침 (실제로는 Todo 리스트만 업데이트)
setTimeout(() => {
location.reload();
}, 2500);
}
/**
* 페이지 초기화
*/
@@ -1740,13 +1863,19 @@
const isCreator = checkIfUserIsCreator(CURRENT_MEETING_ID, currentUser);
if (isCreator) {
const titleContainer = document.getElementById('meeting-title-container');
const badge = titleContainer.querySelector('.badge');
const badgesContainer = document.getElementById('meeting-badges');
const crownIcon = document.createElement('span');
crownIcon.textContent = '👑';
crownIcon.style.fontSize = '24px';
// badge 다음에 👑 삽입
badge.insertAdjacentElement('afterend', crownIcon);
crownIcon.style.fontSize = '20px';
crownIcon.title = '회의 생성자';
// 배지 영역에 크라운 추가
badgesContainer.appendChild(crownIcon);
} else {
// 회의 생성자가 아닐 경우 creator-only 요소 숨김
const creatorOnlyElements = document.querySelectorAll('.creator-only');
creatorOnlyElements.forEach(element => {
element.classList.add('hidden');
});
}
updateTodoProgress();
+65 -70
View File
@@ -6,6 +6,17 @@
<title>회의록 수정 - 회의록 서비스</title>
<link rel="stylesheet" href="common.css">
<style>
/*
MVP 스코프 축소 v1.5.2 적용됨:
- ❌ 실시간 협업 표시 ("편집 중" 표시 제거)
- ❌ Todo 편집 기능 제거 (단순 조회만 가능)
- ❌ 검증률 표시 및 최종 확정 버튼 제거
- ✅ 안건별 검증 완료 체크박스 사용
- ✅ Last Write Wins (LWW) 정책 적용
- ✅ AI 요약 기능 통합: "AI 상세 요약" → "AI 요약"으로 명칭 변경
- ✅ AI 재생성 버튼: 텍스트 편집 영역 내용 기반으로 한줄 요약 재생성
*/
/* 페이지별 커스텀 스타일만 유지 */
/* 공통 스타일(헤더, 메인콘텐츠, 안건, AI요약, 관련회의록, 액션바)은 common.css 사용 */
@@ -101,32 +112,8 @@
gap: var(--space-xs);
}
/* AI 한줄 요약 (읽기 전용, UFR-AI-036) */
.ai-summary-oneline {
background: var(--gray-50);
border-left: 4px solid var(--primary);
border-radius: var(--radius-md);
padding: var(--space-sm) var(--space-md);
margin-bottom: var(--space-md);
display: flex;
align-items: center;
gap: var(--space-sm);
}
.ai-summary-oneline .lock-icon {
font-size: 18px;
color: var(--gray-500);
flex-shrink: 0;
}
.ai-summary-oneline .summary-text {
font-size: var(--font-small);
color: var(--gray-700);
font-weight: var(--font-weight-medium);
flex: 1;
min-width: 0;
}
/* AI 요약 편집 영역 (UFR-AI-036) */
/* 헤더, textarea, footer 스타일은 common.css 사용 */
/* AI 요약 편집 Footer (common.css에 없는 부분) */
.ai-summary-footer {
display: flex;
@@ -187,6 +174,14 @@
.reference-item {
position: relative;
padding-right: 40px; /* 삭제 버튼 공간 확보 */
cursor: default; /* 카드 전체는 클릭 불가 */
}
/* 수정 페이지에서는 카드 hover 효과 제거 (삭제 버튼만 클릭 가능) */
.reference-item:hover {
transform: none;
border-color: var(--gray-200);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.remove-btn {
@@ -245,6 +240,19 @@
gap: var(--space-sm);
}
/* 회의 생성자 전용 UI 표시 제어 */
.creator-only {
display: flex; /* 기본: 회의 생성자 환경 */
}
.agenda-verification:not([data-is-creator="true"]) .creator-only {
display: none; /* 참석자 환경에서는 숨김 */
}
.agenda-verification:not(.verified) .creator-only {
display: none; /* 검증 안 된 경우 버튼 숨김 */
}
/* 읽기 전용 표시 */
.readonly-badge {
display: inline-flex;
@@ -395,23 +403,17 @@
</div>
</div>
<!-- AI 한줄 요약 (읽기 전용, UFR-AI-036) -->
<div class="ai-summary-oneline">
<span class="lock-icon">🔒</span>
<span class="summary-text">타겟 고객을 20-30대로 설정, UI/UX 개선 집중</span>
</div>
<!-- AI 상세 요약 편집 -->
<!-- AI 요약 편집 (UFR-AI-036) -->
<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>
<span class="ai-summary-label">💡 AI 요약</span>
<button class="btn btn-primary btn-sm" onclick="regenerateSummary(1)">AI 재생성</button>
</div>
<textarea
class="ai-summary-textarea"
placeholder="AI 요약을 입력하거나 수정하세요"
readonly
>신제품은 AI 기반 회의록 자동화 서비스로 결정. 타겟은 중소기업 및 스타트업이며, 주요 기능은 음성인식, AI 요약, Todo 추출입니다. 경쟁사 대비 차별점은 실시간 검증 및 협업 기능입니다.</textarea>
>타겟 고객을 20-30대로 설정, UI/UX 개선 집중</textarea>
<div class="ai-summary-footer">
<span class="ai-summary-time">마지막 수정: 1시간 전</span>
</div>
@@ -466,16 +468,18 @@
</div>
</div>
<!-- 안건별 검증 (회의 생성자 권한) -->
<div class="agenda-verification verified" id="verify-agenda-1">
<!-- 안건별 검증 (UFR-COLLAB-030) -->
<!-- 참석자: 체크박스만 표시 (검증완료 시 읽기 전용) -->
<!-- 생성자: 검증완료 시 "잠금 해제" 버튼 표시 -->
<div class="agenda-verification verified" id="verify-agenda-1" data-is-creator="true">
<input type="checkbox" class="checkbox" id="verify-1" checked disabled>
<div class="verification-label">
<label for="verify-1">
<span class="font-medium">검증 완료</span>
<span class="text-caption text-muted"> (잠금됨 · 회의 생성자만 수정 가능)</span>
</label>
</div>
<div class="verification-actions">
<!-- 회의 생성자만 보이는 잠금 해제 버튼 (검증완료 시에만 표시) -->
<div class="verification-actions creator-only">
<button class="btn btn-secondary btn-sm" onclick="unlockAgendaVerification(1)">🔓 잠금 해제</button>
</div>
</div>
@@ -492,23 +496,17 @@
</div>
</div>
<!-- AI 한줄 요약 (읽기 전용, UFR-AI-036) -->
<div class="ai-summary-oneline">
<span class="lock-icon">🔒</span>
<span class="summary-text">개발 기간 3개월, 백엔드 2명/프론트 2명/AI 1명 투입</span>
</div>
<!-- AI 상세 요약 편집 -->
<!-- AI 요약 편집 (UFR-AI-036) -->
<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(2)">AI 재생성</button>
<span class="ai-summary-label">💡 AI 요약</span>
<button class="btn btn-primary btn-sm" onclick="regenerateSummary(2)">AI 재생성</button>
</div>
<textarea
class="ai-summary-textarea"
placeholder="AI 요약을 입력하거나 수정하세요"
readonly
>개발 기간 3개월로 설정. 백엔드 2명, 프론트 2명, AI 엔지니어 1명 투입. 주간 스프린트로 진행하며, 2주마다 베타 테스트 실시.</textarea>
>개발 기간 3개월, 백엔드 2명/프론트 2명/AI 1명 투입</textarea>
<div class="ai-summary-footer">
<span class="ai-summary-time">생성: 2025-10-25 16:32</span>
</div>
@@ -547,16 +545,18 @@
</div>
</div>
<!-- 안건별 검증 (회의 생성자 권한) -->
<div class="agenda-verification verified" id="verify-agenda-2">
<!-- 안건별 검증 (UFR-COLLAB-030) -->
<!-- 참석자: 체크박스만 표시 (검증완료 시 읽기 전용) -->
<!-- 생성자: 검증완료 시 "잠금 해제" 버튼 표시 -->
<div class="agenda-verification verified" id="verify-agenda-2" data-is-creator="true">
<input type="checkbox" class="checkbox" id="verify-2" checked disabled>
<div class="verification-label">
<label for="verify-2">
<span class="font-medium">검증 완료</span>
<span class="text-caption text-muted"> (잠금됨 · 회의 생성자만 수정 가능)</span>
</label>
</div>
<div class="verification-actions">
<!-- 회의 생성자만 보이는 잠금 해제 버튼 (검증완료 시에만 표시) -->
<div class="verification-actions creator-only">
<button class="btn btn-secondary btn-sm" onclick="unlockAgendaVerification(2)">🔓 잠금 해제</button>
</div>
</div>
@@ -573,23 +573,17 @@
</div>
</div>
<!-- AI 한줄 요약 (읽기 전용, UFR-AI-036) -->
<div class="ai-summary-oneline">
<span class="lock-icon">🔒</span>
<span class="summary-text">프리 런칭 캠페인, LinkedIn 및 스타트업 커뮤니티 집중</span>
</div>
<!-- AI 상세 요약 편집 -->
<!-- AI 요약 편집 (UFR-AI-036) -->
<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(3)">AI 재생성</button>
<span class="ai-summary-label">💡 AI 요약</span>
<button class="btn btn-primary btn-sm" onclick="regenerateSummary(3)">AI 재생성</button>
</div>
<textarea
class="ai-summary-textarea"
placeholder="AI 요약을 입력하거나 수정하세요"
readonly
>베타 출시 전 프리 런칭 캠페인 진행. 주요 채널은 LinkedIn 및 스타트업 커뮤니티. 초기 100팀 무료 제공 후 유료 전환 유도.</textarea>
>프리 런칭 캠페인, LinkedIn 및 스타트업 커뮤니티 집중</textarea>
<div class="ai-summary-footer">
<span class="ai-summary-time">생성: 2025-10-25 16:35</span>
</div>
@@ -624,15 +618,15 @@
</div>
<!-- 안건별 검증 (회의 생성자 권한) -->
<div class="agenda-verification verified" id="verify-agenda-3">
<div class="agenda-verification verified" id="verify-agenda-3" data-is-creator="true">
<input type="checkbox" class="checkbox" id="verify-3" checked disabled>
<div class="verification-label">
<label for="verify-3">
<span class="font-medium">검증 완료</span>
<span class="text-caption text-muted"> (잠금됨 · 회의 생성자만 수정 가능)</span>
</label>
</div>
<div class="verification-actions">
<!-- 회의 생성자만 보이는 잠금 해제 버튼 (검증완료 시에만 표시) -->
<div class="verification-actions creator-only">
<button class="btn btn-secondary btn-sm" onclick="unlockAgendaVerification(3)">🔓 잠금 해제</button>
</div>
</div>
@@ -743,12 +737,13 @@
}, 1000);
}
// AI 요약 재생성
// AI 한줄 요약 재생성 (텍스트 편집 영역 내용 기반, UFR-AI-036)
function regenerateSummary(agendaId) {
showToast('AI 요약을 생성 중입니다...', 'info');
showToast('AI 요약을 생성 중입니다...', 'info');
setTimeout(() => {
showToast('AI 요약이 생성되었습니다', 'success');
showToast('생성되었습니다', 'success');
// 재생성된 한줄 요약은 회의록 상세조회 화면의 대시보드 및 회의록 탭에 즉시 반영됨
markAsUnsaved();
}, 2000);
}
@@ -453,10 +453,6 @@
<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) -->
@@ -581,9 +577,6 @@
<a href="12-회의록목록조회.html" class="nav-item active">
<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>
<script src="common.js"></script>
+14 -5
View File
@@ -1429,11 +1429,12 @@ input[type="date"]::-webkit-calendar-picker-indicator {
.related-meeting-item {
background: var(--white);
border-radius: var(--radius-md);
padding: var(--space-sm);
padding: var(--space-md);
margin-bottom: var(--space-sm);
display: flex;
gap: var(--space-sm);
transition: background var(--transition-fast);
cursor: pointer;
transition: all var(--transition-fast);
border: 1px solid var(--gray-200);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.reference-item:last-child,
@@ -1443,7 +1444,15 @@ input[type="date"]::-webkit-calendar-picker-indicator {
.reference-item:hover,
.related-meeting-item:hover {
background: var(--gray-100);
border-color: var(--primary);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
transform: translateY(-1px);
}
.reference-item:active,
.related-meeting-item:active {
transform: translateY(0);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
.reference-content,
+9 -2
View File
@@ -31,7 +31,7 @@ const SAMPLE_MEETINGS = [
time: '14:00',
duration: 90,
location: '본사 2층 대회의실',
status: 'scheduled', // ongoing, scheduled, completed
status: 'draft', // ongoing, scheduled, completed, draft(작성중), complete(확정완료)
participants: [
{ id: 'user-001', name: '김민준', avatar: '김', avatarColor: 'green' },
{ id: 'user-002', name: '박서연', avatar: '박', avatarColor: 'blue' },
@@ -39,7 +39,8 @@ const SAMPLE_MEETINGS = [
{ id: 'user-004', name: '최유진', avatar: '최', avatarColor: 'pink' }
],
sections: 3,
todos: 5
todos: 5,
minutesId: 'minutes-001' // 회의록 ID 연결
},
{
id: 'meeting-002',
@@ -1083,6 +1084,12 @@ function getMeetingStatusInfo(meeting) {
if (meeting.status === 'ongoing') {
return { badgeType: 'ongoing', badgeText: '진행중' };
}
if (meeting.status === 'draft') {
return { badgeType: 'draft', badgeText: '작성중' };
}
if (meeting.status === 'complete') {
return { badgeType: 'complete', badgeText: '확정완료' };
}
if (meeting.status === 'completed') {
return { badgeType: 'complete', badgeText: '확정완료' };
}