feat(project): 프로젝트 페이지에 아사나식 대시보드 탭 추가
보드/리스트/달력/타임라인 옆에 "대시보드" 탭 신설. 서버 재조회 없이 이미 로드된 tasks(visibleTasks(), 스코프 토글도 그대로 따름)로 클라이언트에서 즉시 집계: - 통계 카드 4개(완료/미완료/마감초과/전체), 숫자가 0에서 카운트업 - 미완료 작업 합계(섹션별) — 세로 막대, 보드 컬럼과 같은 순서 - 작업 합계(완료 상태별) — SVG 도넛(완료=초록/미완료=포인트컬러), 등장 시 획이 0에서 목표 길이까지 그려짐 - 예정된 작업 합계(담당자별) — 일정 있는 미완료 업무만, 상위 10명+기타 막대/도넛 둘 다 호버 시 값 우선 툴팁, 카드는 순차 페이드인(아사나 느낌). 차트 라이브러리 추가 없이 순수 SVG/CSS로 구현. 탭 5개로 늘면서 좁은 화면에서 텍스트가 줄바꿈되던 버그도 같이 고침(pj-view-tabs flex-shrink).
This commit is contained in:
+72
-2
@@ -248,8 +248,8 @@
|
||||
.pj-view { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
|
||||
.pj-view[hidden] { display: none; }
|
||||
.pj-view[data-view="board"]:not([hidden]) { display: flex; flex-direction: column; overflow: hidden; }
|
||||
.pj-view-tabs { display: inline-flex; background: #eef0f2; border-radius: 10px; padding: 3px; gap: 2px; }
|
||||
.pj-tab { border: none; background: transparent; padding: 7px 16px; border-radius: 8px; font-size: 13px; font-weight: 600; color: #525860; cursor: pointer; }
|
||||
.pj-view-tabs { display: inline-flex; flex: 0 0 auto; background: #eef0f2; border-radius: 10px; padding: 3px; gap: 2px; }
|
||||
.pj-tab { border: none; background: transparent; padding: 7px 16px; border-radius: 8px; font-size: 13px; font-weight: 600; color: #525860; cursor: pointer; white-space: nowrap; }
|
||||
.pj-tab.is-active { background: var(--pj-accent); color: #fff; box-shadow: 0 1px 3px rgba(123,104,238,.35); }
|
||||
|
||||
/* 알림 벨 흔들림(미읽음 있을 때) */
|
||||
@@ -731,3 +731,73 @@ label.pj-desc-fmtbtn { line-height: 26px; }
|
||||
.pj-ctxmenu-item.is-danger { color: #c22b10; }
|
||||
.pj-ctxmenu-item.is-danger:hover { background: #fbecea; }
|
||||
.pj-ctxmenu-sep { height: 1px; background: var(--pj-border); margin: 4px 2px; }
|
||||
|
||||
/* ── 대시보드 — 통계 카드 + 차트(전부 클라이언트 집계/렌더). 카드 자체는
|
||||
pj-col 과 같은 흰 카드 톤(radius/shadow)을 재사용, 숫자·막대·도넛만
|
||||
애니메이션(등장 시 0→목표값으로 부드럽게, 아사나 느낌). ── */
|
||||
.pj-dash { display: flex; flex-direction: column; gap: 20px; padding: 4px 2px 24px; }
|
||||
.pj-dash-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
|
||||
.pj-dash-stat {
|
||||
background: #fff; border: 1px solid var(--pj-border); border-radius: var(--pj-card-radius);
|
||||
box-shadow: var(--pj-card-shadow); padding: 18px 20px;
|
||||
}
|
||||
.pj-dash-stat-label { font-size: 12.5px; color: var(--pj-ink-soft); font-weight: 600; margin-bottom: 10px; }
|
||||
.pj-dash-stat-value { font-size: 34px; font-weight: 700; color: var(--pj-ink); line-height: 1; }
|
||||
.pj-dash-charts { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; }
|
||||
.pj-dash-card {
|
||||
background: #fff; border: 1px solid var(--pj-border); border-radius: var(--pj-card-radius);
|
||||
box-shadow: var(--pj-card-shadow); padding: 18px 20px 20px; min-width: 0;
|
||||
}
|
||||
.pj-dash-card-wide { grid-column: 1 / -1; }
|
||||
.pj-dash-card-title { font-size: 13.5px; font-weight: 700; color: var(--pj-ink); margin-bottom: 18px; }
|
||||
|
||||
/* 세로 막대(섹션별/담당자별 공용) */
|
||||
.pj-dash-bars { display: flex; align-items: flex-end; gap: 18px; overflow-x: auto; padding-bottom: 2px; min-height: 200px; }
|
||||
.pj-dash-barcol { display: flex; flex-direction: column; align-items: center; flex: 0 0 auto; min-width: 52px; cursor: default; border-radius: 8px; outline: none; }
|
||||
.pj-dash-barcol:focus-visible { box-shadow: 0 0 0 2px var(--pj-accent-tint); }
|
||||
.pj-dash-barcol-value { font-size: 12.5px; font-weight: 700; color: var(--pj-ink); margin-bottom: 6px; }
|
||||
.pj-dash-barcol-track {
|
||||
position: relative; width: 34px; height: 140px; background: var(--pj-accent-tint);
|
||||
border-radius: 6px 6px 4px 4px; overflow: hidden;
|
||||
}
|
||||
.pj-dash-barcol-fill {
|
||||
position: absolute; left: 0; right: 0; bottom: 0; height: 0%;
|
||||
background: var(--pj-accent); border-radius: 6px 6px 0 0;
|
||||
transition: height .7s cubic-bezier(.22,.9,.3,1);
|
||||
}
|
||||
.pj-dash-barcol:hover .pj-dash-barcol-fill { background: var(--pj-accent-dark); }
|
||||
.pj-dash-barcol-label {
|
||||
margin-top: 8px; font-size: 11.5px; color: var(--pj-ink-soft); text-align: center;
|
||||
max-width: 72px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 도넛(완료 상태별) */
|
||||
.pj-dash-donut-wrap { display: flex; align-items: center; gap: 24px; flex-wrap: wrap; }
|
||||
.pj-dash-donut { width: 150px; height: 150px; flex: 0 0 auto; }
|
||||
.pj-dash-donut-track { fill: none; stroke: var(--pj-border); stroke-width: 14; }
|
||||
.pj-dash-donut-seg { fill: none; stroke-width: 14; stroke-linecap: butt; transition: stroke-dasharray .8s cubic-bezier(.22,.9,.3,1); }
|
||||
.pj-dash-donut-center { font-size: 26px; font-weight: 700; fill: var(--pj-ink); }
|
||||
.pj-dash-legend { display: flex; flex-direction: column; gap: 10px; }
|
||||
.pj-dash-legend-item { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--pj-ink); }
|
||||
.pj-dash-legend-item b { margin-left: 2px; }
|
||||
.pj-dash-legend-dot { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; }
|
||||
|
||||
/* 막대/세그먼트 호버 툴팁 — 값이 크게, 라벨은 보조(값 우선 원칙) */
|
||||
.pj-dash-tooltip {
|
||||
position: absolute; z-index: 2150; transform: translate(-50%, -100%);
|
||||
background: var(--pj-ink); color: #fff; padding: 6px 10px; border-radius: 8px;
|
||||
font-size: 12px; white-space: nowrap; pointer-events: none;
|
||||
box-shadow: var(--pj-card-shadow-hover);
|
||||
}
|
||||
.pj-dash-tooltip b { font-size: 13.5px; margin-right: 6px; }
|
||||
.pj-dash-tooltip span { color: #cfd3da; }
|
||||
|
||||
/* 카드 등장 트랜지션 — 대시보드 탭을 열 때마다 살짝 떠오르며 나타난다. */
|
||||
.pj-dash-stat, .pj-dash-card { animation: pj-dash-in .35s cubic-bezier(.22,.9,.3,1) both; }
|
||||
.pj-dash-stats .pj-dash-stat:nth-child(2) { animation-delay: .04s; }
|
||||
.pj-dash-stats .pj-dash-stat:nth-child(3) { animation-delay: .08s; }
|
||||
.pj-dash-stats .pj-dash-stat:nth-child(4) { animation-delay: .12s; }
|
||||
.pj-dash-charts .pj-dash-card:nth-child(2) { animation-delay: .06s; }
|
||||
.pj-dash-charts .pj-dash-card:nth-child(3) { animation-delay: .12s; }
|
||||
@keyframes pj-dash-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@media (max-width: 900px) { .pj-dash-stats { grid-template-columns: repeat(2, 1fr); } }
|
||||
|
||||
Reference in New Issue
Block a user