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>
This commit is contained in:
2026-06-23 00:38:59 +09:00
parent 61d247de08
commit 7568637de2
8 changed files with 178 additions and 234 deletions
+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;