feat(ui): apply inquiry list and modal design

Apply the supplied inquiry layout to the list and native write/detail dialogs while preserving existing API requests, author controls, attachments, comments and search. Keep legacy entry links and confirm before discarding drafts.

Validation: 98 tests and syntax checks pass; mocked Chromium desktop/mobile and six author/session visibility scenarios pass.
This commit is contained in:
root committed 2026-11-02 13:11:47 +09:00
1 parent dbf5dc3611
commit c62630e22d
19 files changed
+1362 -277

No files matched your search

+257
View File
@@ -0,0 +1,257 @@
# S/W 문의 UI 개편 작업 목록
- 작성일: 2026-11-02
- 대상: EG-BIM Q&A 목록·문의 작성·문의 상세
- 디자인 기준: `SW문의_소스패키지`와 사용자가 제공한 목록·상세 모달·작성 모달 스크린샷 3장
- 현재 단계: 프론트 UI 구현 및 모의 API 회귀 검증 완료. 원격 push·스테이징 실서버 확인·운영 배포는 대기 중.
## 1. 사용자 선택으로 확정한 범위
| 항목 | 확정 내용 |
| --- | --- |
| 작성·상세 표시 | 목록 위에 뜨는 페이지 내부 모달로 전환. 원본의 `<dialog>` 방식 기준 |
| 기존 주소 | 기존 작성·상세 주소 연결 유지 |
| 소프트웨어 | EG-BIM 우선 적용. TOVA·GAIA 탭은 준비중 표시 |
| 디자인 적용 범위 | 문의 화면·작성·상세 영역. 홈페이지 헤더·전체 메뉴·푸터는 제외 |
| 입력 중 닫기 | 입력 변경이 있으면 폐기 여부를 확인한 뒤 닫기 |
| 원격지원 | `https://939.co.kr/saman/` 활성화. 원본처럼 새 창으로 열기 |
이번 문서는 현행 한국어 Q&A를 기준으로 한다. 소스 패키지의 영문 페이지는 별도 확대 범위다. 위 선택 항목은 확정됐으며 다시 선택받을 필요가 없다.
## 2. 소스 확인 결과
| 확인 파일 | 확인한 내용 | 적용 방향 |
| --- | --- | --- |
| `SW문의_소스패키지/ko/inquiry.html` | 목록과 작성·상세 `<dialog>`가 한 문서에 있음 | 화면 구조·배치 기준 |
| `SW문의_소스패키지/assets/css/inquiry.css` | 이미지 히어로, 곡선 탭, 검색·표·버튼, 반응형, 모달 스타일 | 문의 영역 스타일을 추려 적용 |
| `SW문의_소스패키지/assets/js/inquiry.js` | 예시 게시물·공지 생성, 모달 열기/닫기, 미리보기 등록 안내 | 모달 UX를 참고하고 실제 데이터 처리는 기존 Q&A 로직으로 연결 |
| `SW문의_소스패키지/assets/js/inquiry-copy.js` | 예시 데이터·상태명·KO/EN 미리보기 문구 | 실제 서비스 문구와 상태명으로 정리 |
| `SW문의_소스패키지/assets/img/inquiry-hero.webp` | 스크린샷의 상단 배경 이미지 | 해당 자산 사용 |
| `SW문의_소스패키지/ko/{egbim,tova,gaia}/img/` | 제품 로고와 모바일용 EG-BIM 심볼 | 활성·준비중 탭에 사용 |
| `SW문의_소스패키지/assets/js/common.js`, `baron-sso-*.js`, `_include/` | 홈페이지 메뉴·헤더·푸터와 별도 SSO 흐름에 의존 | Q&A의 기존 Worker SSO 유지. 필요한 디자인 자산만 반영 |
| `egbim/app.js`, `src/index.js` | 실제 API·SSO·첨부·댓글·검색·작성자 버튼 처리 | 기능 이전의 기준 |
원본의 등록은 저장 API를 호출하지 않고 미리보기 문구만 표시한다. 상세에도 실제 첨부·댓글·수정·삭제·완료 확인 UI가 없다. 따라서 HTML/CSS 전체 복사만으로 기존 기능이 유지되지 않으며, 현재 기능을 새 화면 구조에 연결해야 한다.
`docs/관리페이지 md 파일/detail-popup-window-tasks.md`는 관리페이지의 별도 브라우저 창 작업 기록이다. 이번에 확정한 Q&A 모달 전환과는 다른 화면의 기록이다.
## 3. 목표 화면
### 목록
- 상단 `S/W 문의` 이미지 배경과 설명 문구를 스크린샷에 맞춘다.
- 반투명 배경의 제품 탭과 선택된 EG-BIM 탭의 곡선·로고·색상을 반영한다.
- 홈페이지 헤더를 제외하므로 원본의 헤더 높이 보정용 상단 여백을 함께 조정한다.
- 문의 내역 제목, 원격지원 버튼, 둥근 분류 필터·검색창, 표·페이지 이동·문의등록 버튼을 맞춘다.
- TOVA·GAIA는 준비중임을 표시하고 실제 목록 조회·등록 대상으로 선택되지 않도록 한다.
- 실제 문의 데이터와 현행 검색·상태 표시를 사용한다. 예시 공지·예시 고객 데이터·미리보기 문구는 배포 화면에 넣지 않는다.
### 작성 모달
- 원본의 중앙 모달, 제목과 구분선, 닫기 버튼, 데스크톱 2열·모바일 1열 폼 구성을 반영한다.
- 서비스는 EG-BIM으로 고정한다. TOVA·GAIA로 잘못 등록할 수 없어야 한다.
- 회사·부서·작성자·이메일 칸은 현재 SSO 정보로 자동 표시하며 읽기 전용으로 구성한다.
- 문의 구분·제목·내용·첨부파일에 기존 비밀글 체크 UI를 더한다.
- 작성자 정보의 저장 원본은 기존 Worker의 세션·조직 조회 결과를 유지한다.
- 등록 중 상태, 필수값 오류, 첨부 오류와 API 오류를 모달 안에서 표시한다.
### 상세·수정 모달
- 원본처럼 제목·닫기 버튼·구분선·작성 정보·본문·하단 버튼 순서로 구성한다.
- 본문 아래에 첨부 목록·다운로드, 완료 안내·확인, 댓글·이미지, 댓글 입력 영역을 추가한다.
- 작성자에게만 게시글 수정·삭제 버튼을 표시한다. 댓글 버튼은 각 댓글 작성자에게만 표시한다.
- 수정은 상세 모달의 편집 모드로 연결하고, 저장·취소 후 상세 보기로 복귀한다.
- 댓글과 첨부로 내용이 길어져도 모달 내부에서 스크롤하고 목록의 위치는 유지한다.
기본 모달 크기는 원본의 최대 폭 780px·최대 높이 88dvh·둥근 모서리·어두운 배경을 출발점으로 한다. 상세에 추가되는 기능은 이 구조 안에서 배치하고, 작은 화면의 실제 사용성을 검증한다.
## 4. 반드시 유지할 기능
| 기능 | 개편 후 유지 기준 |
| --- | --- |
| SSO 로그인 | 기존 `/auth/*`, Q&A 세션 쿠키, 로그인 후 복귀 흐름 유지 |
| 회사·부서 | Worker의 `org-context` 매핑 유지. 폼의 수동 입력으로 대체하지 않음 |
| 목록 검색 | fuzzy 검색·초성·띄어쓰기·오타 허용, 정확한 제목 우선 정렬 유지 |
| 목록 수집·필터 | 100건 이후 페이지 수집, 분류·내 글 필터, 로딩·부분 실패 표시 유지 |
| 문의 작성 | 기존 구분·제목·내용 검증과 실제 저장 흐름 유지 |
| 문의 첨부 | 다중 첨부·파일당 30MB, 기존 multipart 업로드와 상세 다운로드 유지 |
| 비밀글 | 작성·수정 체크, 목록의 잠금 표시와 기존 조회 정책 유지 |
| 문의 수정·삭제 | 기존 API 연결과 작성자 전용 버튼 표시 유지 |
| 댓글 | 실제 댓글 조회·등록·수정·삭제와 작성자별 버튼 표시 유지 |
| 댓글 이미지 | 다중 이미지·파일당 30MB, 이미지 형식 검증·미리보기·썸네일 유지 |
| 처리 상태 | 현행 신규·접수·진행중·완료·보류 의미 유지 |
| 완료 확인 | 작성자에게 표시하는 처리 완료 안내·완료 확인·확인 후 상태 갱신 유지 |
| 기존 데이터 | 저장된 작성자·회사·부서·첨부·댓글을 그대로 사용. UI 작업으로 과거 데이터 재매핑하지 않음 |
원본의 상태 예시는 `문의접수/답변완료/검토중/패치예정`이고 현행 상태와 다르다. 배지의 디자인을 적용하면서 현행 상태 의미를 임의로 치환하거나 보류 상태를 누락하지 않는다.
## 5. 화면 전환·주소 연결안
내부 상태 URL은 아래 형태를 구현 기준안으로 사용한다. 기존 주소를 통해 들어오는 사용자도 같은 모달을 볼 수 있도록 연결한다.
| 진입 | 연결할 화면 |
| --- | --- |
| `/egbim/`, `/egbim/index.html` | 새 목록 |
| 목록의 문의등록 | 목록 위 작성 모달. 예: `/egbim/?view=write` |
| 목록 제목 클릭 | 해당 글의 상세 모달. 예: `/egbim/?view=detail&id=<글 UUID>` |
| `/egbim/write.html` | 목록과 작성 모달로 연결 |
| `/egbim/detail.html?id=<글 UUID>` | 목록과 해당 글의 상세 모달로 연결 |
```mermaid
flowchart LR
A[문의 목록] -->|문의등록| B[작성 모달]
A -->|글 제목| C[상세 모달]
B -->|저장 성공| C
C -->|작성자 수정| D[상세 편집 모드]
D -->|저장 또는 취소| C
B -->|닫기| A
C -->|닫기 또는 삭제 완료| A
```
- 모달 URL로 직접 접속·새로고침해도 해당 화면을 복원한다.
- 브라우저 뒤로·앞으로 이동과 모달 열기·닫기를 일치시킨다.
- 모달을 닫을 때 목록의 검색어·분류·내 글 조건·페이지·스크롤을 보존한다.
- 등록 성공 시 작성 모달에서 해당 글의 상세 모달로 전환하고 목록도 갱신한다.
- 수정·댓글 변경·완료 확인 후 상세와 목록의 관련 상태를 동기화한다.
- 삭제 성공 시 목록으로 복귀하고, 마지막 페이지가 비면 유효한 페이지로 보정한다.
- 세션 만료 시 기존 로그인 흐름을 사용하고 로그인 후 원래 작성·상세 진입 주소를 복원한다.
## 6. 실제 API 연결 유지
아래는 브라우저가 사용하는 현재 Worker 경로다. 원본 목업의 폼 이름을 그대로 전송하지 않고 기존 요청 형식을 연결한다.
| 동작 | 기존 경로·방식 | 유지할 처리 |
| --- | --- | --- |
| 로그인 사용자 확인 | `GET /auth/session` | 인증된 사용자·조직 정보 표시 |
| 목록 | `GET /api/feedbacks?limit=100&page=...` | 페이지 수집·검색·필터 |
| 작성 | `POST /api/feedbacks` | 텍스트는 JSON, 첨부는 multipart의 `images` 필드 |
| 상세 | `GET /api/feedbacks/{id}` | 실제 본문·작성자·첨부·상태 |
| 수정·삭제 | `PATCH/DELETE /api/feedbacks/{id}` | 기존 수정 가능 필드와 저장 형식 |
| 댓글 조회·등록 | `GET/POST /api/feedbacks/{id}/comments` | 텍스트 또는 multipart의 `attachments` 이미지 필드 |
| 댓글 수정·삭제 | `PATCH/DELETE /api/feedbacks/{id}/comments/{commentId}` | 실제 댓글 ID와 작성자 연결 |
| 완료 확인 | `POST /api/feedbacks/{id}/completion-confirmation` | 기존 완료 확인 처리 |
이번 범위에서 새 DB나 별도 업로드 API를 도입할 필요는 없다. TOVA·GAIA를 준비중으로 두므로 제품별 API 키·프로젝트·채널 설정 확대도 이번 작업의 선행 조건이 아니다.
## 7. 단계별 작업 목록
### P0. 분석·범위 확정 — 완료
- [x] 원본 HTML·CSS·JS·이미지 목록 확인.
- [x] 원본이 실제 저장 기능이 없는 디자인 목업임을 확인.
- [x] 현행 목록·작성·상세 및 Worker API의 기능 대조.
- [x] 사용자 제공 스크린샷 3장을 디자인 기준으로 반영.
- [x] 모달 전환·기존 주소 유지·EG-BIM 우선·헤더/푸터 제외 결정.
- [x] 입력 변경 확인 후 닫기·원본 원격지원 주소 활성화 결정.
### P1. 스타일·자산 적용
- [x] `UI-01` 문의 전용 스타일 범위를 정하고 기존 공통 버튼·폼·레이아웃과의 충돌을 정리한다.
- [x] `UI-02` 히어로 이미지·제품 로고·필요한 폰트만 배포 자산으로 정리하고 상대 경로를 맞춘다.
- [x] `UI-03` 목록의 히어로·곡선 탭·필터·검색창·표·페이지 이동·등록 버튼을 원본 기준으로 구성한다.
- [x] `UI-04` TOVA·GAIA 준비중 표기와 접근 가능한 비활성 탭 처리를 추가한다.
- [x] `UI-05` 원격지원 링크를 확정 주소로 연결하고 새 창 안내와 `noopener noreferrer`를 유지한다.
- [x] `UI-06` 실제 데이터가 없는 예시 공지·예시 댓글 수·미리보기 문구를 제거한다. 댓글 수 배지가 필요하면 실제 API 제공값을 확인하고 연결하며, 행마다 댓글 API를 추가 호출하는 방식은 피한다.
### P2. 모달·주소·상태 구성
- [x] `UI-07` 목록·작성·상세의 데이터 처리와 화면 표시를 분리하고, 모달을 다시 열어도 이벤트가 중복 등록되지 않도록 구성한다.
- [x] `UI-08` 작성·상세 모달의 열기·닫기·전환과 목록 상태 보존을 구현한다.
- [x] `UI-09` URL 직접 진입·새로고침·뒤로/앞으로 이동을 연결한다.
- [x] `UI-10` 기존 `write.html`, `detail.html?id=...` 주소를 새 모달 진입으로 연결하고 로그인 복귀도 확인한다.
- [x] `UI-11` 입력 변경 확인을 닫기 X·취소·ESC·배경 클릭·뒤로 이동에 동일하게 적용한다. 확인을 취소하면 입력과 선택한 파일을 보존한다.
- [x] `UI-12` 상세 안의 미등록 댓글·편집 중 댓글도 닫기 시 입력 변경 확인 대상에 포함한다.
- [x] `UI-13` 모달을 닫거나 다른 글을 열었을 때 이전 글의 늦은 응답이 새 화면을 덮지 않도록 처리한다.
- [x] `UI-14` 포커스 진입·모달 밖 이동 제한·닫은 뒤 포커스 복귀·배경 스크롤 잠금을 구현한다. 삭제로 원래 행이 사라지면 목록의 적절한 위치로 포커스를 돌린다.
### P3. 실제 작성·상세 기능 이전
- [x] `UI-15` 현재 SSO 확인과 회사·부서 매핑을 작성 모달의 읽기 전용 정보에 연결한다.
- [x] `UI-16` 문의 구분·제목·내용·비밀글·첨부파일과 기존 검증·등록 중 상태를 연결한다.
- [x] `UI-17` 등록 성공 시 전체 페이지 이동 대신 상세 모달을 열고 목록을 갱신한다. 실패 시 입력과 파일 선택 상태를 유지한다.
- [x] `UI-18` 상세 본문·작성 정보·한국 시간 등록일·비밀글 표시·첨부 목록 및 다운로드를 연결한다.
- [x] `UI-19` 작성자 UUID가 일치할 때만 게시글 수정·삭제 버튼을 표시하고 수정 폼·저장·취소·삭제 완료 흐름을 연결한다.
- [x] `UI-20` 댓글 조회·등록·수정·삭제, 댓글 이미지 첨부·미리보기·썸네일을 연결한다.
- [x] `UI-21` 게시글 작성자와 댓글 작성자를 각각 판단한다. 다른 사람의 게시글 안에 있는 본인 댓글에는 댓글 버튼을 표시한다.
- [x] `UI-22` 완료 안내·완료 확인 버튼·확인 완료 표시와 목록 상태 갱신을 연결한다.
- [x] `UI-23` 모달 안의 성공·오류 안내를 표시하고, 반복 열기·닫기 때 오류·수정 상태·이전 글의 댓글·이미지가 남지 않도록 초기화한다.
- [x] `UI-24` 실제로 폐기된 첨부·댓글 이미지 미리보기의 임시 URL을 정리한다.
### P4. 반응형·회귀 검증
- [x] `UI-25` 데스크톱 목록·작성·상세 화면을 제공 스크린샷과 비교한다.
- [x] `UI-26` 모바일에서 제품 탭·가로 스크롤 표·1열 작성 폼·모달 내부 스크롤·하단 버튼을 확인한다.
- [x] `UI-27` 작성자·타인·동명이인·작성자 ID 없음·세션 확인 중/만료/실패 조건에서 게시글과 댓글 버튼의 실제 가시성을 검사한다. 새 `.inquiry-button`에도 `hidden` 속성이 적용되도록 한다.
- [x] `UI-28` 오타·초성·띄어쓰기 검색, 100건 이후 데이터, 분류·내 글·페이지 이동 회귀를 검사한다.
- [x] `UI-29` JSON 작성·다중 첨부 작성·비밀글·첨부 다운로드·댓글 이미지·수정/삭제·완료 확인을 모의 API와 브라우저에서 검사한다.
- [x] `UI-30` 반복 모달 열기·등록/저장 중 상태·닫기 확인 취소·오류 후 재시도·다른 글의 늦은 응답을 검사한다.
- [x] `UI-31` 기존 작성자·댓글·검색·Worker 테스트를 새 화면 진입에 맞춰 유지하고 `npm run check`, `npm test`를 통과시킨다.
### P5. 자산 배포·문서 정리
- [x] `UI-32` `scripts/upload-r2.sh`가 새 CSS·JS·이미지·로고를 업로드하도록 경로를 확인하고 WebP의 MIME을 `image/webp`로 지정한다.
- [x] `UI-33` 기존 주소 연결 파일도 배포 목록에 유지하고 CSS·JS 버전을 갱신한다.
- [x] `UI-34` 현행 3개 페이지 전이 설명을 목록·모달 흐름과 실제 multipart API 기준으로 갱신한다. 구형 presign·로컬 저장 전용 설명을 구현 기준으로 사용하지 않는다.
- [x] `UI-35` 변경 커밋과 검증 결과를 정리한다. 원격 push는 기존 요청대로 사용자가 진행한다.
- [ ] `UI-36` 스테이징 자동 배포 성공 후 실제 로그인·조직 정보·작성·첨부·댓글·비밀글·기존 주소를 확인한다.
- [ ] `UI-37` 운영 적용 시 기존 수동 운영 배포 절차를 따른다. UI 개편을 이유로 운영 API 설정이나 SSO client/callback을 임의로 교체하지 않는다.
## 8. 예상 변경 파일
| 파일·영역 | 작업 |
| --- | --- |
| `egbim/index.html` | 새 목록과 작성·상세 모달의 화면 구조 |
| `egbim/write.html`, `egbim/detail.html` | 기존 주소의 진입 연결 |
| `egbim/app.js` 및 필요 시 문의 전용 JS | 기존 기능 연결·모달 수명주기·URL 상태 처리 |
| `assets/styles.css` 및 문의 전용 CSS | 원본 디자인·기능 확장 영역·반응형·숨김 처리 |
| `assets/` 아래 문의용 자산 | 히어로·로고·필요한 폰트 |
| `src/index.js` | 변경 없음. 기존 로그인 보호·API·SSO·조직 처리 유지 |
| `scripts/upload-r2.sh` | 새 자산 경로·MIME 확인 |
| `tests/page-*.test.js`, 필요 시 Worker 라우팅 검사 | 목록·모달·기능·기존 주소 회귀 |
| `README.md`, `docs/qa-login-to-save-transition.md` | 화면 구조·진입 주소·현재 API 흐름 설명 |
원본 패키지는 비교 기준으로 보존한다. 필요한 자산을 선별해 서비스 경로에 반영하며 패키지 전체와 홈페이지 공통 인증 스크립트를 그대로 배포하지 않는다.
## 9. 완료 기준
- [x] 제공한 세 화면의 배경·탭·목록·폼·모달 디자인을 반영했다.
- [x] 작성·상세를 목록 위 모달로 열며 기존 URL로도 같은 글·작성 화면에 진입한다.
- [x] 회사·부서가 SSO 조직 정보로 표시되고 기존 작성자 식별자를 유지한다.
- [x] 첨부·댓글·비밀글·수정/삭제·완료 확인이 실제 기존 API로 동작한다.
- [x] 게시글·댓글 버튼이 실제 작성자에게만 보이며 로그인 확인 전에도 잘못 노출되지 않는다.
- [x] fuzzy 검색과 전체 페이지 수집·필터·페이지 이동을 유지한다.
- [x] 입력 변경 후 닫기는 확인을 거치며, 닫기 취소 시 입력과 파일을 보존한다.
- [x] 모달을 닫아도 목록 조건·위치가 유지되고 새 글·수정·삭제 결과가 반영된다.
- [x] TOVA·GAIA 준비중 표시와 확정된 원격지원 링크가 동작한다.
- [x] 예시 데이터·미리보기 전용 문구가 남지 않고 모바일·키보드 사용과 회귀 검증을 통과했다.
## 10. 구현·검증 결과
변경은 정적 HTML/CSS/JS와 자산 업로드 MIME·검증·문서에 한정했다. Worker·API 계약·DB·SSO 설정·기존 배포 트리거는 변경하지 않았다. 기존 주소는 프론트의 `assets/inquiry/entry.js`로 연결하며, 새 화면은 기존 API를 그대로 호출한다. 원본 패키지 전체를 서비스 자산으로 복사하지 않았다.
- `npm run check`, `npm test`: 기존 94개와 구형 주소 연결 4개, 총 98개 테스트 통과.
- R2 업로드 검사: 새 하위 경로의 진입 JS와 WebP 이미지가 기존 배포 경로에 포함되며 MIME은 각각 `application/javascript`, `image/webp`다.
- Chromium 데스크톱 1900×1000, 모바일 390×844에서 제공 스크린샷과 비교했다. 모바일 표는 기존 열을 유지한 가로 스크롤, 작성 폼은 1열, 모달은 내부 스크롤이다.
- 모의 API 브라우저 검사: fuzzy 검색·페이지 이동·목록 조건 보존·JSON/다중 첨부 작성·비밀글·첨부 링크·게시글 수정/삭제·댓글 수정/삭제/이미지 등록·완료 확인 통과.
- 모달 검사: 기존 URL·직접 진입·새로고침·뒤로/앞으로·X/ESC/배경 닫기·입력 폐기 확인·파일 유지·등록 실패 재시도·등록 중 닫기 방지·반복 열기·이전 글의 늦은 응답·키보드 모달 밖 이동 제한 통과.
- 권한 검사: 작성자·타인·동명이인/ID 없음·만료/오래된 저장값·세션 조회 실패·세션 응답 대기의 6조건에서 게시글/댓글 버튼 실제 가시성을 검사했다.
- 100건 이후 페이지 수집·분류·내 글·초성·오타·띄어쓰기와 기존 Worker/API 회귀는 기본 테스트에서 확인했다.
위 체크는 로컬 및 모의 API 기준이다. 실제 SSO 로그인·관리페이지 DB 저장·실제 파일 다운로드/업로드 확인은 `UI-36`에 남겨 두었으며, `UI-37` 운영 배포도 아직 수행하지 않았다. 사용자가 push한 뒤 스테이징 배포 결과를 확인한다.
### 브라우저 회귀 검증
`tests/browser/inquiry-ui.mjs`는 Playwright와 Chromium이 있는 별도 검증 환경에서 실행한다. 앱 의존성에는 브라우저 패키지를 추가하지 않았다. 모든 네트워크 요청을 모의 API 또는 로컬 정적 파일로 처리하고 외부 요청은 차단한다.
```bash
# Playwright와 해당 Chromium이 이미 설치된 환경
npm run test:ui
# 검증 도구가 저장소 밖에 설치되어 있는 경우
PLAYWRIGHT_MODULE=/absolute/path/to/playwright-core \
CHROMIUM_EXECUTABLE_PATH=/absolute/path/to/chromium \
npm run test:ui
```
기본 스크린샷 출력 위치는 임시 디렉토리의 `qa-inquiry-ui/`다. `UI_SCREENSHOT_DIR`로 바꿀 수 있으며, 데스크톱·모바일 목록/작성/상세 6장을 생성한다. 실제 사용자 세션·API Key는 필요하지 않다.
+64 -146
View File
@@ -1,165 +1,83 @@
# EG-BIM Q&A 로그인부터 글 저장까지 전이맵
# EG-BIM Q&A 로그인·화면·저장 흐름
기준일: 2026-09-21
대상: `qa-test.baroncs.co.kr` / EG-BIM Q&A
구성: 목록(`index.html`) · 작성(`write.html`) · 상세(`detail.html`)
기준일: 2026-11-02. 스테이징 `qa-test.baroncs.co.kr`, 운영 `qa.baroncs.co.kr`의 현재 구조를 설명한다. 이번 UI 개편은 프론트에 한정하며 Worker API·DB·SSO 설정을 변경하지 않는다.
## 1. 전체 흐름
## 로그인과 화면 진입
홈페이지 로그인 정보를 Q&A 사이트로 직접 전달하지 않는다. 홈페이지의 Q&A 링크는 별도 호스트로 이동하고, Q&A는 같은 BRSW OIDC SSO에 직접 인증을 요청한다. 이미 중앙 SSO 세션이 있으면 사용자는 로그인 화면을 보지 않고 Q&A 세션을 발급받는다.
Q&A는 기존 Worker의 OIDC Authorization Code + PKCE와 HttpOnly 세션을 사용한다. 홈페이지에서 토큰을 전달하지 않는다. 보호된 페이지에 세션 없이 접근하면 Worker가 `/auth/login?return_url=...`로 보내며, 인증 후 원래 경로와 쿼리로 돌아온다. 작성자와 회사·부서는 `GET /auth/session`의 검증된 정보로 표시한다.
| 주소·사용자 동작 | 화면 |
| --- | --- |
| `/egbim/`, `/egbim/index.html` | 문의 목록 |
| 문의등록 / `?view=write` | 목록 위 작성 모달 |
| 글 제목 / `?view=detail&id=<UUID>` | 목록 위 해당 글 상세 모달 |
| `/egbim/write.html` | 프론트에서 `/egbim/?view=write`로 replace 이동 |
| `/egbim/detail.html?id=<UUID>` | 프론트에서 `/egbim/?view=detail&id=<UUID>`로 replace 이동 |
구형 주소도 Worker의 기존 로그인 보호를 거친다. 새로고침과 직접 링크는 URL에 지정한 모달을 복원한다. 모달의 열기·닫기는 브라우저 뒤로·앞으로 이동과 연결한다. 목록 위에서 열었을 때 검색어·분류·내 글·페이지·스크롤을 유지한다. 작성·수정·댓글 초안이나 파일 선택 변경이 있으면 닫기 전에 폐기를 확인한다.
```mermaid
flowchart TD
A[바론 홈페이지<br/>EG-BIM Q&A 링크] --> B[Q&A 목록 진입<br/>https://qa-test.baroncs.co.kr/egbim/]
B --> C{Q&A 세션 쿠키<br/>baron_qa_session_v2 유효?}
C -- 예 --> G[정적 목록 페이지 표시]
C -- 아니오 --> D[302 /auth/login<br/>return_url 보관]
D --> E[Worker가 PKCE 생성<br/>state · verifier · nonce]
E --> F[BARON-SSO OIDC<br/>sso.hmac.kr/oidc]
F --> F1{중앙 SSO 세션 존재?}
F1 -- 아니오 --> F2[SSO 로그인 화면]
F2 --> F3[최초 동의 화면<br/>필요 시 1회]
F1 -- 예 --> H[승인 코드 발급]
F3 --> H
H --> I[/auth/callback?code&state]
I --> J[Worker가 state 검증<br/>PKCE code_verifier 검증]
J --> K[Token Endpoint 호출<br/>Public Client · client_secret 없음]
K --> L[UserInfo 조회 및 claim 정규화]
L --> M[baron_qa_session_v2 발급<br/>HttpOnly · Secure · SameSite=Lax]
M --> G
G --> N[문의 등록 버튼]
N --> O[작성 페이지<br/>write.html]
O --> P[GET /auth/session<br/>작성자·tenant 표시]
P --> Q{userUuid와 tenantId 존재?}
Q -- 아니오 --> R[제출 차단<br/>다시 로그인 안내]
Q -- 예 --> S[카테고리·제목·내용·비밀글 입력]
S --> T[등록 버튼 클릭]
T --> U[브라우저 UUID 생성<br/>feedbackId]
U --> V{첨부파일 존재?}
V -- 아니오 --> Y[첨부 메타데이터 빈 배열]
V -- 예 --> W[POST presign 요청]
W --> X[qa_cdn presigned URL로<br/>파일 PUT 업로드]
X --> Y[첨부파일 메타데이터 확정]
Y --> Z[feedback envelope 생성]
Z --> AA{apiBaseUrl 설정 여부}
AA -- 비어 있음<br/>현재 테스트 모드 --> AB[localStorage에 임시 저장]
AA -- 설정됨<br/>실서비스 모드 --> AC[POST /v1/qa/feedbacks<br/>feedback.hmac.kr API]
AB --> AD[등록 완료 toast<br/>상세 페이지 이동]
AC --> AE{API 성공?}
AE -- 예 --> AD
AE -- 아니오 --> AF[오류 표시<br/>재시도 가능]
A[Q&A 주소 진입] --> B{Q&A 세션 유효?}
B -->|아니오| C[기존 OIDC SSO 로그인]
C --> D[callback 검증·세션 발급]
D --> A
B -->|예| E[목록 + URL에 지정한 모달]
E --> F[GET /auth/session]
F --> G[작성자·회사·부서 표시]
G --> H[작성 모달 입력·검증]
H --> I{첨부파일 있음?}
I -->|아니오| J[POST /api/feedbacks JSON]
I -->|예| K[POST /api/feedbacks multipart images]
J --> L[기존 Worker: 세션·조직 확인 후 ABC 전달]
K --> L
L --> M{저장 성공?}
M -->|예| N[목록 갱신·해당 글 상세 모달]
M -->|아니오| O[모달 오류 표시·입력과 파일 유지]
```
## 2. 상태 전이표
## 작성·첨부 저장
| 상태 | 화면/주소 | 진입 조건 | 주요 처리 | 다음 상태 |
|---|---|---|---|---|
| `S0` | 홈페이지 | Q&A 메뉴 클릭 | 외부 Q&A URL로 이동 | `S1` |
| `S1` | `/egbim/` | Q&A 세션 유효 | R2의 목록 HTML 제공 | `S5` |
| `S2` | `/auth/login` | 세션 없음 | PKCE 상태 생성, SSO로 redirect | `S3` |
| `S3` | `sso.hmac.kr/oidc` | SSO 세션 없음 | 최초 로그인 및 동의 | `S4` |
| `S4` | `/auth/callback` | `code`, `state` 수신 | state/PKCE 검증, token/userinfo 조회 | `S5` |
| `S5` | 목록 또는 작성 페이지 | `baron_qa_session_v2` 유효 | `/auth/session`에서 사용자 확인 | `S6` |
| `S6` | `write.html` | `userUuid`, `tenantId` 확인 | 작성 폼 입력 대기 | `S7` |
| `S7` | 작성 페이지 | 등록 클릭 | `feedbackId` UUID 생성 및 입력 검증 | `S8` |
| `S8` | 작성 페이지 | 첨부 있음 | presign → `qa_cdn` 업로드 | `S9` |
| `S9` | 작성 페이지 | payload 준비 | feedback/support ticket envelope 생성 | `S10` |
| `S10` | API 또는 localStorage | `apiBaseUrl` 상태에 따라 분기 | 실제 DB 저장 또는 테스트 저장 | `S11` |
| `S11` | `detail.html?id=...` | 저장 성공 | 등록 완료 및 상세 이동 | 종료 |
브라우저는 기존 UUID v7 생성 방식으로 `feedbackId`를 만들고 아래 필드를 보낸다. 회사·부서·작성자·이메일 칸은 읽기 전용이며, 서버가 기존 세션·조직 조회 결과에서 작성자 정보를 결정한다.
## 3. 화면별 사용자 경험
### A. 홈페이지
- EG-BIM 메뉴의 Q&A 링크만 제공한다.
- 로그인 정보, 쿠키, token, `SESSION_SECRET`을 Q&A로 전달하지 않는다.
- 링크 대상:
```text
https://qa-test.baroncs.co.kr/egbim/
```json
{
"feedbackId": "<UUID v7>",
"title": "문의 제목",
"contents": "문의 내용",
"Category": "GENERAL_QNA",
"is_secret": "false"
}
```
### B. 중앙 SSO
- 첨부가 없으면 JSON으로 전송한다.
- 첨부가 있으면 같은 필드와 반복 `images` 파일 필드를 `FormData`에 넣는다. `Content-Type`의 boundary는 브라우저가 지정한다.
- 기존 Worker가 세션과 첨부를 검증하고 API Key 및 서버 작성자 정보를 붙여 ABC에 전달한다. 파일당 제한은 30MB다.
- 브라우저가 presign API나 R2 PUT을 직접 호출하지 않는다. API base가 빈 문자열이면 현재 호스트의 Worker를 사용한다.
- API 성공 후 기존 localStorage 캐시를 갱신하고 상세 모달을 연다. 실패를 로컬 저장 성공으로 처리하지 않는다.
- 등록 중에는 중복 클릭과 모달 닫기를 막고, 실패하면 입력·파일을 유지하며 재시도할 수 있게 한다.
- Q&A RP는 `https://sso.hmac.kr/oidc`를 사용한다.
- 최초 사용자는 SSO 로그인 및 동의 화면을 볼 수 있다.
- 같은 중앙 SSO 세션이 있으면 다음 접근부터 로그인 화면 없이 authorization code가 발급된다.
- Q&A RP는 PKCE Public Client이므로 client secret을 보내지 않는다.
## 상세·수정·댓글
### C. Q&A 목록
| 동작 | 기존 Worker 요청 |
| --- | --- |
| 목록 | `GET /api/feedbacks?limit=100&page=...` |
| 상세 | `GET /api/feedbacks/{id}` |
| 수정 | `PATCH /api/feedbacks/{id}`: `title`, `contents`, `Category`, `is_secret` |
| 삭제 | `DELETE /api/feedbacks/{id}` |
| 댓글 조회·등록 | `GET/POST /api/feedbacks/{id}/comments` |
| 댓글 수정·삭제 | `PATCH/DELETE /api/feedbacks/{id}/comments/{commentId}` |
| 완료 확인 | `POST /api/feedbacks/{id}/completion-confirmation` |
- Worker가 `baron_qa_session_v2`를 검사한다.
- 세션이 없으면 `/auth/login?return_url=...`로 이동한다.
- 세션이 있으면 R2의 `egbim/index.html`을 제공한다.
댓글 텍스트는 JSON `content`, 이미지 첨부 시에는 multipart `content`와 반복 `attachments` 필드를 사용한다. 기존 다중 이미지·형식·30MB 제한과 미리보기를 유지한다. 폐기된 미리보기와 모달을 닫을 때 임시 Blob URL을 해제한다.
### D. Q&A 작성
수정·삭제 버튼은 검증된 기본 requester UUID가 해당 게시글·댓글의 작성자 ID와 같을 때만 표시한다. 이름·이메일 일치로 권한을 부여하지 않는다. 로그인 확인 중·세션 만료·확인 실패·작성자 ID 없음 상태에서는 숨긴다. 게시글과 댓글의 소유자는 각각 판단한다.
- 브라우저가 `GET /auth/session`을 호출한다.
- 다음 작성자 정보를 화면과 payload에 사용한다.
수정·완료 확인은 목록에도 반영하고, 삭제 후에는 목록으로 돌아간다. 늦게 도착한 이전 상세 응답은 닫힌 모달의 분리된 DOM에서 처리해 현재 모달을 덮지 않는다. 오류는 모달 안에 표시한다.
```text
userUuid
ssoSubject / requesterId
tenantId / requesterTenantId
tenantIds
scope
roles
email
name
department
phone
```
## 확인과 배포
- `requesterId` 또는 `requesterTenantId`가 없으면 제출을 차단한다.
### E. 글 저장
- 브라우저에서 `feedbackId` UUID를 1회 생성한다.
- 같은 UUID를 다음 식별자로 재사용한다.
```text
feedback.id
feedback.source_record_id
support_tickets.idempotency_key
```
- 첨부파일은 `qa_cdn`에 저장하고, DB에는 bucket/key 및 파일 메타데이터를 기록한다.
- 실제 운영에서는 feedback API가 서버 측에서 Q&A 세션 또는 SSO token을 검증해야 한다.
## 4. 현재 테스트 모드와 운영 모드
현재 `egbim/config.js`는 다음과 같이 `apiBaseUrl`이 비어 있다.
```js
apiBaseUrl: ''
```
따라서 현재 등록 결과는 다음과 같이 동작한다.
```text
글 작성 → UUID 생성 → localStorage 저장 → 상세 페이지 이동
```
운영 API 주소를 설정하면 다음 흐름으로 변경된다.
```text
첨부 presign 요청
→ qa_cdn 직접 업로드
→ feedback.hmac.kr/v1/qa/feedbacks 호출
→ feedbacks/support_tickets 트랜잭션 저장
→ 상세 페이지 이동
```
## 5. 주요 실패 전이
| 실패 지점 | 사용자 화면 | 원인/확인 항목 |
|---|---|---|
| SSO 로그인 전 | 로그인 화면 | 중앙 SSO 세션 없음 또는 다른 SSO 환경 |
| callback | `invalid_oauth_state` | OAuth cookie 만료, 호스트 변경, state 불일치 |
| token 교환 | `oauth_token_exchange_failed` | issuer/endpoint/client ID/PKCE 설정 불일치 |
| 세션 확인 | `authenticated:false` | `baron_qa_session_v2` 없음·만료·`SESSION_SECRET` 불일치 |
| 작성자 확인 | UUID/tenant 오류 | SSO scope 또는 claim mapping 누락 |
| 첨부 업로드 | 업로드 실패 | presign API 또는 `qa_cdn` 권한 오류 |
| DB 저장 | API 요청 오류 | `apiBaseUrl`, CORS, API 인증, DB envelope 규격 오류 |
- 기본 검사: `npm run check`, `npm test`.
- 별도 브라우저 검사: `npm run test:ui`. Playwright/Chromium 준비와 검증 범위는 [UI 작업 목록](qa-inquiry-ui-redesign-tasks.md#브라우저-회귀-검증)을 참고한다. 모든 요청을 모의 처리하며 실제 서버 데이터를 변경하지 않는다.
- 원격 push는 사용자가 진행한다. 스테이징은 기존 main push 자동 배포, 운영은 기존 수동 배포 절차를 사용한다.
- 실제 로그인·관리페이지 저장 연동은 스테이징 배포 후 확인한다. 모의 API 검증만으로 실서버 검증 완료로 표시하지 않는다.