mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 11:26:25 +00:00
회의록 상세조회 및 수정 화면 UI 개선
- Todo 진행상황 → Todo 리스트 명칭 통일 - Todo 리스트 카드 스타일 개선 (플랫 디자인) - 관련회의록 카드 클릭 피드백 추가 (hover/active) - 대시보드 섹션 폰트 사이즈 일관성 확보 (제목 16px, 메타 14px) - 관련회의록 카드 테두리 및 그림자 스타일 강화 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a66a3e4b31
commit
b15f0c7da0
@ -1,454 +0,0 @@
|
|||||||
# 유저스토리 v2.2.0 → v2.3.0 변경사항 보고서
|
|
||||||
|
|
||||||
**작성일**: 2025-10-25
|
|
||||||
**작성자**: 지수 (Product Designer), 민준 (Product Owner)
|
|
||||||
**문서 버전**: 1.0
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 개요
|
|
||||||
|
|
||||||
본 보고서는 AI기반 회의록 작성 및 이력 관리 개선 서비스의 유저스토리 문서가 v2.2.0에서 v2.3.0으로 업데이트되면서 변경된 내용과 그 의미를 분석합니다.
|
|
||||||
|
|
||||||
### 요약 통계
|
|
||||||
|
|
||||||
| 항목 | v2.2.0 | v2.3.0 | 변화 |
|
|
||||||
|------|--------|--------|------|
|
|
||||||
| **유저스토리 수** | 25개 | 27개 | +2개 (+8%) |
|
|
||||||
| **신규 추가** | - | 5개 | UFR-USER-010, UFR-USER-020, UFR-MEET-015, UFR-AI-030, UFR-NOTI-010 |
|
|
||||||
| **삭제/전환** | - | 2개 | AFR-USER-010, AFR-USER-020 → UFR로 전환 |
|
|
||||||
| **AFR 코드** | 2개 | 0개 | -2개 (100% 제거) |
|
|
||||||
| **UFR 코드** | 23개 | 27개 | +4개 (+17%) |
|
|
||||||
| **평균 상세도** | 20-30줄 | 60-100줄 | **약 3배 증가** |
|
|
||||||
| **프로토타입 연계** | 부분적 | 100% (10개 화면) | - |
|
|
||||||
| **표준 형식 적용** | 0% | 100% (27개) | - |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 한눈에 보는 변경사항
|
|
||||||
|
|
||||||
```
|
|
||||||
v2.2.0 (25개) v2.3.0 (27개)
|
|
||||||
┌─────────────────┐ ┌─────────────────┐
|
|
||||||
│ AFR-USER-010 │ ──────────────────>│ UFR-USER-010 ✨ │ (로그인 상세화)
|
|
||||||
│ AFR-USER-020 │ ──────────────────>│ UFR-USER-020 ✨ │ (대시보드 재설계)
|
|
||||||
├─────────────────┤ ├─────────────────┤
|
|
||||||
│ UFR-MEET-010 │ ──────────────────>│ UFR-MEET-010 ✨ │ (회의예약 개선)
|
|
||||||
│ │ │ UFR-MEET-015 🆕 │ (참석자 실시간 초대)
|
|
||||||
│ UFR-MEET-020 │ ──────────────────>│ UFR-MEET-020 ✨ │ (템플릿선택 상세화)
|
|
||||||
│ UFR-MEET-030 │ ──────────────────>│ UFR-MEET-030 ✨ │ (회의시작 4개 탭)
|
|
||||||
│ UFR-MEET-040 │ ──────────────────>│ UFR-MEET-040 ✨ │ (회의종료 3가지 액션)
|
|
||||||
│ UFR-MEET-050 │ ──────────────────>│ UFR-MEET-050 ✨ │ (최종확정 2가지 시나리오)
|
|
||||||
│ UFR-MEET-046 │ ──────────────────>│ UFR-MEET-046 ✨ │ (목록조회 샘플 30개)
|
|
||||||
│ UFR-MEET-047 │ ──────────────────>│ UFR-MEET-047 ✨ │ (상세조회 관련회의록)
|
|
||||||
│ UFR-MEET-055 │ ──────────────────>│ UFR-MEET-055 ✨ │ (회의록수정 3가지 시나리오)
|
|
||||||
├─────────────────┤ ├─────────────────┤
|
|
||||||
│ UFR-AI-010 │ ──────────────────>│ UFR-AI-010 │
|
|
||||||
│ UFR-AI-020 │ ──────────────────>│ UFR-AI-020 │
|
|
||||||
│ │ │ UFR-AI-030 🆕🎯 │ (실시간 AI 제안 - 차별화!)
|
|
||||||
│ UFR-AI-035 │ ──────────────────>│ UFR-AI-035 │
|
|
||||||
│ UFR-AI-036 │ ──────────────────>│ UFR-AI-036 │
|
|
||||||
│ UFR-AI-040 │ ──────────────────>│ UFR-AI-040 │
|
|
||||||
├─────────────────┤ ├─────────────────┤
|
|
||||||
│ UFR-STT-010 │ ──────────────────>│ UFR-STT-010 │
|
|
||||||
│ UFR-STT-020 │ ──────────────────>│ UFR-STT-020 │
|
|
||||||
├─────────────────┤ ├─────────────────┤
|
|
||||||
│ UFR-RAG-010 │ ──────────────────>│ UFR-RAG-010 │
|
|
||||||
│ UFR-RAG-020 │ ──────────────────>│ UFR-RAG-020 │
|
|
||||||
├─────────────────┤ ├─────────────────┤
|
|
||||||
│ UFR-COLLAB-010 │ ──────────────────>│ UFR-COLLAB-010 │
|
|
||||||
│ UFR-COLLAB-020 │ ──────────────────>│ UFR-COLLAB-020 │
|
|
||||||
│ UFR-COLLAB-030 │ ──────────────────>│ UFR-COLLAB-030 │
|
|
||||||
├─────────────────┤ ├─────────────────┤
|
|
||||||
│ UFR-TODO-010 │ ──────────────────>│ UFR-TODO-010 │
|
|
||||||
│ UFR-TODO-030 │ ──────────────────>│ UFR-TODO-030 │
|
|
||||||
│ UFR-TODO-040 │ ──────────────────>│ UFR-TODO-040 │
|
|
||||||
└─────────────────┘ ├─────────────────┤
|
|
||||||
│ UFR-NOTI-010 🆕 │ (알림발송 - 폴링 방식)
|
|
||||||
└─────────────────┘
|
|
||||||
|
|
||||||
범례:
|
|
||||||
🆕 = 완전 신규 추가
|
|
||||||
🎯 = 차별화 핵심 기능
|
|
||||||
✨ = 대폭 개선 (프로토타입 기반 재작성)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 핵심 변경사항
|
|
||||||
|
|
||||||
### 1. 신규 추가된 유저스토리 (5개)
|
|
||||||
|
|
||||||
#### 1.1 UFR-USER-010: 로그인 🆕
|
|
||||||
- **이전**: AFR-USER-010 (간략한 인증 설명)
|
|
||||||
- **변경**: UFR-USER-010으로 전환 및 상세화
|
|
||||||
- **의미**:
|
|
||||||
- 로그인 프로세스 단계별 명시 (Enter 키 동작, 로딩 상태 등)
|
|
||||||
- 예외처리 시나리오 구체화 (사번 미입력, 비밀번호 8자 미만 등)
|
|
||||||
- 프로토타입 `01-로그인.html`과 1:1 매핑
|
|
||||||
|
|
||||||
#### 1.2 UFR-USER-020: 대시보드 🆕
|
|
||||||
- **이전**: AFR-USER-020 (간략한 대시보드 설명)
|
|
||||||
- **변경**: UFR-USER-020으로 전환 및 대폭 확장
|
|
||||||
- **의미**:
|
|
||||||
- 통계 블록, 최근 회의, 나의 Todo, 나의 회의록 위젯 상세 명세
|
|
||||||
- FAB 버튼 2가지 액션 (회의예약/바로 시작) 명확화
|
|
||||||
- 프로토타입 `02-대시보드.html`과 1:1 매핑
|
|
||||||
|
|
||||||
#### 1.3 UFR-MEET-015: 참석자 실시간 초대 🆕
|
|
||||||
- **이전**: 없음
|
|
||||||
- **변경**: 완전 신규 추가
|
|
||||||
- **의미**:
|
|
||||||
- 회의 진행 중 "참석자" 탭에서 실시간으로 참석자 추가 기능
|
|
||||||
- 검색 모달 → 추가 → WebSocket 동기화 → 알림 발송 흐름 명시
|
|
||||||
- **효과**: 회의 진행 중 동적 참석자 관리로 유연성 향상
|
|
||||||
- 프로토타입 `05-회의진행.html`의 "참석자" 탭과 연계
|
|
||||||
|
|
||||||
#### 1.4 UFR-AI-030: 실시간 AI 제안 🆕🎯
|
|
||||||
- **이전**: 없음
|
|
||||||
- **변경**: 완전 신규 추가
|
|
||||||
- **의미**:
|
|
||||||
- **차별화 전략 "지능형 회의 진행 지원" 실현**
|
|
||||||
- STT 텍스트 실시간 분석 → 주요 내용 감지 → AI 제안 카드 생성
|
|
||||||
- 제안 카드에서 메모 탭으로 드래그 앤 드롭으로 추가
|
|
||||||
- **효과**: 회의 중 놓치는 내용 최소화, 차별화 핵심 기능
|
|
||||||
- 프로토타입 `05-회의진행.html`의 "AI 제안" 탭과 연계
|
|
||||||
|
|
||||||
#### 1.5 UFR-NOTI-010: 알림 발송 🆕
|
|
||||||
- **이전**: 없음 (암묵적으로 Meeting Service에서 직접 발송)
|
|
||||||
- **변경**: Notification 서비스의 독립적인 유저스토리로 추가
|
|
||||||
- **의미**:
|
|
||||||
- **알림 아키텍처를 폴링 방식으로 통일**
|
|
||||||
- 1분 간격 폴링 → 이메일 발송 → 최대 3회 재시도
|
|
||||||
- 6가지 알림 유형 명시 (Todo 할당, Todo 완료, 회의 시작, 회의록 확정, 참석자 초대, 회의록 수정)
|
|
||||||
- **효과**: Notification 서비스 독립성 확보, 시스템 안정성 향상
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. 대폭 개선된 유저스토리 (주요 8개)
|
|
||||||
|
|
||||||
#### 2.1 UFR-MEET-010: 회의예약
|
|
||||||
- **변경사항**:
|
|
||||||
- 수행절차 10단계 명시 (FAB 버튼 → 입력 → 저장/완료)
|
|
||||||
- 입력 필드별 상세 명세 (타입, 필수 여부, 최대/최소값, UI 요소)
|
|
||||||
- 임시저장/예약 완료 2가지 시나리오 구분
|
|
||||||
- 예외처리 7가지 추가 (제목 미입력, 과거 날짜, 참석자 미선택 등)
|
|
||||||
- **의미**: 프로토타입 `03-회의예약.html` 기반 전면 재작성
|
|
||||||
|
|
||||||
#### 2.2 UFR-MEET-030: 회의시작
|
|
||||||
- **변경사항**:
|
|
||||||
- 회의 진행 화면 4개 탭 상세 명세 (녹음/메모, 참석자, AI 제안, 안건)
|
|
||||||
- 녹음 시작/일시정지/재시작 플로우 명시
|
|
||||||
- 참석자 상태 표시 (온라인/오프라인/참석중)
|
|
||||||
- 탭별 UI 요소와 인터랙션 상세화
|
|
||||||
- **의미**: 프로토타입 `05-회의진행.html` 4개 탭 구조 반영
|
|
||||||
|
|
||||||
#### 2.3 UFR-MEET-040: 회의종료
|
|
||||||
- **변경사항**:
|
|
||||||
- 회의 종료 후 3가지 액션 명시 (바로 확정, 나중에 확정, 검토 후 확정)
|
|
||||||
- 각 액션별 이동 화면 명확화
|
|
||||||
- 안건 요약 및 검증 상태 표시 추가
|
|
||||||
- **의미**: 프로토타입 `07-회의종료.html` 반영, 사용자 선택권 강화
|
|
||||||
|
|
||||||
#### 2.4 UFR-MEET-050: 최종확정
|
|
||||||
- **변경사항**:
|
|
||||||
- 2가지 시나리오 분리 (검토 후 확정, 회의 종료 화면에서 바로 확정)
|
|
||||||
- 안건별 검증 완료 여부 체크 로직 추가
|
|
||||||
- 미검증 안건 있을 시 확정 불가 정책 명시
|
|
||||||
- **의미**: 회의록 품질 보증 메커니즘 강화
|
|
||||||
|
|
||||||
#### 2.5 UFR-MEET-046: 회의록목록조회
|
|
||||||
- **변경사항**:
|
|
||||||
- 샘플 데이터 30개 명시 (제목, 날짜, 상태, 검증 현황 등)
|
|
||||||
- 필터/정렬 기능 상세화 (기간, 상태, 폴더별)
|
|
||||||
- 상태 배지 5종 추가 (진행중, 검토중, 확정완료 등)
|
|
||||||
- **의미**: 프로토타입 `12-회의록목록조회.html` 반영
|
|
||||||
|
|
||||||
#### 2.6 UFR-MEET-047: 회의록상세조회
|
|
||||||
- **변경사항**:
|
|
||||||
- 관련 회의록 섹션 추가 (AI가 자동 연결한 회의록 3개 표시)
|
|
||||||
- 안건별 검증 상태 표시 추가
|
|
||||||
- 용어 팝업 연계 (UFR-RAG-010) 명시
|
|
||||||
- **의미**: 프로토타입 `10-회의록상세조회.html` 반영, RAG 기능 연계
|
|
||||||
|
|
||||||
#### 2.7 UFR-MEET-055: 회의록수정
|
|
||||||
- **변경사항**:
|
|
||||||
- 3가지 진입 시나리오 명시 (회의종료 화면, 목록 화면, 상세조회 화면)
|
|
||||||
- 실시간 협업 플로우 상세화 (UFR-COLLAB-010, UFR-COLLAB-020 연계)
|
|
||||||
- 수정 저장/임시저장/취소 3가지 액션 구분
|
|
||||||
- **의미**: 프로토타입 `11-회의록수정.html` 반영, 협업 기능 강화
|
|
||||||
|
|
||||||
#### 2.8 UFR-COLLAB-020: 충돌해결
|
|
||||||
- **변경사항**:
|
|
||||||
- 안건 기반 충돌 방지 메커니즘 상세화
|
|
||||||
- 동일 안건 동시 수정 시 경고 표시 및 잠금 정책 명시
|
|
||||||
- 충돌 해결 시나리오 3가지 (대기, 새 안건 작성, 취소)
|
|
||||||
- **의미**: 실시간 협업 안정성 강화
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. 유지된 유저스토리 (14개)
|
|
||||||
|
|
||||||
다음 유저스토리들은 v2.2.0과 v2.3.0에서 ID와 핵심 내용이 유지되었습니다:
|
|
||||||
|
|
||||||
- UFR-AI-010 (회의록 자동 작성)
|
|
||||||
- UFR-AI-020 (Todo 자동 추출)
|
|
||||||
- UFR-AI-035 (섹션 AI 요약)
|
|
||||||
- UFR-AI-036 (AI 한줄 요약)
|
|
||||||
- UFR-AI-040 (관련 회의록 연결)
|
|
||||||
- UFR-STT-010 (음성 녹음 인식)
|
|
||||||
- UFR-STT-020 (텍스트 변환)
|
|
||||||
- UFR-RAG-010 (전문용어 감지)
|
|
||||||
- UFR-RAG-020 (맥락 기반 용어 설명)
|
|
||||||
- UFR-COLLAB-010 (회의록 수정 동기화)
|
|
||||||
- UFR-COLLAB-030 (검증 완료)
|
|
||||||
- UFR-TODO-010 (Todo 할당)
|
|
||||||
- UFR-TODO-030 (Todo 완료 처리)
|
|
||||||
- UFR-TODO-040 (Todo 관리)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📈 문서 품질 개선
|
|
||||||
|
|
||||||
### 3.1 유저스토리 형식 표준화
|
|
||||||
|
|
||||||
#### Before (v2.2.0) - 자유 형식
|
|
||||||
```
|
|
||||||
UFR-MEET-010: [회의예약] 회의 생성자로서 | 나는, ...
|
|
||||||
- 시나리오: 회의 예약 및 참석자 초대
|
|
||||||
회의 예약 화면에 접근한 상황에서 | ...
|
|
||||||
|
|
||||||
[입력 요구사항]
|
|
||||||
- 회의 제목: 최대 100자 (필수)
|
|
||||||
...
|
|
||||||
|
|
||||||
[처리 결과]
|
|
||||||
- 회의가 예약됨
|
|
||||||
...
|
|
||||||
|
|
||||||
- M/13
|
|
||||||
```
|
|
||||||
|
|
||||||
#### After (v2.3.0) - 표준 5단계 형식
|
|
||||||
```
|
|
||||||
### UFR-MEET-010: [회의예약] 회의 생성자로서 | 나는, ...
|
|
||||||
|
|
||||||
**수행절차:**
|
|
||||||
1. 대시보드에서 "회의예약" FAB 버튼 클릭
|
|
||||||
2. 회의 제목 입력 (최대 100자)
|
|
||||||
3. 날짜 선택 (오늘 이후 날짜, 달력 UI)
|
|
||||||
...
|
|
||||||
10. "임시저장" 버튼 또는 "예약 완료" 버튼 클릭
|
|
||||||
|
|
||||||
**입력:**
|
|
||||||
- 회의 제목: 텍스트 입력, 필수, 최대 100자, 문자 카운터 표시
|
|
||||||
- 날짜: date 타입, 필수, 오늘 이후 날짜만 선택 가능
|
|
||||||
...
|
|
||||||
|
|
||||||
**출력/결과:**
|
|
||||||
- 예약 완료: "회의가 예약되었습니다" 토스트 메시지, 대시보드로 이동
|
|
||||||
- 임시저장: "임시 저장되었습니다" 토스트 메시지
|
|
||||||
...
|
|
||||||
|
|
||||||
**예외처리:**
|
|
||||||
- 제목 미입력: "회의 제목을 입력해주세요" 토스트, 제목 필드 포커스
|
|
||||||
- 과거 날짜 선택: "과거 날짜는 선택할 수 없습니다" 토스트
|
|
||||||
...
|
|
||||||
|
|
||||||
**관련 유저스토리:**
|
|
||||||
- UFR-USER-020: 대시보드 조회
|
|
||||||
- UFR-MEET-020: 템플릿선택
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 개선 효과
|
|
||||||
|
|
||||||
| 섹션 | 개선 효과 |
|
|
||||||
|------|-----------|
|
|
||||||
| **수행절차** | 단계별 명확한 작업 흐름, 개발자가 UI 플로우 이해 가능 |
|
|
||||||
| **입력** | 필드 타입, 검증 규칙, UI 요소 상세 명세, API 명세서 작성 기준 제공 |
|
|
||||||
| **출력/결과** | 성공/실패 시나리오별 응답 명시, 테스트 케이스 작성 기준 제공 |
|
|
||||||
| **예외처리** | 에러 상황별 처리 방법 구체화, QA 시나리오 명확화 |
|
|
||||||
| **관련 유저스토리** | 기능 간 연계성 추적, 통합 테스트 범위 파악 용이 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏗️ 프로토타입 연계 강화
|
|
||||||
|
|
||||||
v2.3.0에서는 모든 유저스토리가 프로토타입 화면과 명확하게 연계되었습니다.
|
|
||||||
|
|
||||||
| 프로토타입 화면 | 연계 유저스토리 | 상태 |
|
|
||||||
|----------------|----------------|------|
|
|
||||||
| 01-로그인.html | UFR-USER-010 | ✅ 1:1 매핑 |
|
|
||||||
| 02-대시보드.html | UFR-USER-020 | ✅ 1:1 매핑 |
|
|
||||||
| 03-회의예약.html | UFR-MEET-010 | ✅ 1:1 매핑 |
|
|
||||||
| 04-템플릿선택.html | UFR-MEET-020 | ✅ 1:1 매핑 |
|
|
||||||
| 05-회의진행.html | UFR-MEET-030, UFR-MEET-015 (신규), UFR-AI-030 (신규) | ✅ 1:N 매핑 |
|
|
||||||
| 07-회의종료.html | UFR-MEET-040 | ✅ 1:1 매핑 |
|
|
||||||
| 10-회의록상세조회.html | UFR-MEET-047 | ✅ 1:1 매핑 |
|
|
||||||
| 11-회의록수정.html | UFR-MEET-055 | ✅ 1:1 매핑 |
|
|
||||||
| 12-회의록목록조회.html | UFR-MEET-046 | ✅ 1:1 매핑 |
|
|
||||||
| 08-최종확정.html | UFR-MEET-050 | ✅ 1:1 매핑 |
|
|
||||||
|
|
||||||
**결과**: 10개 프로토타입 화면 100% 유저스토리 연계 완료
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔑 핵심 아키텍처 변경
|
|
||||||
|
|
||||||
### 알림 아키텍처: 실시간 → 폴링 방식
|
|
||||||
|
|
||||||
#### Before (v2.2.0)
|
|
||||||
```
|
|
||||||
[Meeting Service] ──(실시간 발송)──> [Notification Service] ──> [Email]
|
|
||||||
↓
|
|
||||||
Todo 할당 발생 → 즉시 이메일 발송
|
|
||||||
```
|
|
||||||
|
|
||||||
**문제점**:
|
|
||||||
- Meeting Service와 Notification Service 간 강한 결합
|
|
||||||
- 이메일 발송 실패 시 Meeting Service에 영향
|
|
||||||
|
|
||||||
#### After (v2.3.0)
|
|
||||||
```
|
|
||||||
[Meeting Service] ──(DB 레코드 생성)──> [Notification 테이블]
|
|
||||||
↓
|
|
||||||
(1분 간격 폴링)
|
|
||||||
↓
|
|
||||||
[Notification Service] ──> [Email]
|
|
||||||
↓
|
|
||||||
(발송 상태 업데이트)
|
|
||||||
```
|
|
||||||
|
|
||||||
**개선 효과**:
|
|
||||||
- ✅ **Notification 서비스 독립성 강화**: 마이크로서비스 간 느슨한 결합
|
|
||||||
- ✅ **시스템 안정성 향상**: 이메일 발송 실패 시 자동 재시도 (최대 3회)
|
|
||||||
- ✅ **확장성 확보**: 폴링 주기 조정으로 트래픽 제어 가능
|
|
||||||
- ✅ **모니터링 용이**: 발송 대기/성공/실패 상태 DB에서 추적
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💡 변경의 의미와 개선 효과
|
|
||||||
|
|
||||||
### 1. 사용자 경험 (UX) 개선
|
|
||||||
|
|
||||||
| 영역 | 개선 내용 | 효과 |
|
|
||||||
|------|----------|------|
|
|
||||||
| **회의 진행 중 유연성** | UFR-MEET-015 (참석자 실시간 초대) | 회의 중 동적 참석자 관리 가능 |
|
|
||||||
| **회의 중 놓침 방지** | UFR-AI-030 (실시간 AI 제안) 🎯 | 차별화 핵심 기능, 회의 중 주요 내용 실시간 감지 |
|
|
||||||
| **회의 종료 후 선택권** | UFR-MEET-040 (3가지 액션) | 바로 확정/나중에 확정/검토 후 확정 |
|
|
||||||
| **회의록 품질 보증** | UFR-MEET-050 (검증 후 확정) | 미검증 안건 있을 시 확정 불가 정책 |
|
|
||||||
| **실시간 협업 안정성** | UFR-COLLAB-020 (안건 기반 충돌 방지) | 동일 안건 동시 수정 시 경고 및 잠금 |
|
|
||||||
|
|
||||||
### 2. 기능적 개선
|
|
||||||
|
|
||||||
| 영역 | 개선 내용 | 효과 |
|
|
||||||
|------|----------|------|
|
|
||||||
| **알림 시스템 안정성** | UFR-NOTI-010 (폴링 방식) | Notification 서비스 독립성 확보, 재시도 메커니즘 |
|
|
||||||
| **차별화 전략 실현** | UFR-AI-030 (실시간 AI 제안) 🎯 | "지능형 회의 진행 지원" 구체화 |
|
|
||||||
| **프로토타입 정합성** | 10개 화면 100% 매핑 | 기획-디자인-개발 간 일관성 확보 |
|
|
||||||
| **유저스토리 표준화** | 5단계 표준 형식 | 개발 가이드 역할 강화, API 명세서 작성 기준 제공 |
|
|
||||||
|
|
||||||
### 3. 문서화 개선
|
|
||||||
|
|
||||||
| 영역 | 개선 내용 | 효과 |
|
|
||||||
|------|----------|------|
|
|
||||||
| **상세도 3배 증가** | 20-30줄 → 60-100줄 | 개발자가 구현에 필요한 모든 정보 확보 |
|
|
||||||
| **AFR 코드 폐지** | AFR → UFR 통일 | 유저스토리 체계 단순화 |
|
|
||||||
| **예외처리 명시** | 각 유저스토리별 5-7개 예외 시나리오 | QA 테스트 케이스 작성 기준 제공 |
|
|
||||||
| **관련 유저스토리 연계** | 기능 간 의존성 추적 | 통합 테스트 범위 명확화 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 권장 후속 조치
|
|
||||||
|
|
||||||
### 🔴 긴급 (1주 내)
|
|
||||||
|
|
||||||
- [ ] **신규 유저스토리 3개 기반 API 설계**
|
|
||||||
- UFR-MEET-015: 참석자 실시간 초대 API
|
|
||||||
- UFR-AI-030: 실시간 AI 제안 API (SSE 또는 WebSocket)
|
|
||||||
- UFR-NOTI-010: 알림 폴링 및 발송 API
|
|
||||||
|
|
||||||
- [ ] **알림 아키텍처 폴링 방식 반영**
|
|
||||||
- 물리 아키텍처 다이어그램 업데이트
|
|
||||||
- Notification 테이블 스키마 정의
|
|
||||||
- 폴링 스케줄러 설계
|
|
||||||
|
|
||||||
- [ ] **프로토타입 ↔ 유저스토리 1:1 매핑 검증**
|
|
||||||
- 10개 화면별 유저스토리 매핑 검증
|
|
||||||
- 누락된 화면 또는 유저스토리 확인
|
|
||||||
|
|
||||||
### 🟡 중요 (2주 내)
|
|
||||||
|
|
||||||
- [ ] **API 설계서 v2.3.0 기반 전면 업데이트**
|
|
||||||
- 입력/출력 명세 반영 (타입, 필수 여부, 검증 규칙)
|
|
||||||
- 예외처리 시나리오 → HTTP 상태 코드 및 에러 메시지 매핑
|
|
||||||
- 관련 유저스토리 기반 API 그룹핑
|
|
||||||
|
|
||||||
- [ ] **예외처리 시나리오 → 테스트 케이스 전환**
|
|
||||||
- 각 유저스토리의 예외처리 섹션을 테스트 케이스로 변환
|
|
||||||
- 입력 검증 테스트 케이스 작성
|
|
||||||
|
|
||||||
- [ ] **관련 유저스토리 기반 통합 테스트 시나리오 작성**
|
|
||||||
- 예: UFR-MEET-010 → UFR-MEET-020 → UFR-MEET-030 전체 플로우 테스트
|
|
||||||
|
|
||||||
### 🟢 일반 (3주 내)
|
|
||||||
|
|
||||||
- [ ] **유저스토리별 개발 우선순위 재평가**
|
|
||||||
- 신규 유저스토리 3개 우선순위 결정
|
|
||||||
- 차별화 핵심 기능 (UFR-AI-030) 우선 개발 검토
|
|
||||||
|
|
||||||
- [ ] **신규 기능 3개 개발 일정 수립**
|
|
||||||
- UFR-MEET-015: 참석자 실시간 초대
|
|
||||||
- UFR-AI-030: 실시간 AI 제안 (Sprint 목표로 권장)
|
|
||||||
- UFR-NOTI-010: 알림 발송
|
|
||||||
|
|
||||||
- [ ] **프로토타입 기반 개발 가이드 작성**
|
|
||||||
- 프로토타입 → 유저스토리 → API → 컴포넌트 매핑 가이드
|
|
||||||
- 프론트엔드 개발자를 위한 프로토타입 활용 가이드
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔍 핵심 시사점 (Key Takeaways)
|
|
||||||
|
|
||||||
1. **v2.3.0은 프로토타입 분석을 통해 유저스토리를 전면 재정비한 버전**
|
|
||||||
- 10개 프로토타입 화면과 100% 매핑
|
|
||||||
- 실제 UI/UX 플로우를 유저스토리에 반영
|
|
||||||
|
|
||||||
2. **신규 기능 3개 추가로 차별화 강화**
|
|
||||||
- 특히 UFR-AI-030 (실시간 AI 제안)은 차별화 핵심 기능
|
|
||||||
|
|
||||||
3. **알림 아키텍처 폴링 방식으로 통일하여 시스템 안정성 확보**
|
|
||||||
- Notification 서비스 독립성 강화
|
|
||||||
- 재시도 메커니즘으로 안정성 향상
|
|
||||||
|
|
||||||
4. **유저스토리 형식 표준화로 개발 가이드 역할 강화**
|
|
||||||
- 5단계 표준 형식 (수행절차, 입력, 출력/결과, 예외처리, 관련 유저스토리)
|
|
||||||
- API 명세서 및 테스트 케이스 작성 기준 제공
|
|
||||||
|
|
||||||
5. **평균 유저스토리 상세도 약 3배 증가로 품질 대폭 향상**
|
|
||||||
- 개발자가 구현에 필요한 모든 정보 포함
|
|
||||||
- 예외처리, 검증 규칙, UI 요소까지 상세 명시
|
|
||||||
|
|
||||||
6. **기존 24개 유저스토리 ID 승계하여 연속성 유지**
|
|
||||||
- AFR-USER-010 → UFR-USER-010 전환
|
|
||||||
- 기존 설계 문서와의 연계성 유지
|
|
||||||
|
|
||||||
7. **프로토타입-유저스토리 1:1 매핑으로 개발 명확성 확보**
|
|
||||||
- 기획-디자인-개발 간 일관성 확보
|
|
||||||
- 개발 우선순위 및 Sprint 계획 수립 용이
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📎 참고 자료
|
|
||||||
|
|
||||||
- **상세 분석 (JSON)**: `claude/userstory-comparison-v2.2.0-to-v2.3.0.json` (19KB)
|
|
||||||
- **상세 분석 (Markdown)**: `claude/userstory-comparison-v2.2.0-to-v2.3.0.md` (16KB)
|
|
||||||
- **요약 분석**: `claude/userstory-comparison-summary.md` (11KB)
|
|
||||||
- **유저스토리 v2.2.0 백업**: `design/userstory_v2.2.0_backup.md`
|
|
||||||
- **유저스토리 v2.3.0 현재**: `design/userstory.md`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**보고서 작성**: 지수 (Product Designer), 민준 (Product Owner)
|
|
||||||
**분석 일시**: 2025-10-25
|
|
||||||
**문서 버전**: 1.0
|
|
||||||
@ -107,11 +107,41 @@
|
|||||||
margin-bottom: var(--space-md);
|
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 {
|
.meeting-basic-info h2 {
|
||||||
font-size: var(--font-h2);
|
font-size: var(--font-h2);
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
color: var(--gray-900);
|
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 {
|
.info-row {
|
||||||
@ -165,23 +195,6 @@
|
|||||||
.participant {
|
.participant {
|
||||||
width: calc(50% - var(--space-md) / 2);
|
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);
|
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 {
|
.reference-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -310,7 +307,7 @@
|
|||||||
|
|
||||||
.reference-title {
|
.reference-title {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: var(--font-small);
|
font-size: var(--font-body);
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
color: var(--gray-900);
|
color: var(--gray-900);
|
||||||
}
|
}
|
||||||
@ -338,7 +335,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.reference-meta {
|
.reference-meta {
|
||||||
font-size: var(--font-caption);
|
font-size: var(--font-small);
|
||||||
color: var(--gray-500);
|
color: var(--gray-500);
|
||||||
margin-bottom: var(--space-xs);
|
margin-bottom: var(--space-xs);
|
||||||
}
|
}
|
||||||
@ -405,10 +402,11 @@
|
|||||||
color: var(--white);
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 통계 그리드 - 모바일 기본 (2x2) */
|
||||||
.stats-grid {
|
.stats-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: var(--space-md);
|
gap: var(--space-sm);
|
||||||
margin-top: var(--space-md);
|
margin-top: var(--space-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,6 +429,14 @@
|
|||||||
color: var(--gray-500);
|
color: var(--gray-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 데스크톱: 1x4 그리드 */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.stats-grid {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 결정사항 카드 */
|
/* 결정사항 카드 */
|
||||||
.decision-card {
|
.decision-card {
|
||||||
background: var(--white);
|
background: var(--white);
|
||||||
@ -462,7 +468,7 @@
|
|||||||
border-top: 1px solid var(--gray-300);
|
border-top: 1px solid var(--gray-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Todo 진행상황 */
|
/* Todo 리스트 */
|
||||||
.todo-filters {
|
.todo-filters {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
@ -523,7 +529,64 @@
|
|||||||
margin-bottom: var(--space-xs);
|
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 {
|
.todo-filters {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
@ -688,6 +751,24 @@
|
|||||||
display: block;
|
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) {
|
@media (max-width: 600px) {
|
||||||
.reference-item {
|
.reference-item {
|
||||||
@ -738,10 +819,14 @@
|
|||||||
<!-- 기본 정보 카드 -->
|
<!-- 기본 정보 카드 -->
|
||||||
<div class="info-card">
|
<div class="info-card">
|
||||||
<div class="meeting-basic-info">
|
<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);">
|
<div class="meeting-title-container">
|
||||||
<span class="badge badge-complete">확정완료</span>
|
<!-- 배지 + 크라운 영역 -->
|
||||||
<!-- 생성자일 경우 👑 아이콘이 JavaScript로 추가됨 -->
|
<div class="meeting-badges" id="meeting-badges">
|
||||||
<h2 style="margin: 0;">2025년 1분기 제품 기획 회의</h2>
|
<span class="badge badge-complete">확정완료</span>
|
||||||
|
<!-- 생성자일 경우 👑 아이콘이 JavaScript로 추가됨 -->
|
||||||
|
</div>
|
||||||
|
<!-- 회의 제목 -->
|
||||||
|
<h2>2025년 1분기 제품 기획 회의</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-row">
|
<div class="info-row">
|
||||||
<span class="info-icon">📅</span>
|
<span class="info-icon">📅</span>
|
||||||
@ -757,7 +842,7 @@
|
|||||||
<div class="participant">
|
<div class="participant">
|
||||||
<div class="avatar avatar-green">김</div>
|
<div class="avatar avatar-green">김</div>
|
||||||
<span class="participant-name">김민준</span>
|
<span class="participant-name">김민준</span>
|
||||||
<span class="role-badge">작성자</span>
|
<span class="role-badge">생성자</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="participant">
|
<div class="participant">
|
||||||
<div class="avatar avatar-blue">박</div>
|
<div class="avatar avatar-blue">박</div>
|
||||||
@ -1044,60 +1129,65 @@
|
|||||||
|
|
||||||
<!-- Todo 단순 조회 (MVP 스코프 축소 v1.5.1) -->
|
<!-- Todo 단순 조회 (MVP 스코프 축소 v1.5.1) -->
|
||||||
<div class="card mb-lg">
|
<div class="card mb-lg">
|
||||||
<h3 class="card-title">📋 Todo 진행상황</h3>
|
<h3 class="card-title">📋 Todo 리스트</h3>
|
||||||
<p style="font-size: var(--font-small); color: var(--gray-600); margin-bottom: var(--space-md);">
|
<p style="font-size: var(--font-small); color: var(--gray-600); margin-bottom: var(--space-md);">
|
||||||
Todo 항목은 조회만 가능합니다. 제목, 담당자, 마감일 정보만 표시됩니다.
|
Todo 항목은 조회만 가능합니다. 제목, 담당자, 마감일 정보만 표시됩니다.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Todo 단순 조회 리스트 (제목 + 담당자 + 마감일만 표시) -->
|
<!-- Todo 단순 조회 리스트 (제목 + 담당자 + 마감일 + 수정 버튼) -->
|
||||||
<div style="display: flex; flex-direction: column; gap: var(--space-sm);">
|
<div class="simple-todo-list">
|
||||||
<div style="padding: var(--space-md); background: var(--gray-50); border-radius: var(--radius-md); border: 1px solid var(--gray-200);">
|
<div class="simple-todo-item">
|
||||||
<div style="font-weight: var(--font-weight-medium); color: var(--gray-900); margin-bottom: var(--space-xs);">
|
<div class="simple-todo-header">
|
||||||
데이터베이스 스키마 설계
|
<div class="simple-todo-title">데이터베이스 스키마 설계</div>
|
||||||
|
<button class="simple-todo-edit-btn" onclick="editTodo(1)" title="수정">✏️</button>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: var(--font-small); color: var(--gray-600);">
|
<div class="simple-todo-meta">
|
||||||
<span>👤 이준호</span>
|
<span>👤 이준호</span>
|
||||||
<span style="margin-left: var(--space-md);">📅 2025-10-20</span>
|
<span>📅 2025-10-20</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="padding: var(--space-md); background: var(--gray-50); border-radius: var(--radius-md); border: 1px solid var(--gray-200);">
|
<div class="simple-todo-item">
|
||||||
<div style="font-weight: var(--font-weight-medium); color: var(--gray-900); margin-bottom: var(--space-xs);">
|
<div class="simple-todo-header">
|
||||||
API 명세서 작성
|
<div class="simple-todo-title">API 명세서 작성</div>
|
||||||
|
<button class="simple-todo-edit-btn" onclick="editTodo(2)" title="수정">✏️</button>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: var(--font-small); color: var(--gray-600);">
|
<div class="simple-todo-meta">
|
||||||
<span>👤 이준호</span>
|
<span>👤 이준호</span>
|
||||||
<span style="margin-left: var(--space-md);">📅 2025-10-23</span>
|
<span>📅 2025-10-23</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="padding: var(--space-md); background: var(--gray-50); border-radius: var(--radius-md); border: 1px solid var(--gray-200);">
|
<div class="simple-todo-item">
|
||||||
<div style="font-weight: var(--font-weight-medium); color: var(--gray-900); margin-bottom: var(--space-xs);">
|
<div class="simple-todo-header">
|
||||||
예산 편성안 검토
|
<div class="simple-todo-title">예산 편성안 검토</div>
|
||||||
|
<button class="simple-todo-edit-btn" onclick="editTodo(3)" title="수정">✏️</button>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: var(--font-small); color: var(--gray-600);">
|
<div class="simple-todo-meta">
|
||||||
<span>👤 김민준</span>
|
<span>👤 김민준</span>
|
||||||
<span style="margin-left: var(--space-md);">📅 2025-10-22</span>
|
<span>📅 2025-10-22</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="padding: var(--space-md); background: var(--gray-50); border-radius: var(--radius-md); border: 1px solid var(--gray-200);">
|
<div class="simple-todo-item">
|
||||||
<div style="font-weight: var(--font-weight-medium); color: var(--gray-900); margin-bottom: var(--space-xs);">
|
<div class="simple-todo-header">
|
||||||
UI 프로토타입 디자인
|
<div class="simple-todo-title">UI 프로토타입 디자인</div>
|
||||||
|
<button class="simple-todo-edit-btn" onclick="editTodo(4)" title="수정">✏️</button>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: var(--font-small); color: var(--gray-600);">
|
<div class="simple-todo-meta">
|
||||||
<span>👤 최유진</span>
|
<span>👤 최유진</span>
|
||||||
<span style="margin-left: var(--space-md);">📅 2025-10-28</span>
|
<span>📅 2025-10-28</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="padding: var(--space-md); background: var(--gray-50); border-radius: var(--radius-md); border: 1px solid var(--gray-200);">
|
<div class="simple-todo-item">
|
||||||
<div style="font-weight: var(--font-weight-medium); color: var(--gray-900); margin-bottom: var(--space-xs);">
|
<div class="simple-todo-header">
|
||||||
사용자 피드백 분석
|
<div class="simple-todo-title">사용자 피드백 분석</div>
|
||||||
|
<button class="simple-todo-edit-btn" onclick="editTodo(5)" title="수정">✏️</button>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: var(--font-small); color: var(--gray-600);">
|
<div class="simple-todo-meta">
|
||||||
<span>👤 김민준</span>
|
<span>👤 김민준</span>
|
||||||
<span style="margin-left: var(--space-md);">📅 2025-10-19</span>
|
<span>📅 2025-10-19</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1113,7 +1203,7 @@
|
|||||||
<span class="reference-title">AI 기능 개선 회의</span>
|
<span class="reference-title">AI 기능 개선 회의</span>
|
||||||
<span class="relevance-badge relevance-high">92%</span>
|
<span class="relevance-badge relevance-high">92%</span>
|
||||||
</div>
|
</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">
|
<div class="reference-summary">
|
||||||
AI 요약 정확도 개선 방안 논의. BERT 모델 도입 및 학습 데이터 확보 계획 수립.
|
AI 요약 정확도 개선 방안 논의. BERT 모델 도입 및 학습 데이터 확보 계획 수립.
|
||||||
</div>
|
</div>
|
||||||
@ -1125,7 +1215,7 @@
|
|||||||
<span class="reference-title">개발 리소스 계획 회의</span>
|
<span class="reference-title">개발 리소스 계획 회의</span>
|
||||||
<span class="relevance-badge relevance-medium">88%</span>
|
<span class="relevance-badge relevance-medium">88%</span>
|
||||||
</div>
|
</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">
|
<div class="reference-summary">
|
||||||
Q4 개발 리소스 현황 및 배분 계획. 신규 프로젝트 우선순위 협의.
|
Q4 개발 리소스 현황 및 배분 계획. 신규 프로젝트 우선순위 협의.
|
||||||
</div>
|
</div>
|
||||||
@ -1137,7 +1227,7 @@
|
|||||||
<span class="reference-title">경쟁사 분석 회의</span>
|
<span class="reference-title">경쟁사 분석 회의</span>
|
||||||
<span class="relevance-badge relevance-medium">78%</span>
|
<span class="relevance-badge relevance-medium">78%</span>
|
||||||
</div>
|
</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">
|
<div class="reference-summary">
|
||||||
경쟁사 A, B, C 분석 결과. 우리의 차별점은 실시간 협업 및 검증 기능.
|
경쟁사 A, B, C 분석 결과. 우리의 차별점은 실시간 협업 및 검증 기능.
|
||||||
</div>
|
</div>
|
||||||
@ -1151,6 +1241,33 @@
|
|||||||
<button class="btn btn-secondary" onclick="navigateTo('11-회의록수정.html')">수정</button>
|
<button class="btn btn-secondary" onclick="navigateTo('11-회의록수정.html')">수정</button>
|
||||||
</div>
|
</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">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Todo 내용</label>
|
||||||
|
<input type="text" class="form-control" placeholder="할 일을 입력하세요">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">마감일</label>
|
||||||
|
<div class="date-input-wrapper">
|
||||||
|
<input type="date" 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 편집 모달 -->
|
<!-- Todo 편집 모달 -->
|
||||||
<div class="modal-overlay" id="editTodoModal">
|
<div class="modal-overlay" id="editTodoModal">
|
||||||
<div class="modal">
|
<div class="modal">
|
||||||
@ -1159,6 +1276,7 @@
|
|||||||
<button class="modal-close" onclick="closeModal('editTodoModal')">×</button>
|
<button class="modal-close" onclick="closeModal('editTodoModal')">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
<input type="hidden" id="editTodoId">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Todo 제목 <span class="text-error">*</span></label>
|
<label class="form-label">Todo 제목 <span class="text-error">*</span></label>
|
||||||
<input type="text" id="editTodoTitle" class="form-control" placeholder="할 일을 입력하세요">
|
<input type="text" id="editTodoTitle" class="form-control" placeholder="할 일을 입력하세요">
|
||||||
@ -1181,14 +1299,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="form-hint">📅 마감일 변경 시 캘린더가 자동 업데이트됩니다</p>
|
<p class="form-hint">📅 마감일 변경 시 캘린더가 자동 업데이트됩니다</p>
|
||||||
</div>
|
</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">
|
<div class="alert alert-info" id="editTodoPermissionInfo">
|
||||||
<span class="material-icons" style="font-size: 20px;">info</span>
|
<span class="material-icons" style="font-size: 20px;">info</span>
|
||||||
@ -1612,6 +1722,136 @@
|
|||||||
if (filterCompletedCount) filterCompletedCount.textContent = completed;
|
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');
|
||||||
|
const permissionText = document.getElementById('editTodoPermissionText');
|
||||||
|
|
||||||
|
if (isCreator) {
|
||||||
|
// 회의 생성자: 담당자 변경 가능
|
||||||
|
assigneeGroup.style.display = 'block';
|
||||||
|
permissionText.textContent = '회의 생성자로서 모든 항목을 수정할 수 있습니다.';
|
||||||
|
} else {
|
||||||
|
// 일반 담당자: 담당자 변경 불가
|
||||||
|
assigneeGroup.style.display = 'none';
|
||||||
|
permissionText.textContent = '본인에게 할당된 Todo만 수정할 수 있습니다. 담당자는 변경할 수 없습니다.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 모달 열기
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 페이지 초기화
|
* 페이지 초기화
|
||||||
*/
|
*/
|
||||||
@ -1621,13 +1861,13 @@
|
|||||||
const isCreator = checkIfUserIsCreator(CURRENT_MEETING_ID, currentUser);
|
const isCreator = checkIfUserIsCreator(CURRENT_MEETING_ID, currentUser);
|
||||||
|
|
||||||
if (isCreator) {
|
if (isCreator) {
|
||||||
const titleContainer = document.getElementById('meeting-title-container');
|
const badgesContainer = document.getElementById('meeting-badges');
|
||||||
const badge = titleContainer.querySelector('.badge');
|
|
||||||
const crownIcon = document.createElement('span');
|
const crownIcon = document.createElement('span');
|
||||||
crownIcon.textContent = '👑';
|
crownIcon.textContent = '👑';
|
||||||
crownIcon.style.fontSize = '24px';
|
crownIcon.style.fontSize = '20px';
|
||||||
// badge 다음에 👑 삽입
|
crownIcon.title = '회의 생성자';
|
||||||
badge.insertAdjacentElement('afterend', crownIcon);
|
// 배지 영역에 크라운 추가
|
||||||
|
badgesContainer.appendChild(crownIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTodoProgress();
|
updateTodoProgress();
|
||||||
|
|||||||
@ -174,6 +174,14 @@
|
|||||||
.reference-item {
|
.reference-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-right: 40px; /* 삭제 버튼 공간 확보 */
|
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 {
|
.remove-btn {
|
||||||
@ -399,7 +407,7 @@
|
|||||||
<div class="ai-summary-edit">
|
<div class="ai-summary-edit">
|
||||||
<div class="ai-summary-header">
|
<div class="ai-summary-header">
|
||||||
<span class="ai-summary-label">💡 AI 요약</span>
|
<span class="ai-summary-label">💡 AI 요약</span>
|
||||||
<button class="btn-primary btn-sm" onclick="regenerateSummary(1)">AI 재생성</button>
|
<button class="btn btn-primary btn-sm" onclick="regenerateSummary(1)">AI 재생성</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
class="ai-summary-textarea"
|
class="ai-summary-textarea"
|
||||||
@ -492,7 +500,7 @@
|
|||||||
<div class="ai-summary-edit">
|
<div class="ai-summary-edit">
|
||||||
<div class="ai-summary-header">
|
<div class="ai-summary-header">
|
||||||
<span class="ai-summary-label">💡 AI 요약</span>
|
<span class="ai-summary-label">💡 AI 요약</span>
|
||||||
<button class="btn-primary btn-sm" onclick="regenerateSummary(2)">AI 재생성</button>
|
<button class="btn btn-primary btn-sm" onclick="regenerateSummary(2)">AI 재생성</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
class="ai-summary-textarea"
|
class="ai-summary-textarea"
|
||||||
@ -569,7 +577,7 @@
|
|||||||
<div class="ai-summary-edit">
|
<div class="ai-summary-edit">
|
||||||
<div class="ai-summary-header">
|
<div class="ai-summary-header">
|
||||||
<span class="ai-summary-label">💡 AI 요약</span>
|
<span class="ai-summary-label">💡 AI 요약</span>
|
||||||
<button class="btn-primary btn-sm" onclick="regenerateSummary(3)">AI 재생성</button>
|
<button class="btn btn-primary btn-sm" onclick="regenerateSummary(3)">AI 재생성</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
class="ai-summary-textarea"
|
class="ai-summary-textarea"
|
||||||
|
|||||||
@ -1429,11 +1429,12 @@ input[type="date"]::-webkit-calendar-picker-indicator {
|
|||||||
.related-meeting-item {
|
.related-meeting-item {
|
||||||
background: var(--white);
|
background: var(--white);
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
padding: var(--space-sm);
|
padding: var(--space-md);
|
||||||
margin-bottom: var(--space-sm);
|
margin-bottom: var(--space-sm);
|
||||||
display: flex;
|
cursor: pointer;
|
||||||
gap: var(--space-sm);
|
transition: all var(--transition-fast);
|
||||||
transition: background var(--transition-fast);
|
border: 1px solid var(--gray-200);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.reference-item:last-child,
|
.reference-item:last-child,
|
||||||
@ -1443,7 +1444,15 @@ input[type="date"]::-webkit-calendar-picker-indicator {
|
|||||||
|
|
||||||
.reference-item:hover,
|
.reference-item:hover,
|
||||||
.related-meeting-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,
|
.reference-content,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user