Compare commits

...

2 Commits

Author SHA1 Message Date
ttipo 0cdd2bb98e Merge: 보고서 모달·일정 화면 다듬기 + 일정 컴포넌트 rem 통일
tweak/schedule-polish 머지 — 보고서 모달(전체탭 제거·상태버튼 축소·푸터 배경),
일정 화면 여백/마진 정리, 일정 하위 컴포넌트 px→rem 일괄 변환.
2026-06-23 10:36:50 +09:00
ttipo 7568637de2 style: 보고서 모달·일정 화면 다듬기 + 일정 컴포넌트 rem 통일
- 보고서 모달: '전체' 탭 제거(기본=항목 있는 첫 분류), 상태 드롭다운 크기 축소,
  푸터 배경을 다른 모달(.bm-foot)과 동일한 #fafbfc 로 통일
- 일정 화면: 콘텐츠 영역 상하좌우 여백 1.5rem 통일, 툴바 가로 여백 정렬 +
  전역 .toolbar 의 margin-bottom 상속 차단(상단 여분 마진 제거)
- 일정 하위 컴포넌트 px→rem 일괄 변환(보더 width·box-shadow 는 px 유지),
  ScheduleAvatar 도 size 를 rem 렌더 → '전 직원' 표식과 아바타 크기 정합 유지

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 00:38:59 +09:00
8 changed files with 178 additions and 234 deletions
+10 -10
View File
@@ -90,16 +90,16 @@ function pick(v: ChecklistWorkStatus): void {
.ss-btn {
display: inline-flex;
align-items: center;
gap: 7px;
height: 30px;
min-width: 100px;
padding: 0 9px 0 11px;
gap: 6px;
height: 26px;
min-width: 86px;
padding: 0 8px 0 9px;
border: 1px solid var(--border-strong);
border-radius: 8px;
border-radius: 7px;
background: var(--w);
color: var(--c);
font-family: inherit;
font-size: 0.781rem;
font-size: 0.719rem;
font-weight: 700;
cursor: pointer;
white-space: nowrap;
@@ -108,8 +108,8 @@ function pick(v: ChecklistWorkStatus): void {
border-color: var(--c);
}
.ss-dot {
width: 8px;
height: 8px;
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--c);
flex-shrink: 0;
@@ -119,8 +119,8 @@ function pick(v: ChecklistWorkStatus): void {
text-align: left;
}
.ss-chev {
width: 14px;
height: 14px;
width: 13px;
height: 13px;
}
.ss-menu {
position: fixed;
@@ -2,9 +2,11 @@
// 일정 참석자 아바타 — 사이트 공용 UserAvatar 를 size(px)로 감싸는 얇은 어댑터.
// 렌더링(이미지/실루엣)은 UserAvatar 가 단일 책임지고, 여기선 크기/원형 래퍼만 담당.
// (.sa 클래스는 상세 패널 아바타 스택 CSS 의 훅으로 유지)
// size 는 px 로 받되 rem(÷16)으로 렌더 — 앱 전체 rem 스케일과 정합.
import { computed } from 'vue'
import UserAvatar from '@/components/UserAvatar.vue'
withDefaults(
const props = withDefaults(
defineProps<{
name?: string
avatarUrl?: string | null
@@ -12,12 +14,13 @@ withDefaults(
}>(),
{ name: '', avatarUrl: null, size: 21 },
)
const dim = computed(() => `${props.size / 16}rem`)
</script>
<template>
<span
class="sa"
:style="{ width: `${size}px`, height: `${size}px` }"
:style="{ width: dim, height: dim }"
>
<UserAvatar
:url="avatarUrl"
@@ -227,7 +227,7 @@ const extra = computed(() =>
top: 0;
right: 0;
bottom: 0;
width: 360px;
width: 22.5rem;
z-index: 30;
border-left: 1px solid var(--border);
background: var(--panel);
@@ -242,15 +242,15 @@ const extra = computed(() =>
transform: translateX(0);
}
.type-chip {
font-size: 11px;
font-size: 0.6875rem;
font-weight: 600;
padding: 2px 9px;
border-radius: 6px;
padding: 0.125rem 0.5625rem;
border-radius: 0.375rem;
white-space: nowrap;
line-height: 1.6;
}
.dt-head {
padding: 20px 22px 18px;
padding: 1.25rem 1.375rem 1.125rem;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
@@ -260,8 +260,8 @@ const extra = computed(() =>
justify-content: space-between;
}
.dt-close {
width: 30px;
height: 30px;
width: 1.875rem;
height: 1.875rem;
border-radius: var(--radius-sm);
border: none;
background: transparent;
@@ -269,48 +269,48 @@ const extra = computed(() =>
cursor: pointer;
display: grid;
place-items: center;
margin-right: -6px;
margin-right: -0.375rem;
}
.dt-close:hover {
background: #f1f2f4;
color: var(--text);
}
.dt-close svg {
width: 17px;
height: 17px;
width: 1.0625rem;
height: 1.0625rem;
}
.dt-head h2 {
font-size: 18px;
font-size: 1.125rem;
font-weight: 700;
letter-spacing: -0.3px;
letter-spacing: -0.0187rem;
line-height: 1.4;
margin: 13px 0 8px;
margin: 0.8125rem 0 0.5rem;
word-break: keep-all;
}
.dt-date {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: 13px;
gap: 0.4375rem;
font-size: 0.8125rem;
font-weight: 600;
color: var(--text-2);
}
.dt-date svg {
width: 15px;
height: 15px;
width: 0.9375rem;
height: 0.9375rem;
color: var(--text-3);
}
.dt-dur {
font-size: 11px;
font-size: 0.6875rem;
font-weight: 700;
color: var(--accent);
background: var(--accent-weak);
border-radius: 5px;
padding: 1px 7px;
border-radius: 0.3125rem;
padding: 0.0625rem 0.4375rem;
white-space: nowrap;
}
.dt-body {
padding: 18px 22px 24px;
padding: 1.125rem 1.375rem 1.5rem;
flex: 1;
min-height: 0;
overflow-y: auto;
@@ -318,25 +318,25 @@ const extra = computed(() =>
.dt-list {
display: flex;
flex-direction: column;
gap: 16px;
gap: 1rem;
}
.dt-item {
display: flex;
gap: 14px;
gap: 0.875rem;
align-items: flex-start;
}
.dt-k {
font-size: 12px;
font-size: 0.75rem;
font-weight: 600;
color: var(--text-3);
width: 52px;
width: 3.25rem;
flex-shrink: 0;
padding-top: 2px;
padding-top: 0.125rem;
}
.dt-v {
flex: 1;
min-width: 0;
font-size: 13.5px;
font-size: 0.8438rem;
font-weight: 500;
color: var(--text);
word-break: keep-all;
@@ -349,18 +349,18 @@ const extra = computed(() =>
}
.att-stack {
position: relative;
margin: -3px 0;
margin: -0.1875rem 0;
}
.att-trigger {
display: inline-flex;
align-items: center;
gap: 9px;
gap: 0.5625rem;
border: none;
background: none;
cursor: pointer;
padding: 5px 8px 5px 5px;
margin: -5px -8px -5px -5px;
border-radius: 9px;
padding: 0.3125rem 0.5rem 0.3125rem 0.3125rem;
margin: -0.3125rem -0.5rem -0.3125rem -0.3125rem;
border-radius: 0.5625rem;
}
.att-trigger:hover,
.att-trigger.open {
@@ -372,19 +372,19 @@ const extra = computed(() =>
}
.avstack :deep(.sa),
.avstack .av-more {
margin-left: -9px;
margin-left: -0.5625rem;
box-shadow: 0 0 0 2px var(--panel);
}
.avstack :deep(.sa):first-child {
margin-left: 0;
}
.av-more {
width: 30px;
height: 30px;
width: 1.875rem;
height: 1.875rem;
border-radius: 50%;
display: grid;
place-items: center;
font-size: 12px;
font-size: 0.75rem;
font-weight: 700;
background: #eceef1;
color: var(--text-2);
@@ -396,36 +396,36 @@ const extra = computed(() =>
transition: transform 0.15s ease;
}
.att-chev svg {
width: 15px;
height: 15px;
width: 0.9375rem;
height: 0.9375rem;
}
.att-trigger.open .att-chev {
transform: rotate(180deg);
}
.att-panel {
position: absolute;
top: calc(100% + 6px);
top: calc(100% + 0.375rem);
left: 0;
min-width: 200px;
max-width: 260px;
max-height: 240px;
min-width: 12.5rem;
max-width: 16.25rem;
max-height: 15rem;
overflow-y: auto;
z-index: 20;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 10px;
border-radius: 0.625rem;
box-shadow: var(--shadow-md);
padding: 5px;
padding: 0.3125rem;
}
.att-row {
display: flex;
align-items: center;
gap: 9px;
font-size: 13px;
gap: 0.5625rem;
font-size: 0.8125rem;
font-weight: 500;
color: var(--text);
padding: 6px 8px;
border-radius: 6px;
padding: 0.375rem 0.5rem;
border-radius: 0.375rem;
white-space: nowrap;
}
.att-row:hover {
@@ -433,27 +433,27 @@ const extra = computed(() =>
}
.dt-foot {
flex-shrink: 0;
padding: 14px 22px;
padding: 0.875rem 1.375rem;
border-top: 1px solid var(--border);
background: var(--panel);
}
.dt-actions {
display: flex;
gap: 8px;
gap: 0.5rem;
}
.dt-actions .btn {
flex: 1;
justify-content: center;
height: 36px;
height: 2.25rem;
}
.dt-actions .btn.icon-only {
flex: 0 0 auto;
padding: 0;
width: 36px;
width: 2.25rem;
}
.btn {
border-radius: var(--radius);
font-size: 13.5px;
font-size: 0.8438rem;
font-weight: 600;
border: 1px solid var(--border-strong);
background: var(--panel);
@@ -461,7 +461,7 @@ const extra = computed(() =>
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
gap: 0.375rem;
line-height: 1;
white-space: nowrap;
}
@@ -470,8 +470,8 @@ const extra = computed(() =>
color: var(--text);
}
.btn svg {
width: 15px;
height: 15px;
width: 0.9375rem;
height: 0.9375rem;
}
.btn.danger {
background: #fff;
@@ -418,20 +418,20 @@ function submit(): void {
height: 0.9375rem;
}
.ms-all {
/* 참석자 아바타(ScheduleAvatar :size="20" = 20px)와 동일 크기 */
width: 20px;
height: 20px;
/* 참석자 아바타(ScheduleAvatar :size="20" = 1.25rem)와 동일 크기 */
width: 1.25rem;
height: 1.25rem;
border-radius: 50%;
display: grid;
place-items: center;
font-size: 9px;
font-size: 0.5625rem;
font-weight: 700;
color: #fff;
background: #9298a3;
flex-shrink: 0;
}
.ms-div {
height: 1px;
height: 0.0625rem;
background: var(--border);
margin: 0.25rem 0.125rem;
}
@@ -222,7 +222,7 @@ function onBodyScroll(): void {
flex-direction: column;
overflow: hidden;
border: 1px solid var(--border);
border-radius: 12px;
border-radius: 0.75rem;
box-shadow: var(--shadow-sm);
}
.tl-head-scroll {
@@ -254,27 +254,27 @@ function onBodyScroll(): void {
display: flex;
align-items: center;
justify-content: center;
gap: 7px;
padding: 9px 8px;
gap: 0.4375rem;
padding: 0.5625rem 0.5rem;
text-align: center;
border-left: 1px solid var(--border);
}
.tl-dayhead .sdow {
font-size: 12px;
font-size: 0.75rem;
font-weight: 600;
color: var(--text-3);
letter-spacing: 0.2px;
letter-spacing: 0.0125rem;
}
.tl-dayhead .sd {
display: inline-grid;
place-items: center;
min-width: 26px;
height: 26px;
padding: 0 5px;
font-size: 15px;
min-width: 1.625rem;
height: 1.625rem;
padding: 0 0.3125rem;
font-size: 0.9375rem;
font-weight: 700;
letter-spacing: -0.3px;
border-radius: 13px;
letter-spacing: -0.0187rem;
border-radius: 0.8125rem;
}
.tl-dayhead.is-today .sdow {
color: var(--accent);
@@ -301,9 +301,9 @@ function onBodyScroll(): void {
.tl-label {
display: flex;
align-items: center;
gap: 9px;
padding: 0 8px 0 14px;
font-size: 13.5px;
gap: 0.5625rem;
padding: 0 0.5rem 0 0.875rem;
font-size: 0.8438rem;
font-weight: 600;
color: var(--text);
position: sticky;
@@ -314,9 +314,9 @@ function onBodyScroll(): void {
white-space: nowrap;
}
.tl-label .dot {
width: 11px;
height: 11px;
border-radius: 3px;
width: 0.6875rem;
height: 0.6875rem;
border-radius: 0.1875rem;
flex-shrink: 0;
}
.tl-label .ll-name {
@@ -333,12 +333,12 @@ function onBodyScroll(): void {
left: 0;
display: inline-flex;
align-items: center;
gap: 7px;
padding: 11px 14px;
gap: 0.4375rem;
padding: 0.6875rem 0.875rem;
background: var(--panel);
border: none;
font-family: inherit;
font-size: 13px;
font-size: 0.8125rem;
font-weight: 600;
color: var(--text-3);
cursor: pointer;
@@ -347,8 +347,8 @@ function onBodyScroll(): void {
color: var(--accent);
}
.tl-addcat svg {
width: 15px;
height: 15px;
width: 0.9375rem;
height: 0.9375rem;
}
.tl-track {
@@ -372,19 +372,19 @@ function onBodyScroll(): void {
.tl-bars {
position: relative;
display: grid;
row-gap: 7px;
row-gap: 0.4375rem;
column-gap: 0;
padding: 9px 0;
padding: 0.5625rem 0;
}
.tl-bar {
display: flex;
flex-direction: column;
justify-content: center;
gap: 1px;
margin: 0 6px;
padding: 0 11px;
gap: 0.0625rem;
margin: 0 0.375rem;
padding: 0 0.6875rem;
height: 100%;
border-radius: 7px;
border-radius: 0.4375rem;
background: var(--bar-weak);
border-left: 3px solid var(--bar-color);
cursor: pointer;
@@ -395,10 +395,10 @@ function onBodyScroll(): void {
}
.tl-bar:hover {
box-shadow: 0 3px 10px rgba(20, 24, 33, 0.13);
transform: translateY(-1px);
transform: translateY(-0.0625rem);
}
.tl-bar .bt {
font-size: 12.5px;
font-size: 0.7813rem;
font-weight: 600;
color: var(--text);
line-height: 1.3;
@@ -413,10 +413,10 @@ function onBodyScroll(): void {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
padding: 40px;
gap: 0.625rem;
padding: 2.5rem;
color: var(--text-3);
font-size: 13.5px;
font-size: 0.8438rem;
text-align: center;
}
</style>
@@ -207,39 +207,39 @@ const allOn = computed(
}
.filter-menu {
position: absolute;
top: calc(100% + 7px);
top: calc(100% + 0.4375rem);
right: 0;
z-index: 60;
width: 264px;
width: 16.5rem;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 10px;
border-radius: 0.625rem;
box-shadow: var(--shadow-pop);
padding: 6px;
padding: 0.375rem;
}
.fm-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 7px 8px 9px;
padding: 0.4375rem 0.5rem 0.5625rem;
}
.fm-head > span {
font-size: 11px;
font-size: 0.6875rem;
font-weight: 700;
color: var(--text-3);
letter-spacing: 0.3px;
letter-spacing: 0.0187rem;
white-space: nowrap;
}
.fm-all {
border: none;
background: none;
font-family: inherit;
font-size: 12px;
font-size: 0.75rem;
font-weight: 600;
color: var(--accent);
cursor: pointer;
padding: 2px 5px;
border-radius: 4px;
padding: 0.125rem 0.3125rem;
border-radius: 0.25rem;
white-space: nowrap;
}
.fm-all:hover {
@@ -248,13 +248,13 @@ const allOn = computed(
.fm-list {
display: flex;
flex-direction: column;
max-height: 320px;
max-height: 20rem;
overflow-y: auto;
}
.fm-item {
display: flex;
align-items: center;
border-radius: 6px;
border-radius: 0.375rem;
}
.fm-item:hover {
background: #f4f5f7;
@@ -264,20 +264,20 @@ const allOn = computed(
min-width: 0;
display: flex;
align-items: center;
gap: 9px;
gap: 0.5625rem;
border: none;
background: none;
font-family: inherit;
font-size: 13px;
font-size: 0.8125rem;
font-weight: 600;
padding: 8px;
padding: 0.5rem;
cursor: pointer;
text-align: left;
}
.fm-tog .dot {
width: 10px;
height: 10px;
border-radius: 3px;
width: 0.625rem;
height: 0.625rem;
border-radius: 0.1875rem;
flex-shrink: 0;
}
.fm-tog .fm-label {
@@ -289,21 +289,21 @@ const allOn = computed(
white-space: nowrap;
}
.fm-tog .fm-ct {
font-size: 11px;
font-size: 0.6875rem;
font-weight: 700;
color: var(--text-3);
}
.fm-tog .fm-check {
width: 16px;
height: 16px;
width: 1rem;
height: 1rem;
flex-shrink: 0;
color: var(--accent);
display: grid;
place-items: center;
}
.fm-tog .fm-check svg {
width: 15px;
height: 15px;
width: 0.9375rem;
height: 0.9375rem;
}
.fm-item:not(.on) .fm-label {
color: var(--text-3);
@@ -313,19 +313,19 @@ const allOn = computed(
}
.fm-acts {
display: none;
gap: 1px;
padding-right: 5px;
gap: 0.0625rem;
padding-right: 0.3125rem;
}
.fm-item:hover .fm-acts {
display: flex;
}
.fm-act {
width: 25px;
height: 25px;
width: 1.5625rem;
height: 1.5625rem;
border: none;
background: transparent;
color: var(--text-3);
border-radius: 5px;
border-radius: 0.3125rem;
cursor: pointer;
display: grid;
place-items: center;
@@ -335,30 +335,30 @@ const allOn = computed(
color: var(--text);
}
.fm-act svg {
width: 13px;
height: 13px;
width: 0.8125rem;
height: 0.8125rem;
}
.fm-add {
display: flex;
align-items: center;
gap: 7px;
gap: 0.4375rem;
width: 100%;
border: none;
border-top: 1px solid var(--border);
background: none;
font-family: inherit;
font-size: 13px;
font-size: 0.8125rem;
font-weight: 600;
color: var(--accent);
padding: 10px 8px 6px;
margin-top: 4px;
padding: 0.625rem 0.5rem 0.375rem;
margin-top: 0.25rem;
cursor: pointer;
}
.fm-add:hover {
color: var(--accent-hover);
}
.fm-add svg {
width: 15px;
height: 15px;
width: 0.9375rem;
height: 0.9375rem;
}
</style>
+14 -10
View File
@@ -222,7 +222,10 @@ async function onDeleteCategory(id: string): Promise<void> {
align-items: center;
/* 프로젝트 목록 등 전역 .toolbar 와 동일한 컨트롤 간격 */
gap: 0.625rem;
padding: 11px 24px;
/* 가로 여백은 .page(0 1.5rem)·콘텐츠 영역과 동일하게 1.5rem */
padding: 0.6875rem 1.5rem;
/* 전역 .toolbar 의 margin-bottom(0.875rem) 상속 차단 — 고정 바라 여분 마진 불필요 */
margin-bottom: 0;
flex-shrink: 0;
border-bottom: 1px solid var(--border);
background: var(--panel);
@@ -230,14 +233,14 @@ async function onDeleteCategory(id: string): Promise<void> {
.datenav {
display: flex;
align-items: center;
gap: 8px;
gap: 0.5rem;
}
.datenav .range {
font-size: 15.5px;
font-size: 0.969rem;
font-weight: 700;
letter-spacing: -0.3px;
letter-spacing: -0.01875rem;
white-space: nowrap;
margin: 0 6px;
margin: 0 0.375rem;
}
.navgroup {
display: flex;
@@ -250,8 +253,8 @@ async function onDeleteCategory(id: string): Promise<void> {
margin-left: -1px;
}
.navbtn {
width: 32px;
height: 32px;
width: 2rem;
height: 2rem;
border: 1px solid var(--border-strong);
background: #fff;
color: var(--text-2);
@@ -265,8 +268,8 @@ async function onDeleteCategory(id: string): Promise<void> {
z-index: 1;
}
.navbtn svg {
width: 16px;
height: 16px;
width: 1rem;
height: 1rem;
}
.spacer {
flex: 1;
@@ -285,7 +288,8 @@ async function onDeleteCategory(id: string): Promise<void> {
min-width: 0;
overflow: hidden;
display: flex;
padding: 18px 24px 28px;
/* 콘텐츠 영역 상하좌우 여백 통일(1.5rem) — 툴바 가로 여백·.page 와 일치 */
padding: 1.5rem;
}
.detail-backdrop {
position: absolute;
+19 -82
View File
@@ -314,7 +314,13 @@ const CAT_COLOR: Record<ChecklistCategory, string> = {
보안: '#dc2626',
부가: '#b7791f',
}
type CatTab = ChecklistCategory | 'all'
// 보고서 모달 탭 — 카테고리(전체 탭 없음). 기본 탭은 항목이 있는 첫 분류.
type CatTab = ChecklistCategory
function firstCat(items: { category: ChecklistCategory }[]): ChecklistCategory {
return (
CHECKLIST_CATEGORIES.find((c) => items.some((i) => i.category === c)) ?? '필수'
)
}
// KST(UTC+9) 기준 'yyyy-MM-dd' — '당일' 판정
function kstDate(iso: string | Date): string {
@@ -349,12 +355,11 @@ const reportItems = ref<
>([])
const reportNote = ref('')
const reportSubmitting = ref(false)
const writeTab = ref<CatTab>('all')
const writeTab = ref<CatTab>('필수')
function openReportForm(cp: TaskCheckpointView) {
if (!task.value) return
reportingCpId.value = cp.id
writeTab.value = 'all'
// 현재(마지막 보고) 상태로 프리필
reportItems.value = task.value.checklist
.filter((c) => c.id)
@@ -364,16 +369,11 @@ function openReportForm(cp: TaskCheckpointView) {
category: c.category,
workStatus: c.workStatus ?? 'todo',
}))
writeTab.value = firstCat(reportItems.value)
reportNote.value = ''
}
// 작성 모달 — 탭별 그룹/개수/요약
// 작성 모달 — 선택 분류의 그룹/개수/요약
const writeGroups = computed(() => {
if (writeTab.value === 'all') {
return CHECKLIST_CATEGORIES.map((c) => ({
cat: c,
items: reportItems.value.filter((i) => i.category === c),
})).filter((g) => g.items.length)
}
const c = writeTab.value
return [{ cat: c, items: reportItems.value.filter((i) => i.category === c) }]
})
@@ -387,11 +387,7 @@ const writeSummary = computed(() =>
})),
)
// 현재 탭에서 보이는 항목 수(0이면 빈 안내 표시)
const writeVisible = computed(() =>
writeTab.value === 'all'
? reportItems.value.length
: writeCatCount(writeTab.value),
)
const writeVisible = computed(() => writeCatCount(writeTab.value))
function cancelReportForm() {
reportingCpId.value = null
reportItems.value = []
@@ -418,33 +414,24 @@ async function submitReport() {
// --- 보고 기록 열람(모달) — 전체/카테고리 탭 ---
const openedReport = ref<CheckpointReportView | null>(null)
const viewTab = ref<CatTab>('all')
const viewTab = ref<CatTab>('필수')
// 보고 완료 체크포인트 클릭 → 해당 체크포인트의 최신 보고를 모달로 연다
function openReportModal(cp: TaskCheckpointView) {
const r = task.value?.checkpointReports.find((x) => x.checkpointId === cp.id)
if (!r) return
openedReport.value = r
viewTab.value = 'all'
viewTab.value = firstCat(r.items)
}
const viewItems = computed(() => openedReport.value?.items ?? [])
const viewGroups = computed(() => {
const items = viewItems.value
if (viewTab.value === 'all') {
return CHECKLIST_CATEGORIES.map((c) => ({
cat: c,
items: items.filter((i) => i.category === c),
})).filter((g) => g.items.length)
}
const c = viewTab.value
return [{ cat: c, items: items.filter((i) => i.category === c) }]
return [{ cat: c, items: viewItems.value.filter((i) => i.category === c) }]
})
function viewCatCount(cat: ChecklistCategory): number {
return viewItems.value.filter((i) => i.category === cat).length
}
// 현재 탭에서 보이는 항목 수(0이면 빈 안내 표시)
const viewVisible = computed(() =>
viewTab.value === 'all' ? viewItems.value.length : viewCatCount(viewTab.value),
)
const viewVisible = computed(() => viewCatCount(viewTab.value))
const reporterInitial = computed(() => openedReport.value?.reporterName?.[0] ?? '?')
// 담당자 액션 카드 테마(시안): changes→빨강 / review→앰버 / done→초록 / 그 외→accent
const assigneeCardClass = computed(() => {
@@ -1674,14 +1661,6 @@ function badgeFor(name: string | undefined): { label: string; cls: string } {
class="pr-tabs"
role="tablist"
>
<button
type="button"
class="pr-tab"
:class="{ on: viewTab === 'all' }"
@click="viewTab = 'all'"
>
전체<span class="pr-tct">{{ viewItems.length }}</span>
</button>
<button
v-for="c in CHECKLIST_CATEGORIES"
:key="c"
@@ -1702,15 +1681,6 @@ function badgeFor(name: string | undefined): { label: string; cls: string } {
v-for="g in viewGroups"
:key="g.cat"
>
<div
v-if="viewTab === 'all'"
class="pr-ghead"
>
<span
class="pr-gdot"
:style="{ background: CAT_COLOR[g.cat] }"
/>{{ g.cat }}<span class="pr-gct">· {{ g.items.length }}</span>
</div>
<div
v-for="(it, i) in g.items"
:key="g.cat + '-' + i"
@@ -1733,7 +1703,7 @@ function badgeFor(name: string | undefined): { label: string; cls: string } {
v-if="viewVisible === 0"
class="pr-empty"
>
{{ viewTab === 'all' ? '보고된 항목이 없습니다.' : `'${viewTab}' 분류에 항목이 없습니다.` }}
'{{ viewTab }}' 분류에 항목이 없습니다.
</div>
</div>
@@ -1810,14 +1780,6 @@ function badgeFor(name: string | undefined): { label: string; cls: string } {
class="pr-tabs"
role="tablist"
>
<button
type="button"
class="pr-tab"
:class="{ on: writeTab === 'all' }"
@click="writeTab = 'all'"
>
전체<span class="pr-tct">{{ reportItems.length }}</span>
</button>
<button
v-for="c in CHECKLIST_CATEGORIES"
:key="c"
@@ -1838,15 +1800,6 @@ function badgeFor(name: string | undefined): { label: string; cls: string } {
v-for="g in writeGroups"
:key="g.cat"
>
<div
v-if="writeTab === 'all'"
class="pr-ghead"
>
<span
class="pr-gdot"
:style="{ background: CAT_COLOR[g.cat] }"
/>{{ g.cat }}<span class="pr-gct">· {{ g.items.length }}</span>
</div>
<div
v-for="it in g.items"
:key="it.id"
@@ -1861,7 +1814,7 @@ function badgeFor(name: string | undefined): { label: string; cls: string } {
v-if="writeVisible === 0"
class="pr-empty"
>
{{ writeTab === 'all' ? '보고할 할 일이 없습니다. 메모만 전송됩니다.' : `'${writeTab}' 분류에 할 일이 없습니다.` }}
'{{ writeTab }}' 분류에 일이 없습니다.
</div>
</div>
@@ -3807,24 +3760,6 @@ a.file-info {
padding: 0 1.5rem 0.5rem;
border-top: 1px solid var(--border);
}
.pr-ghead {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 1rem 0.125rem 0.4375rem;
font-size: 0.719rem;
font-weight: 700;
color: var(--text-3);
}
.pr-ghead .pr-gdot {
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
}
.pr-ghead .pr-gct {
color: var(--text-3);
font-weight: 600;
}
.pr-task,
.pr-titem {
display: flex;
@@ -3947,6 +3882,8 @@ a.file-info {
gap: 0.75rem;
padding: 0.875rem 1.5rem;
border-top: 1px solid var(--border);
/* 다른 모달 푸터(BaseModal .bm-foot)와 동일한 배경 */
background: #fafbfc;
}
.pr-foot .pr-sp {
flex: 1;