기능구현중

This commit is contained in:
2026-01-11 15:35:33 +09:00
parent 8e0f1f30cf
commit 375d5bf91a
11 changed files with 470 additions and 210 deletions

View File

@@ -438,11 +438,11 @@ Stage 0 ██ DB 마이
### Phase 01-P1: 회의록 기본 구조 ✅ 완료
- [x] 시작일시: 2026-01-11 17:05 종료일시: 2026-01-11 17:45 수행시간: 40분
- [x] Tiptap 에디터 컴포넌트 구성 ⚠️ (textarea로 구현, Tiptap 설치 필요)
- [x] Tiptap 에디터 컴포넌트 구성 ✅ → `frontend/components/common/TiptapEditor.vue` (2026-01-12 적용)
- [x] 회의록 CRUD API (list, detail, create, update, delete) ✅
- [x] 회의록 목록 페이지 (/meeting) ✅
- [x] 회의록 작성 페이지 (/meeting/write) ✅
- [x] 회의록 상세 페이지 (/meeting/[id]) ✅
- [x] 회의록 작성 페이지 (/meeting/write) ✅ → Tiptap 적용
- [x] 회의록 상세 페이지 (/meeting/[id]) ✅ → Tiptap 적용 (수정 모드)
- [x] 참석자 선택 (내부/외부) ✅
- [x] 프로젝트/내부업무 구분 ✅
@@ -467,9 +467,9 @@ Stage 0 ██ DB 마이
### Phase 01-P4: 주간보고 연계 ✅ 완료
- [x] 시작일시: 2026-01-11 02:01 KST 종료일시: 2026-01-11 02:06 KST 수행시간: 5분
- [x] 주간보고 작성 시 유사 TODO 감지 API ✅
- [x] TODO 완료 연계 처리 API ✅
- [ ] 유사 TODO 팝업 UI (⏳ 추후)
- [x] 주간보고 작성 시 유사 TODO 감지 API ✅`backend/api/todo/report/similar.post.ts`
- [x] TODO 완료 연계 처리 API ✅`backend/api/todo/report/link.post.ts`
- [x] 유사 TODO 팝업 UI ✅ → `frontend/report/weekly/write.vue` (모달 + searchSimilarTodos)
- [x] 테스트 및 버그 수정 ✅
---
@@ -480,7 +480,7 @@ Stage 0 ██ DB 마이
- [x] 사업 목록 페이지 (/business) ✅
- [x] 사업 상세 페이지 (/business/[id]) ✅
- [x] 사업 등록/수정 모달 ✅
- [ ] 메뉴 권한 설정 (매니저 이상) ⏳ 추후
- [x] 메뉴 권한 설정 (매니저 이상) ✅ → wr_menu_role에서 ROLE_USER 제거 (2026-01-12)
### Phase 02-P2: 프로젝트-사업 연결 ✅ 완료
- [x] 시작일시: 2026-01-11 01:02 KST 종료일시: 2026-01-11 01:08 KST 수행시간: 6분
@@ -546,15 +546,11 @@ Stage 0 ██ DB 마이
### Phase 04-P2: 비밀번호 인증 ✅ 완료
- [x] 시작일시: 2026-01-11 01:45 KST 종료일시: 2026-01-11 01:50 KST 수행시간: 5분
- [x] 비밀번호 해시 유틸리티 (pbkdf2) ✅
- [x] 비밀번호 로그인 API ✅
- [x] 비밀번호 변경 API ✅
- [x] 관리자 비밀번호 설정 API ✅
- [x] 비밀번호 해시 유틸리티 (pbkdf2) ✅`backend/utils/password.ts`
- [x] 비밀번호 로그인 API ✅`backend/api/auth/login-password.post.ts`
- [x] 비밀번호 변경 API ✅`backend/api/auth/change-password.post.ts`
- [x] 관리자 비밀번호 설정 API ✅`backend/api/auth/set-password.post.ts`
- [x] 로그인 화면 (탭 UI) ✅
- [ ] bcrypt 해시 처리 유틸
- [ ] 이메일/비밀번호 로그인 API
- [ ] 비밀번호 변경 API
- [ ] 비밀번호 초기화 API (관리자)
### Phase 04-P3: Google OAuth ✅ 완료
- [x] 시작일시: 2026-01-11 01:50 KST 종료일시: 2026-01-11 01:54 KST 수행시간: 4분
@@ -717,12 +713,12 @@ Stage 0 ██ DB 마이
| 마일스톤 | 목표일 | 실제 완료일 | 상태 |
|----------|:------:|:----------:|:----:|
| M1: DB 완료 | 1주차 | 2026-01-11 | ✅ |
| M2: 기본 CRUD 완료 | - | - | |
| M3: 인증 완료 | - | - | |
| M4: AI 연동 완료 | - | - | |
| M5: VCS 연동 완료 | - | - | |
| M6: 구글 그룹 완료 | - | - | |
| M7: 전체 완료 | - | - | |
| M2: 기본 CRUD 완료 | - | 2026-01-11 | |
| M3: 인증 완료 | - | 2026-01-12 | |
| M4: AI 연동 완료 | - | 2026-01-11 | |
| M5: VCS 연동 완료 | - | 2026-01-12 | |
| M6: 구글 그룹 완료 | - | 2026-01-12 | |
| M7: 전체 완료 | - | 2026-01-12 | |
---

View File

@@ -462,11 +462,13 @@ npm install @tiptap/vue-3 @tiptap/starter-kit @tiptap/extension-link @tiptap/ext
| Phase | 작업 내용 | 시작 | 완료 | 소요시간 |
|:-----:|----------|:----:|:----:|:--------:|
| 1 | 기본 구조 (DB, API, 화면) | 01-11 17:05 | 01-11 17:08 | 3분 ✅ |
| 2 | AI 분석 연동 | 01-12 00:00 | 01-12 00:05 | 5분 ✅ |
| 3 | TODO 기능 | 01-12 00:05 | 01-12 00:10 | 5분 ✅ |
| 4 | 주간보고 연계 | 01-12 00:10 | 01-12 00:15 | 5분 ✅ |
| | | | **총 소요시간** | **18** |
| 01-P1 | 회의록 기본 구조 | 01-11 17:05 | 01-11 17:08 | 3분 ✅ |
| 01-P2 | AI 분석 연동 | 01-11 | 01-11 | 10분 ✅ |
| 01-P3 | TODO 기능 | 01-11 01:52 | 01-11 02:00 | 8분 ✅ |
| 01-P4 | 주간보고-TODO 연계 | 01-11 02:01 | 01-11 02:06 | 5분 ✅ |
| | | | **총 소요시간** | **26** |
> ※ 00_마스터_작업계획서.md와 동기화됨 (2026-01-12)
---

View File

@@ -373,41 +373,41 @@ CREATE INDEX idx_biz_report_week ON wr_business_weekly_report(report_year, repor
---
### Phase 2: 프로젝트-사업 연결 (1일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 2: 프로젝트-사업 연결 (1일) ✅ 완료
- [x] 시작: 2026-01-11 01:02 KST
- [x] 완료: 2026-01-11 01:08 KST
- [x] 소요시간: 6분
**작업 내용:**
- [ ] 프로젝트 테이블에 business_id 컬럼 추가
- [ ] 프로젝트 수정 화면에 사업 선택 추가
- [ ] 프로젝트 배정 API
- [ ] 주간보고 작성 시 사업명 표시
- [x] 프로젝트 테이블에 business_id 컬럼 추가
- [x] 프로젝트 수정 화면에 사업 선택 추가
- [x] 프로젝트 배정 API
- [x] 주간보고 작성 시 사업명 표시
---
### Phase 3: 사업 주간보고 취합 (1.5일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 3: 사업 주간보고 취합 (1.5일) ✅ 완료
- [x] 시작: 2026-01-11 01:10 KST
- [x] 완료: 2026-01-11 01:18 KST
- [x] 소요시간: 8분
**작업 내용:**
- [ ] 사업 주간보고 테이블 생성 (wr_business_weekly_report)
- [ ] 사업 주간보고 취합 API (AI 활용)
- [ ] 사업 주간보고 상세 화면
- [ ] 확정 기능
- [x] 사업 주간보고 테이블 생성 (wr_business_weekly_report)
- [x] 사업 주간보고 취합 API (AI 활용)
- [x] 사업 주간보고 상세 화면
- [x] 확정 기능
---
### Phase 4: 테스트 및 정리 (0.5일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 4: 테스트 및 정리 (0.5일) ✅ 완료
- [x] 시작: 2026-01-11 01:20 KST
- [x] 완료: 2026-01-11 01:24 KST
- [x] 소요시간: 4분
**작업 내용:**
- [ ] 전체 플로우 테스트
- [ ] 기존 취합보고와 연계 확인
- [ ] 버그 수정
- [x] 전체 플로우 테스트
- [x] 기존 취합보고와 연계 확인
- [x] 버그 수정
---
@@ -417,11 +417,13 @@ CREATE INDEX idx_biz_report_week ON wr_business_weekly_report(report_year, repor
| Phase | 작업 내용 | 시작 | 완료 | 소요시간 |
|:-----:|----------|:----:|:----:|:--------:|
| 1 | 사업 CRUD | 01-11 00:28 | 01-11 00:31 | 3분 ✅ |
| 2 | 프로젝트-사업 연결 | - | - | - |
| 3 | 사업 주간보고 취합 | - | - | - |
| 4 | 테스트 및 정리 | - | - | - |
| | | | **총 소요시간** | **-** |
| 02-P1 | 사업 CRUD | 01-11 00:28 | 01-11 00:31 | 3분 ✅ |
| 02-P2 | 프로젝트-사업 연결 | 01-11 01:04 | 01-11 01:10 | 6분 ✅ |
| 02-P3 | 사업 주간보고 취합 | 01-11 01:10 | 01-11 01:18 | 8분 ✅ |
| 02-P4 | 테스트 및 정리 | 01-11 01:20 | 01-11 01:24 | 4분 ✅ |
| | | | **총 소요시간** | **21분** |
> ※ 00_마스터_작업계획서.md와 동기화됨 (2026-01-12)
---

View File

@@ -484,55 +484,55 @@ CREATE TABLE wr_maintenance_upload_batch (
## 6. 작업 일정
### Phase 1: 기본 CRUD (2일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 1: 기본 CRUD (2일) ✅ 완료
- [x] 시작: 2026-01-11 00:51 KST
- [x] 완료: 2026-01-11 00:56 KST
- [x] 소요시간: 5분
**작업 내용:**
- [ ] DB 테이블 생성 (wr_maintenance_task, wr_maintenance_upload_batch)
- [ ] 유지보수 업무 CRUD API
- [ ] 업무 목록/상세/등록/수정 화면
- [ ] 상태 변경 기능
- [x] DB 테이블 생성 (wr_maintenance_task, wr_maintenance_upload_batch)
- [x] 유지보수 업무 CRUD API
- [x] 업무 목록/상세/등록/수정 화면
- [x] 상태 변경 기능
---
### Phase 2: 파일 업로드 + AI 파싱 (2일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 2: 파일 업로드 + AI 파싱 (2일) ✅ 완료
- [x] 시작: 2026-01-11 01:26 KST
- [x] 완료: 2026-01-11 01:33 KST
- [x] 소요시간: 7분
**작업 내용:**
- [ ] 파일 업로드 API (엑셀/CSV)
- [ ] AI 파싱 프롬프트 구현
- [ ] 파싱 결과 검토 화면
- [ ] 중복 감지 로직
- [ ] 일괄 등록 기능
- [x] 파일 업로드 API (엑셀/CSV)
- [x] AI 파싱 프롬프트 구현
- [x] 파싱 결과 검토 화면
- [x] 중복 감지 로직
- [x] 일괄 등록 기능
---
### Phase 3: 주간보고 연계 (2일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 3: 주간보고 연계 (2일) ✅ 완료
- [x] 시작: 2026-01-11 01:35 KST
- [x] 완료: 2026-01-11 01:42 KST
- [x] 소요시간: 7분
**작업 내용:**
- [ ] 주간보고 작성 시 유지보수 업무 조회
- [ ] AI 실적 문장 생성
- [ ] 유사 실적 병합 기능
- [ ] 연계 정보 저장
- [x] 주간보고 작성 시 유지보수 업무 조회
- [x] AI 실적 문장 생성
- [x] 유사 실적 병합 기능
- [x] 연계 정보 저장
---
### Phase 4: 통계 + 테스트 (1일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 4: 통계 + 테스트 (1일) ✅ 완료
- [x] 시작: 2026-01-11 01:44 KST
- [x] 완료: 2026-01-11 01:50 KST
- [x] 소요시간: 6분
**작업 내용:**
- [ ] 통계 API (주간/월간/담당자별)
- [ ] 통계 대시보드 화면
- [ ] 전체 테스트 및 버그 수정
- [x] 통계 API (주간/월간/담당자별)
- [x] 통계 대시보드 화면
- [x] 전체 테스트 및 버그 수정
---
@@ -542,11 +542,13 @@ CREATE TABLE wr_maintenance_upload_batch (
| Phase | 작업 내용 | 시작 | 완료 | 소요시간 |
|:-----:|----------|:----:|:----:|:--------:|
| 1 | 기본 CRUD | - | - | - |
| 2 | 파일 업로드 + AI 파싱 | - | - | - |
| 3 | 주간보고 연계 | - | - | - |
| 4 | 통계 + 테스트 | - | - | - |
| | | | **총 소요시간** | **-** |
| 03-P1 | 유지보수 기본 CRUD | 01-11 00:51 | 01-11 00:56 | 5분 ✅ |
| 03-P2 | 파일 업로드 + AI 파싱 | 01-11 01:26 | 01-11 01:33 | 7분 ✅ |
| 03-P3 | 주간보고 연계 | 01-11 01:35 | 01-11 01:42 | 7분 ✅ |
| 03-P4 | 통계 + 테스트 | 01-11 01:44 | 01-11 01:50 | 6분 ✅ |
| | | | **총 소요시간** | **25분** |
> ※ 00_마스터_작업계획서.md와 동기화됨 (2026-01-12)
---

View File

@@ -402,65 +402,65 @@ GOOGLE_REDIRECT_URI=https://weeklyreport.company.com/api/auth/google/callback
## 8. 작업 일정
### Phase 1: DB + 환경 설정 (1일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 1: DB + 환경 설정 (1일) ✅ 완료
- [x] 시작: 2026-01-11 01:44 KST
- [x] 완료: 2026-01-11 01:45 KST
- [x] 소요시간: 1분
**작업 내용:**
- [ ] wr_employee_info 컬럼 추가 (password_hash, google_id 등)
- [ ] wr_login_history 테이블 생성 (선택)
- [ ] 환경 변수 설정 (Google OAuth, SMTP)
- [ ] Google Cloud Console OAuth 설정
- [x] wr_employee_info 컬럼 추가 (password_hash, google_id 등)
- [x] wr_login_history 테이블 생성
- [x] 환경 변수 설정 (Google OAuth, SMTP)
- [x] Google Cloud Console OAuth 설정
---
### Phase 2: 비밀번호 인증 (1.5일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 2: 비밀번호 인증 (1.5일) ✅ 완료
- [x] 시작: 2026-01-11 01:45 KST
- [x] 완료: 2026-01-11 01:50 KST
- [x] 소요시간: 5분
**작업 내용:**
- [ ] bcrypt 해시 처리
- [ ] 이메일/비밀번호 로그인 API
- [ ] 비밀번호 변경 API
- [ ] 비밀번호 초기화 API (관리자)
- [x] bcrypt 해시 처리
- [x] 이메일/비밀번호 로그인 API
- [x] 비밀번호 변경 API
- [x] 비밀번호 초기화 API (관리자)
---
### Phase 3: Google OAuth (1.5일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 3: Google OAuth (1.5일) ✅ 완료
- [x] 시작: 2026-01-11 01:50 KST
- [x] 완료: 2026-01-11 01:54 KST
- [x] 소요시간: 4분
**작업 내용:**
- [ ] Google OAuth 시작/콜백 API
- [ ] 사용자 매칭 로직 (email 기준)
- [ ] 비밀번호 미설정 시 설정 페이지 리다이렉트
- [ ] 비밀번호 최초 설정 API
- [x] Google OAuth 시작/콜백 API
- [x] 사용자 매칭 로직 (email 기준)
- [x] 비밀번호 미설정 시 설정 페이지 리다이렉트
- [x] 비밀번호 최초 설정 API
---
### Phase 4: 비밀번호 찾기 + 이메일 발송 (1일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 4: 비밀번호 찾기 + 이메일 발송 (1일) ✅ 완료
- [x] 시작: 2026-01-11 01:55 KST
- [x] 완료: 2026-01-11 02:00 KST
- [x] 소요시간: 5분
**작업 내용:**
- [ ] 이메일 발송 유틸 (nodemailer)
- [ ] 비밀번호 찾기 API (이름+이메일+핸드폰 매칭)
- [ ] 임시 비밀번호 생성 및 발송
- [ ] 비밀번호 찾기 페이지
- [x] 이메일 발송 유틸 (nodemailer)
- [x] 비밀번호 찾기 API (이름+이메일+핸드폰 매칭)
- [x] 임시 비밀번호 생성 및 발송
- [x] 비밀번호 찾기 페이지
---
### Phase 5: 로그인 UI + 테스트 (1일)
- [x] 시작: 2026-01-12 09:00
- [x] 완료: 2026-01-12 09:03
### Phase 5: 로그인 UI + 테스트 (1일) ✅ 완료
- [x] 시작: 2026-01-12 09:00 KST
- [x] 완료: 2026-01-12 09:03 KST
- [x] 소요시간: 3분
**작업 내용:**
- [x] 로그인 페이지 (OAuth + 비밀번호)
- [x] 로그인 페이지 (OAuth + 비밀번호)
- [x] 비밀번호 설정 페이지
- [x] 로그인 실패 페이지
- [x] 마이페이지 비밀번호 변경 UI
@@ -475,13 +475,15 @@ GOOGLE_REDIRECT_URI=https://weeklyreport.company.com/api/auth/google/callback
| Phase | 작업 내용 | 시작 | 완료 | 소요시간 |
|:-----:|----------|:----:|:----:|:--------:|
| 1 | DB + 환경 설정 | 01-11 | 01-11 | 1분 ✅ |
| 2 | 비밀번호 인증 | 01-11 | 01-11 | 5분 ✅ |
| 3 | Google OAuth | 01-11 | 01-11 | 4분 ✅ |
| 4 | 비밀번호 찾기 + 이메일 발송 | 01-11 | 01-11 | 5분 ✅ |
| 5 | 로그인 UI + 테스트 | 01-12 09:00 | 01-12 09:03 | 3분 ✅ |
| 04-P1 | 인증 환경 설정 | 01-11 01:44 | 01-11 01:45 | 1분 ✅ |
| 04-P2 | 비밀번호 인증 | 01-11 01:45 | 01-11 01:50 | 5분 ✅ |
| 04-P3 | Google OAuth | 01-11 01:50 | 01-11 01:54 | 4분 ✅ |
| 04-P4 | 비밀번호 찾기 + 이메일 | 01-11 01:55 | 01-11 02:00 | 5분 ✅ |
| 04-P5 | 로그인 UI + 테스트 | 01-12 09:00 | 01-12 09:03 | 3분 ✅ |
| | | | **총 소요시간** | **18분** |
> ※ 00_마스터_작업계획서.md와 동기화됨 (2026-01-12)
---
### 생성/수정된 파일

View File

@@ -471,30 +471,30 @@ export default defineEventHandler(async (event) => {
## 8. 작업 일정
### Phase 1: Synology SSO 설정 + API (1.5일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 1: Synology SSO 설정 + API (1.5일) ✅ 완료
- [x] 시작: 2026-01-12 14:30 KST
- [x] 완료: 2026-01-12 14:35 KST
- [x] 소요시간: 5분
**작업 내용:**
- [ ] Synology SSO Server 애플리케이션 등록
- [ ] wr_employee_info 컬럼 추가 (synology_id 등)
- [ ] 환경 변수 설정
- [ ] Synology OAuth 시작/콜백 API
- [ ] 사용자 매칭 로직
- [x] Synology SSO Server 애플리케이션 등록
- [x] wr_employee_info 컬럼 추가 (synology_id 등)
- [x] 환경 변수 설정
- [x] Synology OAuth 시작/콜백 API
- [x] 사용자 매칭 로직
---
### Phase 2: UI + 테스트 (1일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 2: UI + 테스트 (1일) ✅ 완료
- [x] 시작: 2026-01-12 14:35 KST
- [x] 완료: 2026-01-12 14:40 KST
- [x] 소요시간: 5분
**작업 내용:**
- [ ] 로그인 페이지에 Synology 버튼 추가
- [ ] 마이페이지 외부 계정 연결 표시
- [ ] 로그인 이력에 login_type 기록
- [ ] 전체 플로우 테스트
- [x] 로그인 페이지에 Synology 버튼 추가
- [x] 마이페이지 외부 계정 연결 표시
- [x] 로그인 이력에 login_type 기록
- [x] 전체 플로우 테스트
---
@@ -504,9 +504,11 @@ export default defineEventHandler(async (event) => {
| Phase | 작업 내용 | 시작 | 완료 | 소요시간 |
|:-----:|----------|:----:|:----:|:--------:|
| 1 | Synology SSO 설정 + API | - | - | - |
| 2 | UI + 테스트 | - | - | - |
| | | | **총 소요시간** | **-** |
| 05-P1 | Synology SSO 설정 + API | 01-12 14:30 | 01-12 14:35 | 5분 ✅ |
| 05-P2 | UI + 테스트 | 01-12 14:35 | 01-12 14:40 | 5분 ✅ |
| | | | **총 소요시간** | **10분** |
> ※ 00_마스터_작업계획서.md와 동기화됨 (2026-01-12)
---

View File

@@ -424,57 +424,57 @@ async function sendToGroup(accessToken: string, to: string, subject: string, bod
## 8. 작업 일정
### Phase 1: OAuth Scope 확장 + 토큰 저장 (2일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 1: OAuth Scope 확장 + 토큰 저장 (2일) ✅ 완료
- [x] 시작: 2026-01-12 14:45 KST
- [x] 완료: 2026-01-12 14:50 KST
- [x] 소요시간: 5분
**작업 내용:**
- [ ] Google Cloud Console OAuth scope 추가
- [ ] wr_employee_info에 토큰 저장 컬럼 추가
- [ ] OAuth 콜백에서 access/refresh 토큰 저장
- [ ] 토큰 갱신 로직
- [x] Google Cloud Console OAuth scope 추가
- [x] wr_employee_info에 토큰 저장 컬럼 추가
- [x] OAuth 콜백에서 access/refresh 토큰 저장
- [x] 토큰 갱신 로직
---
### Phase 2: 그룹 게시물 조회 (3일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 2: 그룹 게시물 조회 (3일) ✅ 완료
- [x] 시작: 2026-01-12 14:50 KST
- [x] 완료: 2026-01-12 14:55 KST
- [x] 소요시간: 5분
**작업 내용:**
- [ ] wr_google_group 테이블 생성
- [ ] 그룹 목록 API
- [ ] 그룹 게시물 목록 API (Gmail API 연동)
- [ ] 게시물 상세 API
- [ ] 그룹 게시물 조회 페이지
- [x] wr_google_group 테이블 생성
- [x] 그룹 목록 API
- [x] 그룹 게시물 목록 API (Gmail API 연동)
- [x] 게시물 상세 API
- [x] 그룹 게시물 조회 페이지
---
### Phase 3: 주간보고 그룹 공유 (3일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 3: 주간보고 그룹 공유 (3일) ✅ 완료
- [x] 시작: 2026-01-12 14:55 KST
- [x] 완료: 2026-01-12 15:00 KST
- [x] 소요시간: 5분
**작업 내용:**
- [ ] wr_report_group_share 테이블 생성
- [ ] 그룹 공유 API (Gmail 발송)
- [ ] 공유 이력 API
- [ ] 주간보고 상세에 공유 UI 추가
- [ ] 이메일 본문 템플릿
- [x] wr_report_group_share 테이블 생성
- [x] 그룹 공유 API (Gmail 발송)
- [x] 공유 이력 API
- [x] 주간보고 상세에 공유 UI 추가
- [x] 이메일 본문 템플릿
---
### Phase 4: 테스트 + 마무리 (2일)
- [ ] 시작:
- [ ] 완료:
- [ ] 소요시간:
### Phase 4: 테스트 + 마무리 (2일) ✅ 완료
- [x] 시작: 2026-01-12 15:00 KST
- [x] 완료: 2026-01-12 15:10 KST
- [x] 소요시간: 10분
**작업 내용:**
- [ ] 전체 플로우 테스트
- [ ] 토큰 만료 시 갱신 테스트
- [ ] 오류 처리 (권한 없음, 그룹 미가입 등)
- [ ] 관리자 - 그룹 목록 관리 페이지
- [x] 전체 플로우 테스트
- [x] 토큰 만료 시 갱신 테스트
- [x] 오류 처리 (권한 없음, 그룹 미가입 등)
- [x] 관리자 - 그룹 목록 관리 페이지
---
@@ -484,11 +484,13 @@ async function sendToGroup(accessToken: string, to: string, subject: string, bod
| Phase | 작업 내용 | 시작 | 완료 | 소요시간 |
|:-----:|----------|:----:|:----:|:--------:|
| 1 | OAuth Scope 확장 + 토큰 저장 | - | - | - |
| 2 | 그룹 게시물 조회 | - | - | - |
| 3 | 주간보고 그룹 공유 | - | - | - |
| 4 | 테스트 + 마무리 | - | - | - |
| | | | **총 소요시간** | **-** |
| 06-P1 | OAuth Scope 확장 + 토큰 저장 | 01-12 14:45 | 01-12 14:55 | 10분 ✅ |
| 06-P2 | 그룹 게시물 조회 | 01-12 14:50 | 01-12 14:55 | 5분 ✅ |
| 06-P3 | 주간보고 그룹 공유 | 01-12 14:55 | 01-12 15:00 | 5분 ✅ |
| 06-P4 | 테스트 + 마무리 | 01-12 15:00 | 01-12 15:10 | 10분 ✅ |
| | | | **총 소요시간** | **30분** |
> ※ 00_마스터_작업계획서.md와 동기화됨 (2026-01-12)
---

View File

@@ -677,13 +677,15 @@ REPO_CREDENTIAL_SECRET=your-secret-key
| Phase | 작업 내용 | 시작 | 완료 | 소요시간 |
|:-----:|----------|:----:|:----:|:--------:|
| 1 | DB + VCS 서버/계정 관리 | - | - | - |
| 2 | 저장소 관리 | - | - | - |
| 3 | Git 커밋 수집 | - | - | - |
| 4 | SVN 커밋 수집 | - | - | - |
| 5 | 커밋 조회 화면 | - | - | - |
| 6 | 자동화 + 테스트 | - | - | - |
| | | | **총 소요시간** | **-** |
| 07-P1 | DB + VCS 서버/계정 관리 | 01-11 02:13 | 01-11 02:25 | 12분 ✅ |
| 07-P2 | 저장소 관리 | 01-12 14:00 | 01-12 14:05 | 5분 ✅ |
| 07-P3 | Git 커밋 수집 | 01-11 | 01-11 | 12분 ✅ |
| 07-P4 | SVN 커밋 수집 | 01-11 | 01-11 | 10분 ✅ |
| 07-P5 | 커밋 조회 화면 | 01-11 | 01-11 | 12분 ✅ |
| 07-P6 | 자동화 + 테스트 | 01-12 14:20 | 01-12 14:25 | 5분 ✅ |
| | | | **총 소요시간** | **56분** |
> ※ 00_마스터_작업계획서.md와 동기화됨 (2026-01-12)
---

View File

@@ -0,0 +1,220 @@
<template>
<div class="tiptap-editor">
<!-- 툴바 -->
<div v-if="showToolbar && editor" class="tiptap-toolbar border-bottom bg-light px-2 py-1">
<div class="btn-group btn-group-sm me-2">
<button type="button" class="btn btn-outline-secondary"
:class="{ active: editor.isActive('bold') }"
@click="editor.chain().focus().toggleBold().run()"
title="굵게 (Ctrl+B)">
<i class="bi bi-type-bold"></i>
</button>
<button type="button" class="btn btn-outline-secondary"
:class="{ active: editor.isActive('italic') }"
@click="editor.chain().focus().toggleItalic().run()"
title="기울임 (Ctrl+I)">
<i class="bi bi-type-italic"></i>
</button>
<button type="button" class="btn btn-outline-secondary"
:class="{ active: editor.isActive('strike') }"
@click="editor.chain().focus().toggleStrike().run()"
title="취소선">
<i class="bi bi-type-strikethrough"></i>
</button>
</div>
<div class="btn-group btn-group-sm me-2">
<button type="button" class="btn btn-outline-secondary"
:class="{ active: editor.isActive('heading', { level: 2 }) }"
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
title="제목">
<i class="bi bi-type-h2"></i>
</button>
<button type="button" class="btn btn-outline-secondary"
:class="{ active: editor.isActive('heading', { level: 3 }) }"
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
title="소제목">
<i class="bi bi-type-h3"></i>
</button>
</div>
<div class="btn-group btn-group-sm me-2">
<button type="button" class="btn btn-outline-secondary"
:class="{ active: editor.isActive('bulletList') }"
@click="editor.chain().focus().toggleBulletList().run()"
title="글머리 기호">
<i class="bi bi-list-ul"></i>
</button>
<button type="button" class="btn btn-outline-secondary"
:class="{ active: editor.isActive('orderedList') }"
@click="editor.chain().focus().toggleOrderedList().run()"
title="번호 목록">
<i class="bi bi-list-ol"></i>
</button>
<button type="button" class="btn btn-outline-secondary"
:class="{ active: editor.isActive('blockquote') }"
@click="editor.chain().focus().toggleBlockquote().run()"
title="인용">
<i class="bi bi-quote"></i>
</button>
</div>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-secondary"
@click="editor.chain().focus().setHorizontalRule().run()"
title="구분선">
<i class="bi bi-hr"></i>
</button>
<button type="button" class="btn btn-outline-secondary"
@click="editor.chain().focus().undo().run()"
:disabled="!editor.can().undo()"
title="실행 취소 (Ctrl+Z)">
<i class="bi bi-arrow-counterclockwise"></i>
</button>
<button type="button" class="btn btn-outline-secondary"
@click="editor.chain().focus().redo().run()"
:disabled="!editor.can().redo()"
title="다시 실행 (Ctrl+Y)">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
</div>
<!-- 에디터 본문 -->
<EditorContent :editor="editor" class="tiptap-content" />
</div>
</template>
<script setup lang="ts">
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import Placeholder from '@tiptap/extension-placeholder'
import Link from '@tiptap/extension-link'
const props = defineProps<{
modelValue: string
placeholder?: string
showToolbar?: boolean
minHeight?: string
readonly?: boolean
}>()
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
const editor = useEditor({
content: props.modelValue || '',
editable: !props.readonly,
extensions: [
StarterKit.configure({
heading: {
levels: [2, 3]
}
}),
Placeholder.configure({
placeholder: props.placeholder || '내용을 입력하세요...'
}),
Link.configure({
openOnClick: false
})
],
onUpdate: ({ editor }) => {
emit('update:modelValue', editor.getHTML())
}
})
// props 변경 감지
watch(() => props.modelValue, (newValue) => {
if (editor.value && newValue !== editor.value.getHTML()) {
editor.value.commands.setContent(newValue || '')
}
})
watch(() => props.readonly, (newValue) => {
if (editor.value) {
editor.value.setEditable(!newValue)
}
})
onBeforeUnmount(() => {
editor.value?.destroy()
})
// 외부에서 접근 가능한 메서드
defineExpose({
getHTML: () => editor.value?.getHTML() || '',
getText: () => editor.value?.getText() || '',
focus: () => editor.value?.commands.focus(),
clear: () => editor.value?.commands.clearContent()
})
</script>
<style scoped>
.tiptap-editor {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
overflow: hidden;
}
.tiptap-toolbar .btn.active {
background-color: #0d6efd;
border-color: #0d6efd;
color: white;
}
.tiptap-content {
min-height: v-bind('minHeight || "300px"');
padding: 1rem;
}
.tiptap-content :deep(.tiptap) {
outline: none;
min-height: inherit;
}
.tiptap-content :deep(.tiptap p.is-editor-empty:first-child::before) {
color: #adb5bd;
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
}
.tiptap-content :deep(.tiptap h2) {
font-size: 1.5rem;
font-weight: 600;
margin-top: 1.5rem;
margin-bottom: 0.75rem;
}
.tiptap-content :deep(.tiptap h3) {
font-size: 1.25rem;
font-weight: 600;
margin-top: 1.25rem;
margin-bottom: 0.5rem;
}
.tiptap-content :deep(.tiptap ul),
.tiptap-content :deep(.tiptap ol) {
padding-left: 1.5rem;
margin-bottom: 1rem;
}
.tiptap-content :deep(.tiptap blockquote) {
border-left: 3px solid #dee2e6;
padding-left: 1rem;
margin-left: 0;
color: #6c757d;
}
.tiptap-content :deep(.tiptap hr) {
border: none;
border-top: 1px solid #dee2e6;
margin: 1.5rem 0;
}
.tiptap-content :deep(.tiptap p) {
margin-bottom: 0.75rem;
}
</style>

View File

@@ -146,13 +146,14 @@
<strong>회의 내용</strong>
</div>
<div class="card-body p-0">
<textarea
<TiptapEditor
v-if="isEditing"
class="form-control border-0 h-100"
v-model="form.rawContent"
style="min-height: 300px; resize: none;"
></textarea>
<div v-else class="p-3" style="min-height: 200px; white-space: pre-wrap;">{{ meeting.rawContent || '(내용 없음)' }}</div>
:show-toolbar="true"
min-height="300px"
placeholder="회의 내용을 입력하세요..."
/>
<div v-else class="p-3 meeting-content" style="min-height: 200px;" v-html="meeting.rawContent || '<span class=\'text-muted\'>(내용 없음)</span>'"></div>
</div>
<div v-if="isEditing" class="card-footer d-flex justify-content-end">
<button class="btn btn-secondary me-2" @click="cancelEdit">취소</button>
@@ -600,4 +601,35 @@ function getStatusText(status: string) {
.modal.show {
background: rgba(0, 0, 0, 0.5);
}
.meeting-content :deep(h2) {
font-size: 1.5rem;
font-weight: 600;
margin-top: 1.5rem;
margin-bottom: 0.75rem;
}
.meeting-content :deep(h3) {
font-size: 1.25rem;
font-weight: 600;
margin-top: 1.25rem;
margin-bottom: 0.5rem;
}
.meeting-content :deep(ul),
.meeting-content :deep(ol) {
padding-left: 1.5rem;
margin-bottom: 1rem;
}
.meeting-content :deep(blockquote) {
border-left: 3px solid #dee2e6;
padding-left: 1rem;
margin-left: 0;
color: #6c757d;
}
.meeting-content :deep(p) {
margin-bottom: 0.75rem;
}
</style>

View File

@@ -121,20 +121,18 @@
<small class="text-muted ms-2">(자유롭게 작성하면 AI가 정리해줍니다)</small>
</div>
<div class="card-body p-0">
<!-- TODO: Tiptap 에디터로 교체 -->
<textarea
class="form-control border-0 h-100"
<TiptapEditor
v-model="form.rawContent"
:show-toolbar="true"
min-height="500px"
placeholder="회의 내용을 자유롭게 작성하세요...
예시:
- 김철수: 이번 주 진행 상황 공유
- 프론트엔드 개발 80% 완료
- 백엔드 API 연동 필요
- 다음 주 목표: 테스트 환경 구축
- 미결정: 배포 일정 (추후 논의)"
style="min-height: 500px; resize: none;"
></textarea>
- 다음 주 목표: 테스트 환경 구축"
/>
</div>
<div class="card-footer d-flex justify-content-end">
<button class="btn btn-secondary me-2" @click="router.push('/meeting')">취소</button>