feat(project): 3단계 구조 프론트엔드 — 세션 관리·스코프·하위업무·멀티호밍·이미지
지난 커밋(07f4d0f)의 백엔드 위에 화면을 얹는다.
- 세션(기존 "단계") 문구 통일 + 프로젝트 스코프("이 프로젝트")일 때 보드
컬럼에 관리 UI 추가: 이름변경/완료토글/삭제(만든 사람만)/"+ 세션 추가"/
드래그 재정렬(카드 드래그와 같은 네이티브 HTML5 DnD, 기존
PUT stages/order 재사용).
- 프로젝트 화면 툴바에 "이 프로젝트"/"전체 프로젝트" 스코프 토글 추가.
기본은 아사나처럼 지금 프로젝트만(달력/타임라인/보드/리스트 4개 뷰
전부), "전체"는 예전처럼 접근 가능한 프로젝트를 다 합쳐서 보여준다.
서버가 tasks 각 행에 context_project_id(어느 프로젝트를 보다가 담겼는지)
를 실어주고 클라이언트가 그걸로 순수 필터링(재조회 없음).
- 하위 업무 인라인 UI — 업무 모달 안에 체크박스·제목·담당자·마감일이 한
줄인 하위업무 목록(변경 즉시 저장), "+ 하위 업무 추가". 카드에는
"완료/전체" 진행 배지만 표시. update_task 에 completed 필드 추가(세션이
없는 하위업무의 직접 완료 토글용).
- 멀티호밍 UI — 업무 모달에 "연결된 프로젝트" 칩 + "+ 프로젝트에 추가"(
프로젝트/세션 선택). 보드에서 다른 프로젝트가 원 소속인 카드는 그
프로젝트 배지를 달고 나타나며, 드래그로 세션을 옮기면
POST /api/tasks/{id}/links 로 그 프로젝트 안에서의 배치만 바뀐다(원본
불변). 링크 생성 권한을 다듬음 — 최초 연결은 원/대상 프로젝트 양쪽 권한이
필요하지만, 이미 연결된 카드를 그 프로젝트 보드 안에서 옮기는 것은 대상
프로젝트 권한만 있으면 되게 해 정상적인 드래그가 403 나지 않게 했다.
- 업무 설명란을 textarea → contenteditable 로 교체. 클립보드 이미지
붙여넣기/파일 업로드 버튼으로 삽입(기존 task_attachments 재사용, 새
GET /api/attachments/{id}/inline 추가 — 기존 /download 는
Content-Disposition: attachment 라 <img> 렌더링에 못 씀). 새 업무 작성
중(저장 전)에는 이미지 삽입 비활성화.
- .pj-modal-card 에 max-height/overflow-y 추가(내용이 늘어 화면보다
길어지는 것 방지).
홈(index.html)·프로젝트(project.html) 두 업무 모달 모두 요소 id 를 공유하는
기존 구조를 그대로 따라, 새 로직은 공용 함수(initDescEditor/initSubtaskBlock/
initLinksBlock)로 한 번만 작성해 양쪽에서 재사용한다.
docs/PROJECT_MODULE.md 갱신 — 1-1절에 3단계 구조·권한 변경 전반을 정리.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+79
-14
@@ -1,7 +1,7 @@
|
||||
# 프로젝트 관리 모듈 (아사나식) — `app/modules/project/`
|
||||
|
||||
> 회사(dbxcorp.co.kr) 직원이 프로젝트·서브프로젝트·업무를 달력/타임라인/보드로
|
||||
> 관리하는 아사나(Asana) 스타일 협업 모듈. DB는 `project_db` 전용.
|
||||
> 회사(dbxcorp.co.kr) 직원이 프로젝트→세션→업무 3단계 구조를 달력/타임라인/
|
||||
> 보드로 관리하는 아사나(Asana) 스타일 협업 모듈. DB는 `project_db` 전용.
|
||||
|
||||
## 1. 개요
|
||||
|
||||
@@ -12,16 +12,66 @@
|
||||
| 연결 env | `PROJECT_DB_URL` (미설정 시 "설정 필요" 안내, 앱은 죽지 않음) |
|
||||
| 진입 권한 | 권한키 `project` (관리자 페이지 토글로 직원별 부여, admin 자동) |
|
||||
| 멤버 배정 후보 | `project` 권한 보유 등록 사용자 자동 목록 (`GET /project/api/assignable-users`) |
|
||||
| 관리 권한 | 프로젝트 생성/삭제·사용자 배정 = ERP 관리자(`is_admin`) / 서브프로젝트·업무·단계 = 배정 멤버 또는 owner |
|
||||
| 생성 권한 | **프로젝트·세션·업무(하위 업무 포함) 생성은 로그인해 이 모듈에 접근할 수 있는 사용자 누구나.** 프로젝트를 만든 사람은 자동으로 owner 가 되어 그 프로젝트를 관리(`_can_manage`)할 수 있다. |
|
||||
| 수정/이동 권한 | 서브프로젝트·업무·세션 CRUD(이름변경·완료토글·순서변경 등) = 그 프로젝트의 배정 멤버 또는 owner 또는 admin(`_require_manage`) |
|
||||
| 삭제 권한 | **프로젝트·세션·업무 모두 "만든 사람만"**(슈퍼관리자 예외, 생성자 정보 없는 과거 데이터는 admin 허용) — `_can_delete` 하나로 통일 |
|
||||
| 멤버 배정 | 여전히 관리자(`is_admin`) 전용. 비관리자가 "새 프로젝트" 모달을 열면 멤버 선택란은 안내 문구만 뜨고 시도하지 않는다(불필요한 403 방지) |
|
||||
| 메일 알림 | `app/mail.py` (stdlib smtplib). 업무 배정/완료 시 관리자에게 발송 |
|
||||
|
||||
## 2. 데이터 모델 (`scripts/sql/project_db_init.sql`)
|
||||
## 1-1. 프로젝트 → 세션 → 업무 (하위 업무 포함) 3단계 구조
|
||||
|
||||
**"세션"은 새 개념이 아니라 기존 "단계"(칸반 컬럼)를 그대로 재사용한다** —
|
||||
아사나도 보드 뷰의 컬럼과 리스트 뷰의 구분선이 같은 "섹션" 하나다. DB
|
||||
컬럼/테이블 이름(`project_stages`, `stage_id`)은 그대로 두고, 사용자에게
|
||||
보이는 문구만 "세션"으로 바꿨다.
|
||||
|
||||
- **세션 관리** — 프로젝트 화면(`/project/p/{id}`)에서 "이 프로젝트" 스코프로
|
||||
볼 때만 보드 컬럼에 관리 UI가 붙는다(이름변경·완료토글·삭제·"+ 세션 추가").
|
||||
컬럼 헤더를 **드래그해서 순서를 바꿀 수 있다**(카드 드래그와 같은 네이티브
|
||||
HTML5 DnD, `PUT /api/projects/{id}/stages/order` 재사용). "전체 프로젝트"
|
||||
스코프(여러 프로젝트를 세션 '이름'으로 합쳐 보는 예전 방식)에서는 세션을
|
||||
하나로 특정할 수 없어 관리 UI가 없다.
|
||||
- **하위 업무(subtask)** — 새 테이블이 아니라 `tasks.parent_task_id` 재사용
|
||||
(부모 삭제 시 CASCADE). 업무 편집 팝업 안에 인라인으로 표시되며(체크박스+
|
||||
제목+담당자+마감일, 한 줄에서 바로 저장), 보드/캘린더/리스트에는 최상위
|
||||
업무만 카드로 뜬다(하위 업무는 부모 카드에 "2/5" 진행 배지로만 나타난다).
|
||||
세션을 갖지 않는다(칸반에 안 보이므로).
|
||||
- **멀티호밍(다른 프로젝트에 연결)** — `task_project_links` 테이블. 업무 하나가
|
||||
원래 소속(기본 홈, `tasks.project_id`/`stage_id`)과 무관하게 다른 프로젝트의
|
||||
한 세션에도 동시에 나타날 수 있다(아사나의 "Add to project"). 업무 편집
|
||||
팝업의 "연결된 프로젝트" 칩에서 추가/해제한다. 새 연결을 만들 때는 원
|
||||
프로젝트·대상 프로젝트 양쪽 관리 권한이 필요하지만(무단으로 남의 업무를
|
||||
끌어와 노출시키는 것 방지), **이미 연결된 업무를 그 프로젝트 보드 안에서
|
||||
다른 세션으로 드래그하는 것은 대상 프로젝트 권한만 있으면 된다.**
|
||||
`list_tasks(project_id=X)`(비재귀 단일 프로젝트 조회, 프로젝트 화면이 쓰는
|
||||
바로 그 경로)가 자동으로 연결된 업무까지 포함해서 돌려준다 — 다른 호출
|
||||
경로(홈/내 업무 등 `project_id` 없이 부르는 전체보기)는 원 소속만 세어
|
||||
중복 노출을 막는다.
|
||||
- **사이드바 프로젝트 스코프** — 프로젝트 화면 툴바에 "이 프로젝트"/"전체
|
||||
프로젝트" 토글이 있다. 기본은 "이 프로젝트"(아사나처럼 지금 선택한
|
||||
프로젝트의 세션만 보드/캘린더/타임라인/리스트에 나온다), "전체 프로젝트"는
|
||||
예전처럼 접근 가능한 모든 프로젝트를 합쳐서 보여준다. 서버가 `tasks` 배열의
|
||||
각 행에 `context_project_id`(어느 프로젝트를 보다가 담겼는지)를 표시해두고,
|
||||
클라이언트가 이 값으로 스코프를 걸러낸다(순수 프론트 필터 — 서버 재조회 없음).
|
||||
- **업무 설명 이미지 삽입** — 설명란이 `contenteditable` 로 바뀌어 이미지를
|
||||
붙여넣거나 파일로 올릴 수 있다. 새 첨부 endpoint 없이 기존
|
||||
`task_attachments` 업로드를 재사용하고, 표시는 `GET
|
||||
/api/attachments/{id}/inline`(다운로드용 `/download` 는 `Content-Disposition:
|
||||
attachment` 가 걸려 `<img>` 로 못 씀 — 그래서 인라인 전용 endpoint를 따로
|
||||
뒀다)로 한다. 저장 전 서버가 `store.sanitize_description_html()` 로 허용
|
||||
태그만 남기고 스크립트/이벤트속성/위험 URL 스킴을 제거한다(표준
|
||||
`html.parser`, 외부 라이브러리 없음). 새 업무 작성 중(저장 전, task_id 없음)
|
||||
에는 이미지 삽입이 비활성화된다 — 첨부가 업무 id 를 필요로 하기 때문.
|
||||
|
||||
## 2. 데이터 모델 (`scripts/sql/project_db_init.sql` + `..._002_*`/`..._003_*`)
|
||||
|
||||
- `projects` — `parent_id`(self-FK, NULL=최상위 / NOT NULL=서브프로젝트, `ON DELETE CASCADE`), name, description, color, owner_email, start/due_date, status(active|archived).
|
||||
- `project_members` — 프로젝트↔사용자 배정. role(manager|member), `UNIQUE(project_id, user_email)`.
|
||||
- `project_stages` — 진행단계(칸반 컬럼). 프로젝트 생성 시 기본 4단계 seed(할 일/진행 중/검토/완료). `is_done_stage`=TRUE 단계로 옮기면 업무 완료 처리.
|
||||
- `tasks` — 업무. stage_id, title, description, assignee_email/name, priority(low|normal|high), start/due_date, completed_at.
|
||||
- `task_comments` — 댓글(스켈레톤 테이블, UI는 추후).
|
||||
- `project_stages`(="세션") — 진행단계(칸반 컬럼). 프로젝트 생성 시 기본 4단계 seed(할 일/진행 중/검토/완료). `is_done_stage`=TRUE 단계로 옮기면 업무 완료 처리. `created_by`(003) — 삭제를 "만든 사람만"으로 제한하기 위함.
|
||||
- `tasks` — 업무. stage_id, title, description(HTML — 이미지 삽입 지원), assignee_email/name, priority(low|normal|high), start/due_date, completed_at, `parent_task_id`(003, self-FK `ON DELETE CASCADE`, NULL=최상위 업무).
|
||||
- `task_project_links`(003) — 멀티호밍. `(task_id, project_id)` UNIQUE, `stage_id` nullable(그 프로젝트 안에서의 세션).
|
||||
- `task_comments` — 댓글.
|
||||
- `task_attachments` — 첨부(설명란 이미지도 이걸 재사용).
|
||||
- `project_activity` — 활동 이력(created/assigned/completed/stage_changed). 메일 트리거 근거 + 타임라인.
|
||||
|
||||
## 3. 화면 / 뷰
|
||||
@@ -42,8 +92,11 @@
|
||||
좌측 트리의 업무 클릭도 페이지 이동 없이 바로 팝업(이미 로드된 전체 업무 데이터 사용).
|
||||
- **달력** — FullCalendar. 업무를 시작~마감 기간으로 표시. 클릭 편집.
|
||||
- **타임라인** — vis-timeline(간트형). 기간 있는 업무만.
|
||||
- **보드** — 단계별 칸반. 카드 드래그로 단계 이동(`PUT /api/tasks/{id}` `stage_id`).
|
||||
- **보드** — 세션별 칸반. 카드 드래그로 세션 이동(`PUT /api/tasks/{id}` `stage_id`,
|
||||
멀티호밍된 업무는 `POST /api/tasks/{id}/links`). 세션 컬럼 자체도 드래그로
|
||||
재정렬(§1-1).
|
||||
- **리스트** — 표.
|
||||
- 4개 뷰 모두 툴바의 "이 프로젝트"/"전체 프로젝트" 스코프 토글을 따른다(§1-1).
|
||||
- 라이브러리는 현재 CDN 로드(스켈레톤). 추후 `app/static/vendor/` self-host 권장.
|
||||
|
||||
## 4. 메일 알림
|
||||
@@ -64,18 +117,30 @@ docker exec -i postgres-db psql -U postgres -v app_password="$APP_PWD" \
|
||||
# PROJECT_DB_URL=postgresql://project_app:<APP_PWD>@postgres-db:5432/project_db
|
||||
# (메일 쓰려면 SMTP_* 추가)
|
||||
|
||||
# 3) 재배포 (git pull 후 반드시 --build)
|
||||
# 3) 마이그레이션 002·003 적용 (첨부/알림, 세션 생성자·하위업무·멀티호밍)
|
||||
docker exec -i postgres-db psql -U postgres -d project_db \
|
||||
< scripts/sql/project_db_002_attachments_notifications.sql
|
||||
docker exec -i postgres-db psql -U postgres -d project_db \
|
||||
< scripts/sql/project_db_003_sections_subtasks_links.sql
|
||||
|
||||
# 4) 재배포 (git pull 후 반드시 --build)
|
||||
cd /opt/www/main && docker compose up -d --build web
|
||||
```
|
||||
|
||||
## 6. 댓글 · 첨부 · 알림센터 · 단계편집 (구현됨)
|
||||
⚠️ 003 안에는 기존 `tasks.description`(일반 텍스트)을 안전한 HTML로 바꾸는
|
||||
**1회성** `UPDATE` 문이 있다(설명란이 contenteditable 로 바뀌어 이제
|
||||
description 을 항상 HTML로 취급하기 때문). 두 번 실행하면 이중 이스케이프되니
|
||||
꼭 한 번만 돌릴 것.
|
||||
|
||||
## 6. 댓글 · 첨부 · 알림센터 · 세션 편집 (구현됨)
|
||||
|
||||
- **댓글** `task_comments` — 업무 모달 하단. 등록/삭제(본인·관리자). 새 댓글 시 관련자 인앱 알림.
|
||||
- **첨부** `task_attachments` — 업무 모달. 파일 업로드(최대 20MB)/다운로드/삭제. 실제 파일은 `DATA_DIR/project/<task_id>/<uuid>.<ext>`, DB엔 메타만. 첨부 디렉토리는 운영 볼륨(DATA_DIR)에 저장돼 재배포에도 보존.
|
||||
- **단계 편집** — 보드 칸반 헤더에서 단계 이름변경/완료토글/좌우 이동(순서)/삭제, 트레일링 "+ 단계 추가". `PUT .../stages/order`, `PUT .../stages/{id}`.
|
||||
- **첨부** `task_attachments` — 업무 모달. 파일 업로드(최대 20MB)/다운로드/삭제. 실제 파일은 `DATA_DIR/project/<task_id>/<uuid>.<ext>`, DB엔 메타만. 첨부 디렉토리는 운영 볼륨(DATA_DIR)에 저장돼 재배포에도 보존. 설명란 이미지 삽입도 이 표를 재사용(§1-1).
|
||||
- **세션 편집** — 보드 칸반 헤더에서 이름변경/완료토글/삭제, 트레일링 "+ 세션 추가", **컬럼 드래그로 순서 변경**. `PUT .../stages/order`, `PUT .../stages/{id}`, 삭제는 만든 사람만.
|
||||
- **알림센터(인앱)** `project_notifications` — 배정/완료/댓글 시 수신자별 알림 생성. 우측 상단 벨(미읽음 배지) → `/project/inbox`. 항목 클릭=읽음, "모두 읽음". 본인 행동은 알림 제외.
|
||||
- 마이그레이션: `scripts/sql/project_db_002_attachments_notifications.sql` (첨부·알림 테이블 + 권한).
|
||||
- 마이그레이션: `scripts/sql/project_db_002_attachments_notifications.sql` (첨부·알림 테이블 + 권한), `scripts/sql/project_db_003_sections_subtasks_links.sql` (세션 생성자·하위업무·멀티호밍).
|
||||
|
||||
## 7. 추후 단계
|
||||
|
||||
태그 · 하위업무(체크리스트) · 검색/필터 · 업무 정렬 영속화 · 칸반 단계 드래그 정렬 · 멘션.
|
||||
태그 · 검색/필터 · 업무 정렬 영속화 · 멘션 · 하위 업무의 하위 업무(다단계 중첩,
|
||||
아사나도 UI상 1단계만 허용해 현재 의도적으로 안 함).
|
||||
|
||||
Reference in New Issue
Block a user