fix(project): 보드 세션 매이슨리 배치 — 열별 독립 높이, 행 강제 확장 제거
CSS Grid 4열은 같은 행의 세션 하나만 카드가 많아도 그 행 전체 높이가 맞춰져 다른 칸이 텅 빈 채로 늘어나는 문제가 있었다(그 여파로 .pj-main이 불필요하게 커져 페이지 전체가 스크롤되고 "내 업무" 패널까지 스크롤되는 것처럼 보였음). layoutMasonry()로 세션을 절대배치해 열은 왼쪽부터 고정 순서로 배정하고, 각 열은 자기 카드 높이만큼만 다음 세션을 바로 그 밑에 쌓는다. 창 크기 변경 시 재계산. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}
|
{% block head_extra %}
|
||||||
<link rel="stylesheet" href="/static/project.css?v=20260916h" />
|
<link rel="stylesheet" href="/static/project.css?v=20260916i" />
|
||||||
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@@ -45,5 +45,5 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/project.js?v=20260916i" defer></script>
|
<script src="/static/project.js?v=20260916j" defer></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}
|
{% block head_extra %}
|
||||||
<link rel="stylesheet" href="/static/project.css?v=20260916h" />
|
<link rel="stylesheet" href="/static/project.css?v=20260916i" />
|
||||||
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@@ -376,5 +376,5 @@
|
|||||||
<script>
|
<script>
|
||||||
window.PJ_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772"] | tojson }};
|
window.PJ_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772"] | tojson }};
|
||||||
</script>
|
</script>
|
||||||
<script src="/static/project.js?v=20260916i" defer></script>
|
<script src="/static/project.js?v=20260916j" defer></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}
|
{% block head_extra %}
|
||||||
<link rel="stylesheet" href="/static/project.css?v=20260916h" />
|
<link rel="stylesheet" href="/static/project.css?v=20260916i" />
|
||||||
<!-- 구글 머티리얼 심볼(담당자 아이콘 등) — self-host -->
|
<!-- 구글 머티리얼 심볼(담당자 아이콘 등) — self-host -->
|
||||||
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
||||||
<!-- 타임라인 vis-timeline — self-host -->
|
<!-- 타임라인 vis-timeline — self-host -->
|
||||||
@@ -379,5 +379,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="/static/vendor/vis-timeline/vis-timeline-graph2d.min.js"></script>
|
<script src="/static/vendor/vis-timeline/vis-timeline-graph2d.min.js"></script>
|
||||||
<script src="/static/project.js?v=20260916i" defer></script>
|
<script src="/static/project.js?v=20260916j" defer></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
+11
-8
@@ -191,21 +191,24 @@
|
|||||||
transform-origin: top center;
|
transform-origin: top center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── 보드(칸반) — 4열 x 2행 그리드. 세션이 8개를 넘으면 이 영역 안에서만
|
/* ── 보드(칸반) — 최대 4열 매이슨리(masonry). CSS Grid로 4열을 만들면 같은
|
||||||
|
행의 한 칸만 카드가 많아도 그 행 전체 높이가 맞춰져 빈 칸이 생긴다 —
|
||||||
|
그래서 각 세션을 JS(layoutMasonry)로 절대배치해 열은 고정(왼쪽부터
|
||||||
|
순서대로 배정)하되 높이는 열마다 독립적으로 쌓이게 한다(같은 열의 다음
|
||||||
|
세션이 바로 위 세션 아래에 붙는다). 세션이 많아지면 이 영역 안에서만
|
||||||
세로 스크롤되고, "+ 세션 추가" 버튼은 이 영역 밖(pj-board-toolbar)에
|
세로 스크롤되고, "+ 세션 추가" 버튼은 이 영역 밖(pj-board-toolbar)에
|
||||||
있어 스크롤과 무관하게 항상 보인다. ── */
|
있어 스크롤과 무관하게 항상 보인다. ── */
|
||||||
.pj-board-toolbar { display: flex; justify-content: flex-end; margin-bottom: 10px; }
|
.pj-board-toolbar { display: flex; justify-content: flex-end; margin-bottom: 10px; }
|
||||||
.pj-board-toolbar .pj-add-stage { width: auto; padding: 8px 18px; }
|
.pj-board-toolbar .pj-add-stage { width: auto; padding: 8px 18px; }
|
||||||
.pj-board {
|
.pj-board {
|
||||||
display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); grid-auto-rows: min-content;
|
position: relative; max-height: 74vh; overflow-y: auto; overflow-x: hidden;
|
||||||
gap: 16px; align-content: start; max-height: 74vh; overflow-y: auto; padding-right: 4px; padding-bottom: 8px;
|
padding-right: 4px; padding-bottom: 8px;
|
||||||
}
|
}
|
||||||
@media (max-width: 1400px) { .pj-board { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
|
|
||||||
@media (max-width: 1100px) { .pj-board { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
||||||
@media (max-width: 700px) { .pj-board { grid-template-columns: 1fr; } }
|
|
||||||
/* 세션(보드 컬럼) — 배경이 페이지와 거의 같으면 구분이 안 되므로(#f6f5fb ≈
|
/* 세션(보드 컬럼) — 배경이 페이지와 거의 같으면 구분이 안 되므로(#f6f5fb ≈
|
||||||
흰 배경) 뚜렷한 회색 톤 + 테두리로 아사나처럼 컬럼 경계를 분명히 준다. */
|
흰 배경) 뚜렷한 회색 톤 + 테두리로 아사나처럼 컬럼 경계를 분명히 준다.
|
||||||
.pj-col { background: #eef0f4; border: 1px solid #e2e4ea; border-radius: 14px; padding: 12px; min-width: 0; }
|
position/width/left/top 은 layoutMasonry 가 매번 인라인으로 계산해 준다. */
|
||||||
|
.pj-col { position: absolute; background: #eef0f4; border: 1px solid #e2e4ea; border-radius: 14px; padding: 12px; box-sizing: border-box; }
|
||||||
|
.pj-empty { position: static; }
|
||||||
.pj-col-head {
|
.pj-col-head {
|
||||||
display: flex; align-items: center; gap: 6px; font-weight: 700; font-size: 13px;
|
display: flex; align-items: center; gap: 6px; font-weight: 700; font-size: 13px;
|
||||||
padding: 2px 6px 12px; margin-bottom: 4px; color: var(--pj-ink);
|
padding: 2px 6px 12px; margin-bottom: 4px; color: var(--pj-ink);
|
||||||
|
|||||||
@@ -1108,8 +1108,38 @@
|
|||||||
if (boardAddStageBtn) boardAddStageBtn.hidden = !(viewScope === "project" && canManage);
|
if (boardAddStageBtn) boardAddStageBtn.hidden = !(viewScope === "project" && canManage);
|
||||||
if (viewScope === "project") renderBoardByStage(board, vis);
|
if (viewScope === "project") renderBoardByStage(board, vis);
|
||||||
else renderBoardByName(board, vis);
|
else renderBoardByName(board, vis);
|
||||||
|
layoutMasonry(board);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 세션(보드 컬럼) 매이슨리 배치 — 열은 왼쪽부터 순서대로 고정 배정하고,
|
||||||
|
// 각 열은 자기 안에 쌓인 카드 높이만큼만 다음 세션을 그 밑에 이어붙인다
|
||||||
|
// (CSS Grid처럼 행 전체를 가장 큰 세션에 맞춰 늘리지 않는다).
|
||||||
|
function layoutMasonry(board) {
|
||||||
|
const items = Array.prototype.slice.call(board.children).filter(function (node) {
|
||||||
|
return node.classList.contains("pj-col");
|
||||||
|
});
|
||||||
|
if (!items.length) { board.style.height = "auto"; return; }
|
||||||
|
const gap = 16, minColWidth = 220, maxCols = 4;
|
||||||
|
const boardWidth = board.clientWidth || 1;
|
||||||
|
const cols = Math.max(1, Math.min(maxCols, Math.floor((boardWidth + gap) / (minColWidth + gap))));
|
||||||
|
const colWidth = (boardWidth - gap * (cols - 1)) / cols;
|
||||||
|
const colHeights = new Array(cols).fill(0);
|
||||||
|
items.forEach(function (node, i) {
|
||||||
|
const c = i % cols;
|
||||||
|
node.style.width = colWidth + "px";
|
||||||
|
node.style.left = (c * (colWidth + gap)) + "px";
|
||||||
|
node.style.top = colHeights[c] + "px";
|
||||||
|
colHeights[c] += node.offsetHeight + gap;
|
||||||
|
});
|
||||||
|
board.style.height = Math.max.apply(null, colHeights) + "px";
|
||||||
|
}
|
||||||
|
let boardResizeTimer = null;
|
||||||
|
window.addEventListener("resize", function () {
|
||||||
|
if (currentView !== "board") return;
|
||||||
|
clearTimeout(boardResizeTimer);
|
||||||
|
boardResizeTimer = setTimeout(function () { layoutMasonry(el("pj-board")); }, 120);
|
||||||
|
});
|
||||||
|
|
||||||
function renderBoardByName(board, vis) {
|
function renderBoardByName(board, vis) {
|
||||||
// 컬럼 이름 = 서버가 정렬해준 전체 세션 이름 + 세션 미지정 업무용 '미지정'
|
// 컬럼 이름 = 서버가 정렬해준 전체 세션 이름 + 세션 미지정 업무용 '미지정'
|
||||||
let names = boardStages.slice();
|
let names = boardStages.slice();
|
||||||
|
|||||||
Reference in New Issue
Block a user