feat(project): 보드 카드를 같은 세션 안에서 드래그로 순서 변경
지금까지 같은 세션에 다시 놓으면 아무 일도 안 일어났다(다른 세션으로 옮길 때만 동작). 놓은 위치(마우스 Y좌표)를 카드들 사이 인덱스로 계산해 그 순서를 새 엔드포인트로 저장하고(tasks.sort_order, 세션 소속만 재배정 — 세션 재정렬의 reorder_stages 와 동일 패턴), 로컬 tasks 배열도 그 순서에 맞게 재배치(applyStageOrder)해 새로고침 없이 즉시 반영. 실제 3개 업무로 드래그→서버 저장→하드리로드까지 헤드리스 크롬으로 검증.
This commit is contained in:
@@ -812,6 +812,20 @@ class ProjectStore:
|
||||
(i, int(sid), project_id),
|
||||
)
|
||||
|
||||
def reorder_tasks(self, *, stage_id: int, ordered_ids: list[int]) -> None:
|
||||
"""보드 카드 드래그로 같은 세션 안에서 순서 바꾸기. sort_order 는
|
||||
업무 생성 시 프로젝트 전체 기준으로 매겨지지만(다른 세션과 섞여도
|
||||
무방 — 화면은 항상 세션으로 먼저 필터링한 뒤 이 값으로 정렬한다),
|
||||
여기서는 이 세션 소속 업무만 골라 0부터 다시 매긴다."""
|
||||
with self._pool.connection() as conn:
|
||||
with conn.transaction():
|
||||
for i, tid in enumerate(ordered_ids):
|
||||
conn.execute(
|
||||
"UPDATE tasks SET sort_order = %s "
|
||||
"WHERE id = %s AND stage_id = %s",
|
||||
(i, int(tid), stage_id),
|
||||
)
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# 알림센터 (project_notifications)
|
||||
# ════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -1287,6 +1287,21 @@ async def api_reorder_stages(
|
||||
return JSONResponse({"stages": st.list_stages(project_id=project_id)})
|
||||
|
||||
|
||||
@router.put("/api/projects/{project_id}/stages/{stage_id}/tasks/order")
|
||||
async def api_reorder_tasks(
|
||||
request: Request, project_id: int, stage_id: int, payload: dict[str, Any] = Body(...)
|
||||
) -> JSONResponse:
|
||||
"""보드에서 카드를 같은 세션 안에서 드래그로 재정렬."""
|
||||
user = _require_user(request)
|
||||
st = _db_or_503(request)
|
||||
_require_manage(request, st, user, project_id)
|
||||
ids = payload.get("ordered_ids") or []
|
||||
if not isinstance(ids, list) or not ids:
|
||||
raise HTTPException(status_code=400, detail="ordered_ids 가 필요합니다.")
|
||||
st.reorder_tasks(stage_id=stage_id, ordered_ids=[int(i) for i in ids])
|
||||
return JSONResponse({"ok": True})
|
||||
|
||||
|
||||
@router.put("/api/projects/{project_id}/stages/{stage_id}")
|
||||
async def api_update_stage(
|
||||
request: Request, project_id: int, stage_id: int, payload: dict[str, Any] = Body(...)
|
||||
|
||||
@@ -45,5 +45,5 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script src="/static/project.js?v=20260917k" defer></script>
|
||||
<script src="/static/project.js?v=20260917l" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -393,5 +393,5 @@
|
||||
<script>
|
||||
window.PJ_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772"] | tojson }};
|
||||
</script>
|
||||
<script src="/static/project.js?v=20260917k" defer></script>
|
||||
<script src="/static/project.js?v=20260917l" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -461,5 +461,5 @@
|
||||
</script>
|
||||
|
||||
<script src="/static/vendor/vis-timeline/vis-timeline-graph2d.min.js"></script>
|
||||
<script src="/static/project.js?v=20260917k" defer></script>
|
||||
<script src="/static/project.js?v=20260917l" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user