<script setup lang="ts">
// 업무 상세 — 내용/체크리스트/댓글·활동 + 사이드(승인/상태/담당/마감/첨부/지시자)
// + 삭제·승인·수정요청 모달 + AI 채팅 패널
import {
  computed,
  defineComponent,
  h,
  onMounted,
  onUnmounted,
  ref,
  watch,
} from 'vue'
import { useRoute, useRouter, RouterLink } from 'vue-router'
import AppShell from '@/layouts/AppShell.vue'
import {
  CHECKLIST_CATEGORIES,
  type ChecklistCategory,
  type ChecklistItem,
  type ChecklistWorkStatus,
  type CheckpointReportView,
  type Comment,
  type TaskCheckpointView,
  type TaskDetail,
  type TaskStatus,
  type User,
} from '@/mock/relay.mock'
import type { ChecklistReviewItem } from '@/types/task'
import { useTaskStore } from '@/stores/task.store'
import { useAuthStore } from '@/stores/auth.store'
import { useDialog } from '@/composables/useDialog'
import UserAvatar from '@/components/UserAvatar.vue'
import { attachmentKindOf, fileBadge } from '@/shared/utils/format'

const route = useRoute()
const router = useRouter()
const projectId = computed(() => String(route.params.projectId))
const taskId = computed(() => Number(route.params.taskId))

const taskStore = useTaskStore()
const authStore = useAuthStore()
const dialog = useDialog()

// 업무 상세 — API 연동
const task = ref<TaskDetail | null>(null)
async function loadTask() {
  try {
    task.value = await taskStore.fetchOne(projectId.value, taskId.value)
  } catch {
    task.value = null // 404 등은 인터셉터가 알림 처리
  }
}
watch(taskId, loadTask, { immediate: true })

// 상태 배지 클래스(prog/review/done/changes)
const statusClass = computed<TaskStatus>(() => task.value?.status ?? 'todo')

// 체크리스트 진행률
const checklistDone = computed(() => task.value?.checklist.filter((c) => c.done).length ?? 0)
const checklistPct = computed(() => {
  const total = task.value?.checklist.length ?? 0
  return total ? Math.round((checklistDone.value / total) * 100) : 0
})

// 수정 요청(changes) 상태 — 미완료 항목을 '보완 필요(X)'로 표기
const isChangesReview = computed(() => task.value?.status === 'changes')

// 카테고리별 그룹(항목 있는 카테고리만, 필수/관리/보안/부가 고정 순서)
const checklistGroups = computed(() =>
  CHECKLIST_CATEGORIES.map((cat) => ({
    category: cat,
    items: task.value?.checklist.filter((c) => c.category === cat) ?? [],
  })).filter((g) => g.items.length > 0),
)
// 카테고리 → 색상 클래스(필수=sec-1 …)
function categoryClass(cat: ChecklistCategory): string {
  const map: Record<ChecklistCategory, string> = {
    필수: 'sec-1',
    관리: 'sec-2',
    보안: 'sec-3',
    부가: 'sec-4',
  }
  return map[cat]
}

// 본문 멘션(@…)을 강조 span 으로 변환
function renderMention(text: string): string {
  const escaped = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
  return escaped.replace(/(@[^\s]+)/g, '<span class="mention">$1</span>')
}

// 활동 타임라인 아이콘 — name 으로 SVG path 를 분기하는 작은 렌더 컴포넌트
const ACT_PATHS: Record<string, string> = {
  'check-circle': '<circle cx="12" cy="12" r="9"/><path d="M9 12l2 2 4-4"/>',
  check: '<path d="M20 6 9 17l-5-5"/>',
  paperclip:
    '<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/>',
  calendar: '<rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/>',
  users:
    '<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/>',
  flag: '<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/>',
}
const ActivityIcon = defineComponent({
  props: { name: { type: String, required: true } },
  setup(props) {
    return () =>
      h('svg', {
        viewBox: '0 0 24 24',
        fill: 'none',
        stroke: 'currentColor',
        'stroke-width': props.name === 'check' ? '2.6' : '2',
        'stroke-linecap': 'round',
        'stroke-linejoin': 'round',
        innerHTML: ACT_PATHS[props.name] ?? '',
      })
  },
})

/* ============================================================
 * 더보기 메뉴 / 모달
 * ============================================================ */
const moreOpen = ref(false)
const showChanges = ref(false)

function closeAllOverlays() {
  showChanges.value = false
  moreOpen.value = false
}

// 삭제 — 공통 확인 다이얼로그로 확인받고 DELETE 후 프로젝트 상세로 이동
async function askDelete() {
  moreOpen.value = false
  if (!task.value) return
  const ok = await dialog.confirm(
    `#${task.value.id} ${task.value.title} 업무와 체크리스트·댓글·첨부파일이 영구적으로 삭제됩니다. 이 작업은 되돌릴 수 없습니다.`,
    { title: '업무를 삭제할까요?', variant: 'danger', confirmText: '삭제' },
  )
  if (!ok) return
  try {
    await taskStore.remove(projectId.value, task.value.id)
    router.push(`/projects/${projectId.value}`)
  } catch {
    // 인터셉터가 알림 처리
  }
}

// 할 일 완료 검토(지시자) — 모든 항목 '완료'면 승인, 보완 항목이 있으면 메시지와 함께 수정 요청
const changesNote = ref('')
// 항목 ID → 완료 여부(완료=true / 보완=false)
const reviewChecks = ref<Record<string, boolean>>({})
// 항목 ID → 보완 내용(항목별 개별 입력)
const reviewNotes = ref<Record<string, string>>({})
const reviewTotal = computed(() => task.value?.checklist.length ?? 0)
const reviewDoneCount = computed(
  () => Object.values(reviewChecks.value).filter(Boolean).length,
)
const reviewTodoCount = computed(() => reviewTotal.value - reviewDoneCount.value)
const reviewPct = computed(() =>
  reviewTotal.value
    ? Math.round((reviewDoneCount.value / reviewTotal.value) * 100)
    : 0,
)
// 보완 항목이 없으면 곧바로 승인 가능
const reviewAllDone = computed(() => reviewTodoCount.value === 0)
// 담당자 표기(푸터)
const assigneeNames = computed(() => {
  const names = task.value?.assignees.map((a) => a.name) ?? []
  if (names.length === 0) return '담당자'
  if (names.length === 1) return names[0]
  return `${names[0]} 외 ${names.length - 1}명`
})
// 수정 요청 모달 열기 — 모든 항목을 '완료'로 초기화(담당자가 완료를 주장하며 요청)
function openChanges() {
  const checks: Record<string, boolean> = {}
  const notes: Record<string, string> = {}
  for (const item of task.value?.checklist ?? []) {
    if (!item.id) continue
    checks[item.id] = true
    notes[item.id] = item.reviewNote ?? ''
  }
  reviewChecks.value = checks
  reviewNotes.value = notes
  changesNote.value = ''
  showChanges.value = true
}
function reviewState(item: ChecklistItem): boolean {
  return item.id ? !!reviewChecks.value[item.id] : false
}
function setReview(item: ChecklistItem, done: boolean) {
  if (!item.id) return
  reviewChecks.value = { ...reviewChecks.value, [item.id]: done }
}
function reviewNoteOf(item: ChecklistItem): string {
  return item.id ? (reviewNotes.value[item.id] ?? '') : ''
}
function setReviewNote(item: ChecklistItem, val: string) {
  if (!item.id) return
  reviewNotes.value = { ...reviewNotes.value, [item.id]: val }
}
// 카테고리 → 그룹 도트 색 클래스
function groupClass(cat: ChecklistCategory): string {
  const map: Record<ChecklistCategory, string> = {
    필수: 'g-req',
    관리: 'g-mng',
    보안: 'g-sec',
    부가: 'g-ext',
  }
  return map[cat]
}
// 검토 제출 — 전부 완료면 승인, 보완 항목이 있으면 수정 요청
async function submitChanges() {
  if (!task.value) return
  try {
    if (reviewAllDone.value) {
      task.value = await taskStore.approve(projectId.value, task.value.id)
    } else {
      const checklist: ChecklistReviewItem[] = (task.value.checklist ?? [])
        .filter((c): c is ChecklistItem & { id: string } => !!c.id)
        .map((c) => {
          const done = !!reviewChecks.value[c.id]
          // 보완(미완료) 항목만 항목별 보완 내용 동봉
          if (done) return { id: c.id, done }
          const note = (reviewNotes.value[c.id] ?? '').trim()
          return { id: c.id, done, note: note || undefined }
        })
      task.value = await taskStore.requestChanges(
        projectId.value,
        task.value.id,
        changesNote.value.trim() || undefined,
        checklist.length ? checklist : undefined,
      )
    }
  } catch {
    // 인터셉터가 알림 처리
  } finally {
    showChanges.value = false
    changesNote.value = ''
  }
}

// 담당자 액션 — 업무 시작(todo→prog) / 승인 요청(prog/changes→review)
async function changeTo(status: TaskStatus) {
  if (!task.value) return
  try {
    task.value = await taskStore.changeStatus(projectId.value, task.value.id, status)
  } catch {
    // 인터셉터가 알림 처리
  }
}
function startTask() {
  void changeTo('prog')
}
// 승인 요청 — 승인 워크플로우 엔드포인트로 전송(메시지 없음)
async function sendApproval() {
  if (!task.value) return
  try {
    task.value = await taskStore.requestApproval(projectId.value, task.value.id)
  } catch {
    // 인터셉터가 알림 처리
  }
}

/* ============================================================
 * 댓글 / 활동 탭
 * ============================================================ */
const activeTab = ref<'comments' | 'activity'>('comments')

// 댓글 — 상세(API)에서 받아온 목록을 사용. 작성/답글은 store 를 통해 영속
const comments = computed<Comment[]>(() => task.value?.comments ?? [])
const replyOpen = ref<number | null>(null)
const replyText = ref('')
const commentText = ref('')

// 현재 로그인 사용자 (댓글 작성자)
const me = computed<User>(() => {
  const u = authStore.user
  return {
    id: u?.id ?? '',
    name: u?.name ?? '나',
    role: u?.role ?? '',
    avatarUrl: u?.avatarUrl ?? null,
  }
})

// 현재 사용자의 이 업무에 대한 역할(액션 분기용)
const isIssuer = computed(
  () => !!task.value && !!me.value.id && task.value.issuer.id === me.value.id,
)
const isAssignee = computed(
  () => !!task.value && task.value.assignees.some((a) => a.id === me.value.id),
)

/* ============================================================
 * 중간 점검(체크포인트) — 작업자가 점검일 당일에 보고서로 전송, 지시자는 기록 열람
 * ============================================================ */
const WORK_STATUS_LABEL: Record<ChecklistWorkStatus, string> = {
  todo: '미착수',
  doing: '진행 중',
  done: '완료',
}
const WORK_STATUS_OPTIONS: ChecklistWorkStatus[] = ['todo', 'doing', 'done']

// KST(UTC+9) 기준 'yyyy-MM-dd' — '당일' 판정
function kstDate(iso: string | Date): string {
  const t = typeof iso === 'string' ? new Date(iso).getTime() : iso.getTime()
  return new Date(t + 9 * 60 * 60 * 1000).toISOString().slice(0, 10)
}
const todayKst = computed(() => kstDate(new Date()))

type CheckpointState = 'future' | 'today' | 'reported' | 'missed'
const CP_STATE_LABEL: Record<CheckpointState, string> = {
  future: '예정',
  today: '오늘 점검',
  reported: '보고 완료',
  missed: '누락',
}
// 체크포인트 상태 — 보고됨 / 당일 / 지남(누락) / 예정
function checkpointState(cp: TaskCheckpointView): CheckpointState {
  const reported = task.value?.checkpointReports.some((r) => r.checkpointId === cp.id) ?? false
  if (reported) return 'reported'
  const cpDay = kstDate(cp.checkAt)
  if (cpDay === todayKst.value) return 'today'
  if (cpDay < todayKst.value) return 'missed'
  return 'future'
}

// --- 보고서 작성 폼(인라인, 점검일 당일·담당자만) ---
const reportingCpId = ref<string | null>(null)
const reportItems = ref<{ id: string; text: string; workStatus: ChecklistWorkStatus }[]>([])
const reportNote = ref('')
const reportSubmitting = ref(false)

function openReportForm(cp: TaskCheckpointView) {
  if (!task.value) return
  reportingCpId.value = cp.id
  // 현재(마지막 보고) 상태로 프리필
  reportItems.value = task.value.checklist
    .filter((c) => c.id)
    .map((c) => ({
      id: c.id as string,
      text: c.text,
      workStatus: c.workStatus ?? 'todo',
    }))
  reportNote.value = ''
}
function cancelReportForm() {
  reportingCpId.value = null
  reportItems.value = []
  reportNote.value = ''
}
async function submitReport() {
  if (!task.value || !reportingCpId.value || reportSubmitting.value) return
  reportSubmitting.value = true
  try {
    task.value = await taskStore.submitCheckpointReport(
      projectId.value,
      task.value.id,
      reportingCpId.value,
      reportItems.value.map((it) => ({ id: it.id, workStatus: it.workStatus })),
      reportNote.value.trim() || undefined,
    )
    cancelReportForm()
  } catch {
    // 인터셉터가 알림 처리
  } finally {
    reportSubmitting.value = false
  }
}

// --- 보고 기록 열람(모달) ---
const openedReport = ref<CheckpointReportView | null>(null)
const reportTab = ref<ChecklistCategory>('필수')
// 보고 완료 체크포인트 클릭 → 해당 체크포인트의 최신 보고를 모달로 연다
function openReportModal(cp: TaskCheckpointView) {
  const r = task.value?.checkpointReports.find((x) => x.checkpointId === cp.id)
  if (!r) return
  openedReport.value = r
  reportTab.value = '필수'
}
function reportItemsByCat(cat: ChecklistCategory) {
  return openedReport.value?.items.filter((it) => it.category === cat) ?? []
}
function reportCatCount(cat: ChecklistCategory): number {
  return reportItemsByCat(cat).length
}
// 담당자 액션 카드 테마(시안): changes→빨강 / review→앰버 / done→초록 / 그 외→accent
const assigneeCardClass = computed(() => {
  switch (task.value?.status) {
    case 'changes':
      return 'changes'
    case 'review':
      return 'review'
    case 'done':
      return 'done'
    default:
      return 'request'
  }
})

function toggleReply(index: number) {
  replyOpen.value = replyOpen.value === index ? null : index
  replyText.value = ''
}
async function submitReply(index: number) {
  const text = replyText.value.trim()
  const target = comments.value[index]
  if (!text || !target?.id || !task.value) return
  try {
    task.value = await taskStore.addReply(projectId.value, task.value.id, target.id, text)
  } catch {
    // 인터셉터가 알림 처리
  }
  replyText.value = ''
  replyOpen.value = null
}
async function submitComment() {
  const text = commentText.value.trim()
  if (!text || !task.value) return
  try {
    task.value = await taskStore.addComment(projectId.value, task.value.id, text)
  } catch {
    // 인터셉터가 알림 처리
  }
  commentText.value = ''
}

const commentTotal = computed(
  () => comments.value.length + comments.value.reduce((sum, c) => sum + c.replies.length, 0),
)

/* ============================================================
 * 파일 첨부 — 상세 화면은 목록 표시 전용(업로드/삭제는 작성·편집 화면에서만).
 * ============================================================ */

/* ============================================================
 * 키보드 / 외부 클릭 처리
 * ============================================================ */
function onKeydown(e: KeyboardEvent) {
  if (e.key === 'Escape') {
    closeAllOverlays()
  }
}
function onDocClick() {
  moreOpen.value = false
}
onMounted(() => {
  window.addEventListener('keydown', onKeydown)
  document.addEventListener('click', onDocClick)
})
onUnmounted(() => {
  window.removeEventListener('keydown', onKeydown)
  document.removeEventListener('click', onDocClick)
})

// 파일명(확장자) → 종류 배지(라벨/색상). 활동·댓글은 파일명만 있으므로 종류를 파생
function badgeFor(name: string | undefined): { label: string; cls: string } {
  return fileBadge(attachmentKindOf(name ?? ''))
}
</script>

<template>
  <!--
    이 화면의 v-html 은 모두 내부에서 생성한 신뢰 가능한 마크업(활동/댓글 조립 마크업)만 렌더한다.
    사용자 입력은 {{ }} 로 이스케이프하거나 renderMention() 에서 이스케이프 후 처리하므로 XSS 위험이 없다.
  -->
  <!-- eslint-disable vue/no-v-html -->
  <AppShell>
    <div
      v-if="task"
      class="page"
    >
      <div class="crumb">
        <RouterLink
          to="/projects"
          class="lnk"
        >
          프로젝트
        </RouterLink>
        <span class="sep">/</span>
        <RouterLink
          :to="`/projects/${projectId}`"
          class="lnk"
        >
          {{ task.projectName }}
        </RouterLink>
        <span class="sep">/</span>
        <RouterLink
          :to="`/projects/${projectId}`"
          class="lnk"
        >
          업무
        </RouterLink>
        <span class="sep">/</span>
        <b>#{{ task.id }}</b>
      </div>

      <div class="pagehead">
        <div class="ph-main">
          <div class="ph-titlerow">
            <span
              class="st-badge bordered"
              :class="statusClass"
            >{{ task.statusLabel }}</span>
            <span class="ph-tid">#{{ task.id }}</span>
          </div>
          <h1 class="ph-title">
            {{ task.title }}
          </h1>
        </div>
        <div class="actions">
          <RouterLink
            class="btn"
            :to="`/projects/${projectId}/tasks/${task.id}/edit`"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <path d="M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z" />
            </svg>
            수정
          </RouterLink>
          <div
            class="more-wrap"
            @click.stop
          >
            <button
              class="btn icon"
              type="button"
              title="더보기"
              @click="moreOpen = !moreOpen"
            >
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
              >
                <circle
                  cx="12"
                  cy="5"
                  r="1.5"
                /><circle
                  cx="12"
                  cy="12"
                  r="1.5"
                /><circle
                  cx="12"
                  cy="19"
                  r="1.5"
                />
              </svg>
            </button>
            <div
              class="menu"
              :class="{ open: moreOpen }"
            >
              <button
                class="menu-item"
                type="button"
              >
                <svg
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                ><rect
                  x="9"
                  y="9"
                  width="13"
                  height="13"
                  rx="2"
                /><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" /></svg>
                업무 복제
              </button>
              <button
                class="menu-item"
                type="button"
              >
                <svg
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                ><path d="M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1" /><path d="M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1" /></svg>
                링크 복사
              </button>
              <div class="menu-sep" />
              <button
                class="menu-item danger"
                type="button"
                @click="askDelete"
              >
                <svg
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                ><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" /><path d="M10 11v6M14 11v6" /></svg>
                업무 삭제
              </button>
            </div>
          </div>
        </div>
      </div>

      <div class="layout">
        <!-- 메인 -->
        <section class="card main-card">
          <div class="sect">
            <div class="sect-label">
              업무 내용
            </div>
            <div class="content">
              <p
                v-for="(para, i) in task.content"
                :key="i"
                v-html="renderMention(para)"
              />
            </div>
          </div>

          <div class="sect">
            <div class="sect-label">
              해야 할 일
              <span class="pr">
                <span class="progress-bar"><i :style="{ width: checklistPct + '%' }" /></span>
                <span class="progress-txt">{{ checklistDone }} / {{ task.checklist.length }} 완료</span>
              </span>
            </div>
            <!--
              체크리스트는 읽기 전용 — 사용자가 개별 토글/추가/삭제하지 않는다.
              완료 처리는 승인 워크플로우(승인 완료 시 일괄 완료)로만 이루어진다.
            -->
            <div class="checklist">
              <div
                v-for="group in checklistGroups"
                :key="group.category"
                class="cat-group"
              >
                <div
                  class="cat-group-head"
                  :class="categoryClass(group.category)"
                >
                  <span class="cat-dot" />
                  <span class="cat-name">{{ group.category }}</span>
                  <span class="cat-count">{{ group.items.length }}</span>
                </div>
                <div
                  v-for="(item, i) in group.items"
                  :key="item.id ?? i"
                  class="citem-wrap"
                >
                  <div
                    class="citem"
                    :class="{ done: item.done, todo: !item.done && isChangesReview }"
                  >
                    <span
                      class="cbox"
                      :class="{ done: item.done, todo: !item.done && isChangesReview }"
                    >
                      <svg
                        v-if="item.done"
                        viewBox="0 0 24 24"
                        fill="none"
                        stroke="currentColor"
                        stroke-width="3.5"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                      ><path d="M20 6 9 17l-5-5" /></svg>
                      <svg
                        v-else-if="isChangesReview"
                        viewBox="0 0 24 24"
                        fill="none"
                        stroke="currentColor"
                        stroke-width="3.5"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                      ><path d="M18 6 6 18M6 6l12 12" /></svg>
                    </span>
                    <span class="ctext">{{ item.text }}</span>
                    <span
                      v-if="!item.done && isChangesReview"
                      class="citem-tag"
                    >보완 필요</span>
                  </div>
                  <!-- 항목별 보완 내용(수정 요청 시 입력된 경우) -->
                  <div
                    v-if="!item.done && isChangesReview && item.reviewNote"
                    class="citem-note"
                  >
                    {{ item.reviewNote }}
                  </div>
                </div>
              </div>
              <div
                v-if="checklistGroups.length === 0"
                class="check-empty"
              >
                등록된 할 일이 없습니다.
              </div>
            </div>
          </div>

          <!-- 댓글 / 활동 -->
          <div class="sect">
            <div class="thread-tabs">
              <button
                class="thread-tab"
                :class="{ active: activeTab === 'comments' }"
                type="button"
                @click="activeTab = 'comments'"
              >
                댓글 <span class="tt-count">{{ commentTotal }}</span>
              </button>
              <button
                class="thread-tab"
                :class="{ active: activeTab === 'activity' }"
                type="button"
                @click="activeTab = 'activity'"
              >
                활동 <span class="tt-count">{{ task.activities.length }}</span>
              </button>
            </div>

            <!-- 댓글 패널 -->
            <div
              v-show="activeTab === 'comments'"
              class="thread-panel"
            >
              <div
                v-for="(c, i) in comments"
                :key="i"
                class="comment"
              >
                <span class="c-av"><UserAvatar
                  :url="c.author.avatarUrl"
                  :name="c.author.name"
                /></span>
                <div class="c-body">
                  <div class="c-head">
                    <span class="c-name">{{ c.author.name }}</span>
                    <span
                      v-if="c.roleBadge"
                      class="c-role"
                    >{{ c.roleBadge }}</span>
                    <span class="c-time">{{ c.time }}</span>
                  </div>
                  <div class="c-text">
                    {{ c.text }}
                  </div>
                  <div
                    v-if="c.file"
                    class="c-file"
                  >
                    <span
                      class="fi"
                      :class="badgeFor(c.file).cls"
                    >{{ badgeFor(c.file).label }}</span>{{ c.file }}
                  </div>

                  <div
                    v-if="c.replies.length"
                    class="c-replies"
                  >
                    <div
                      v-for="(r, ri) in c.replies"
                      :key="ri"
                      class="c-reply"
                    >
                      <span class="c-av"><UserAvatar
                        :url="r.author.avatarUrl"
                        :name="r.author.name"
                      /></span>
                      <div class="c-body">
                        <div class="c-head">
                          <span class="c-name">{{ r.author.name }}</span><span class="c-time">{{ r.time }}</span>
                        </div>
                        <div class="c-text">
                          {{ r.text }}
                        </div>
                      </div>
                    </div>
                  </div>

                  <div class="c-actions">
                    <button
                      class="c-reply-btn"
                      type="button"
                      @click="toggleReply(i)"
                    >
                      <svg
                        viewBox="0 0 24 24"
                        fill="none"
                        stroke="currentColor"
                        stroke-width="2"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                      ><polyline points="9 17 4 12 9 7" /><path d="M20 18v-2a4 4 0 0 0-4-4H4" /></svg>
                      답글
                    </button>
                  </div>

                  <div
                    class="reply-form"
                    :class="{ open: replyOpen === i }"
                  >
                    <span class="c-av"><UserAvatar
                      :url="me.avatarUrl"
                      :name="me.name"
                    /></span>
                    <div class="field">
                      <textarea
                        v-model="replyText"
                        placeholder="답글을 입력하세요…"
                      />
                      <div class="crow">
                        <button
                          class="btn ghost sm"
                          type="button"
                          @click="toggleReply(i)"
                        >
                          취소
                        </button>
                        <button
                          class="btn primary sm"
                          type="button"
                          @click="submitReply(i)"
                        >
                          답글 등록
                        </button>
                      </div>
                    </div>
                  </div>
                </div>
              </div>

              <div class="composer">
                <span class="c-av"><UserAvatar
                  :url="me.avatarUrl"
                  :name="me.name"
                /></span>
                <div class="field">
                  <textarea
                    v-model="commentText"
                    placeholder="댓글을 입력하세요…"
                  />
                  <div class="crow">
                    <button
                      class="btn primary"
                      type="button"
                      @click="submitComment"
                    >
                      댓글 등록
                    </button>
                  </div>
                </div>
              </div>
            </div>

            <!-- 활동 패널 -->
            <div
              v-show="activeTab === 'activity'"
              class="thread-panel"
            >
              <div class="activity">
                <div
                  v-for="(a, i) in task.activities"
                  :key="i"
                  class="act"
                >
                  <span
                    class="act-ico"
                    :class="a.tone"
                  ><component
                    :is="ActivityIcon"
                    :name="a.icon"
                  /></span>
                  <div class="act-body">
                    <div
                      class="act-text"
                      v-html="a.html"
                    />
                    <div
                      v-if="a.file"
                      class="act-file"
                    >
                      <span
                        class="fi"
                        :class="badgeFor(a.file).cls"
                      >{{ badgeFor(a.file).label }}</span>{{ a.file }}
                    </div>
                    <div
                      v-if="a.statusFrom"
                      class="act-chips"
                    >
                      <span class="act-chip from">{{ a.statusFrom }}</span>
                      <svg
                        viewBox="0 0 24 24"
                        fill="none"
                        stroke="currentColor"
                        stroke-width="2"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                      ><path d="M5 12h14M13 6l6 6-6 6" /></svg>
                      <span class="act-chip to">{{ a.statusTo }}</span>
                    </div>
                    <div class="act-time">
                      {{ a.time }}
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </section>

        <!-- 사이드바 -->
        <aside class="side">
          <!-- 지시자: 승인 대기(review) → 승인/수정요청 -->
          <div
            v-if="task.status === 'review' && isIssuer"
            class="card ap-card review"
          >
            <div class="ap-card-head">
              <span class="ap-card-ico">
                <svg
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                ><path d="M12 8v4l3 2" /><circle
                  cx="12"
                  cy="12"
                  r="9"
                /></svg>
              </span>
              <span class="ap-card-title">승인 요청 도착</span>
            </div>
            <div class="ap-card-meta">
              <template v-if="task.approval">
                <b>{{ task.approval.requester }}</b>님이 {{ task.approval.requestedAgo }} 승인을 요청했습니다. 검토 후 승인하거나 수정을 요청하세요.
              </template>
              <template v-else>
                담당자가 작업 완료 후 승인을 요청했습니다. 검토 후 승인하거나 수정을 요청하세요.
              </template>
            </div>
            <div class="ap-card-actions">
              <button
                class="ap-btn approve"
                type="button"
                @click="openChanges"
              >
                <svg
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                ><path d="M9 11l3 3L22 4" /><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" /></svg>
                할 일 검토하기
              </button>
            </div>
          </div>

          <!-- 담당자: 시작 / 승인요청 / 재요청 / 대기중 / 완료 -->
          <div
            v-else-if="isAssignee"
            class="card ap-card"
            :class="assigneeCardClass"
          >
            <!-- 할 일 → 업무 시작 -->
            <template v-if="task.status === 'todo'">
              <div class="ap-card-head">
                <span class="ap-card-ico">
                  <svg
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  ><path d="M5 3 19 12 5 21V3z" /></svg>
                </span>
                <span class="ap-card-title">업무 시작</span>
              </div>
              <div class="ap-card-desc">
                아직 시작하지 않은 업무입니다. 작업을 시작하면 상태가 <b>진행 중</b>으로 바뀌고 지시자에게 표시됩니다.
              </div>
              <div class="ap-card-actions">
                <button
                  class="ap-btn request-send"
                  type="button"
                  @click="startTask"
                >
                  <svg
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2.2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  ><path d="M5 3 19 12 5 21V3z" /></svg>
                  업무 시작
                </button>
              </div>
            </template>
            <!-- 진행 중 → 승인 요청 -->
            <template v-else-if="task.status === 'prog'">
              <div class="ap-card-head">
                <span class="ap-card-ico">
                  <svg
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  ><path d="m22 2-7 20-4-9-9-4Z" /><path d="M22 2 11 13" /></svg>
                </span>
                <span class="ap-card-title">지시자에게 승인 요청</span>
              </div>
              <div class="ap-card-desc">
                작업을 마쳤다면 지시자 <b>{{ task.issuer.name }}</b>님에게 검토 승인을 요청하세요. 승인되면 업무가 완료 처리됩니다.
              </div>
              <div class="ap-card-actions">
                <button
                  class="ap-btn request-send"
                  type="button"
                  @click="sendApproval"
                >
                  <svg
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2.2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  ><path d="m22 2-7 20-4-9-9-4Z" /><path d="M22 2 11 13" /></svg>
                  승인 요청 보내기
                </button>
              </div>
            </template>
            <!-- 수정 요청됨 → 다시 승인 요청 -->
            <template v-else-if="task.status === 'changes'">
              <div class="ap-card-head">
                <span class="ap-card-ico">
                  <svg
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  ><path d="M3 7v6h6" /><path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13" /></svg>
                </span>
                <span class="ap-card-title">수정 요청됨</span>
              </div>
              <div class="ap-card-meta">
                지시자 <b>{{ task.issuer.name }}</b>님이 수정을 요청했습니다. 보완 후 다시 승인을 요청하세요.
              </div>
              <div
                v-if="task.changesNote"
                class="ap-note-box"
              >
                {{ task.changesNote }}
              </div>
              <div class="ap-card-actions">
                <button
                  class="ap-btn request-send"
                  type="button"
                  @click="sendApproval"
                >
                  <svg
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2.2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  ><path d="m22 2-7 20-4-9-9-4Z" /><path d="M22 2 11 13" /></svg>
                  다시 승인 요청
                </button>
              </div>
            </template>
            <!-- 승인 대기 중 -->
            <template v-else-if="task.status === 'review'">
              <div class="ap-card-head">
                <span class="ap-card-ico">
                  <svg
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  ><path d="M12 8v4l3 2" /><circle
                    cx="12"
                    cy="12"
                    r="9"
                  /></svg>
                </span>
                <span class="ap-card-title">승인 대기 중</span>
              </div>
              <div class="ap-card-desc">
                지시자에게 승인을 요청했습니다. 검토 결과를 기다리는 중입니다.
              </div>
            </template>
            <!-- 완료 -->
            <template v-else>
              <div class="ap-card-head">
                <span class="ap-card-ico">
                  <svg
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2.2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  ><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" /><path d="M22 4 12 14.01l-3-3" /></svg>
                </span>
                <span class="ap-card-title">승인 완료</span>
              </div>
              <div class="ap-card-meta">
                지시자 <b>{{ task.issuer.name }}</b>님이 검토를 승인했습니다. 업무가 완료되었습니다.
              </div>
            </template>
          </div>

          <div class="card side-card">
            <div class="side-field">
              <div class="side-label">
                상태
              </div>
              <span
                class="status-badge"
                :class="task.status"
              ><span class="dot" />{{ task.statusLabel }}</span>
            </div>
            <div class="side-field">
              <div class="side-label">
                담당자 · {{ task.assignees.length }}명
              </div>
              <div class="assignees">
                <div
                  v-for="a in task.assignees"
                  :key="a.id"
                  class="ass"
                >
                  <span class="avatar"><UserAvatar
                    :url="a.avatarUrl"
                    :name="a.name"
                  /></span>
                  <span><div class="nm">{{ a.name }}</div><div class="rl">{{ a.role }}</div></span>
                </div>
              </div>
            </div>
            <div class="side-field">
              <div class="side-label">
                마감기한
              </div>
              <div class="dl">
                <svg
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                ><rect
                  x="3"
                  y="4"
                  width="18"
                  height="18"
                  rx="2"
                /><path d="M16 2v4M8 2v4M3 10h18" /></svg>
                {{ task.dueLabel || '마감 없음' }}
                <span
                  v-if="task.dday"
                  class="dday"
                >{{ task.dday }}</span>
              </div>
            </div>
          </div>

          <div class="card side-card">
            <div class="side-field">
              <div class="side-label files-head">
                <span>첨부파일 · {{ task.files.length }}개</span>
              </div>
              <div
                v-if="task.files.length"
                class="files"
              >
                <div
                  v-for="f in task.files"
                  :key="f.id ?? f.name"
                  class="file"
                >
                  <a
                    class="file-ico"
                    :class="fileBadge(f.kind).cls"
                    :href="f.url"
                    target="_blank"
                    rel="noopener"
                  >{{ fileBadge(f.kind).label }}</a>
                  <a
                    class="file-info"
                    :href="f.url"
                    target="_blank"
                    rel="noopener"
                  ><span class="file-name">{{ f.name }}</span><span class="file-size">{{ f.size }}</span></a>
                </div>
              </div>
              <p
                v-else
                class="files-empty"
              >
                아직 첨부된 파일이 없습니다.
              </p>
            </div>
          </div>

          <!-- 중간 점검 — 점검일/보고 -->
          <div
            v-if="task.checkpoints.length || task.checkpointReports.length"
            class="card side-card"
          >
            <div class="side-field">
              <div class="side-label">
                중간 점검
              </div>
              <!-- 점검일 목록 + 상태(예정/오늘/완료/누락) -->
              <ul
                v-if="task.checkpoints.length"
                class="cp-d-list"
              >
                <li
                  v-for="cp in task.checkpoints"
                  :key="cp.id"
                  class="cp-d-item"
                >
                  <span class="cp-d-date">{{ cp.dateLabel }}</span>
                  <!-- 당일 + 담당자만 보고서 작성 -->
                  <button
                    v-if="checkpointState(cp) === 'today' && isAssignee"
                    class="cp-report-btn"
                    type="button"
                    @click="reportingCpId === cp.id ? cancelReportForm() : openReportForm(cp)"
                  >
                    {{ reportingCpId === cp.id ? '취소' : '보고서 작성' }}
                  </button>
                  <button
                    v-else-if="checkpointState(cp) === 'reported'"
                    class="cp-state reported clickable"
                    type="button"
                    title="보고 내용 보기"
                    @click="openReportModal(cp)"
                  >
                    보고 완료
                  </button>
                  <span
                    v-else
                    class="cp-state"
                    :class="checkpointState(cp)"
                  >{{ CP_STATE_LABEL[checkpointState(cp)] }}</span>
                </li>
              </ul>

              <!-- 보고서 작성 폼(인라인) — 항목별 상태 선택 + 메모 -->
              <div
                v-if="reportingCpId"
                class="cp-form"
              >
                <div class="cp-form-title">
                  진행 상태 보고서
                </div>
                <div
                  v-if="reportItems.length"
                  class="cp-form-items"
                >
                  <div
                    v-for="it in reportItems"
                    :key="it.id"
                    class="cp-form-item"
                  >
                    <span class="cp-fi-text">{{ it.text }}</span>
                    <span class="ws-seg">
                      <button
                        v-for="s in WORK_STATUS_OPTIONS"
                        :key="s"
                        type="button"
                        class="ws-btn"
                        :class="[s, { active: it.workStatus === s }]"
                        @click="it.workStatus = s"
                      >{{ WORK_STATUS_LABEL[s] }}</button>
                    </span>
                  </div>
                </div>
                <p
                  v-else
                  class="cp-form-empty"
                >
                  등록된 할 일이 없어 메모만 보고됩니다.
                </p>
                <textarea
                  v-model="reportNote"
                  class="cp-note-input"
                  rows="2"
                  placeholder="보고 메모(선택)"
                />
                <div class="cp-form-actions">
                  <button
                    class="cp-send-btn"
                    type="button"
                    :disabled="reportSubmitting"
                    @click="submitReport"
                  >
                    {{ reportSubmitting ? '보내는 중…' : '지시자에게 보내기' }}
                  </button>
                </div>
              </div>
            </div>
          </div>

          <div class="card side-card">
            <div class="side-field bare">
              <div class="side-head">
                <div class="side-label">
                  지시자
                </div>
                <div class="issued-at">
                  {{ task.issuedLabel }}
                </div>
              </div>
              <div class="issuer">
                <span class="avatar"><UserAvatar
                  :url="task.issuer.avatarUrl"
                  :name="task.issuer.name"
                /></span>
                <span><div class="nm">{{ task.issuer.name }}</div><div class="rl">{{ task.issuer.role }}</div></span>
              </div>
            </div>
          </div>
        </aside>
      </div>
    </div>

    <!-- 로딩/없음 상태 -->
    <div
      v-else
      class="page"
    >
      <p class="task-empty">
        업무를 불러오는 중이거나 찾을 수 없습니다.
      </p>
    </div>

    <!-- ===== 모달 ===== -->
    <Teleport to="body">
      <!-- 할 일 완료 검토(수정 요청 / 승인) -->
      <div
        class="modal-backdrop"
        :class="{ open: showChanges }"
        @click.self="showChanges = false"
      >
        <div
          v-if="task"
          class="rv-modal"
          role="dialog"
          aria-modal="true"
          aria-label="할 일 완료 검토"
        >
          <!-- 헤더 -->
          <div class="rv-head">
            <div class="rv-head-top">
              <div class="rv-badge">
                <svg
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                ><path d="M9 11l3 3L22 4" /><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" /></svg>
              </div>
              <div class="rv-htext">
                <div class="rv-title">
                  할 일 완료 검토
                </div>
                <div class="rv-sub">
                  완료된 항목은 <span class="ok">완료</span>, 보완이 필요한 항목은 <span class="no">보완</span>으로 표시하세요.
                  보완 항목이 있으면 메시지와 함께 담당자에게 전달되어 업무가 다시 <b>진행 중</b>으로 돌아갑니다.
                </div>
              </div>
            </div>
            <button
              class="rv-x"
              type="button"
              aria-label="닫기"
              @click="showChanges = false"
            >
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
              ><path d="M18 6 6 18M6 6l12 12" /></svg>
            </button>
          </div>

          <!-- 본문 -->
          <div class="rv-body">
            <div
              v-if="task.checklist.length"
              class="ck-card"
            >
              <div class="ck-top">
                <span class="ck-top-name">진행률</span>
                <span class="ck-progress">
                  <span class="pbar"><i
                    :class="{ red: reviewDoneCount === 0 }"
                    :style="{ width: reviewPct + '%' }"
                  /></span>
                </span>
                <span class="ck-stat"><b>{{ reviewDoneCount }}</b> / {{ reviewTotal }} 완료</span>
              </div>

              <div
                v-for="group in checklistGroups"
                :key="group.category"
                class="ck-group"
                :class="groupClass(group.category)"
              >
                <div class="ck-glabel">
                  <span class="ck-dot" />{{ group.category }}
                </div>
                <div
                  v-for="item in group.items"
                  :key="item.id"
                  class="ck-item-wrap"
                >
                  <div
                    class="ck-item"
                    :class="reviewState(item) ? 'done' : 'todo'"
                  >
                    <span class="ck-state">
                      <svg
                        v-if="reviewState(item)"
                        viewBox="0 0 24 24"
                        fill="none"
                        stroke="currentColor"
                        stroke-width="3"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                      ><path d="M20 6 9 17l-5-5" /></svg>
                      <svg
                        v-else
                        viewBox="0 0 24 24"
                        fill="none"
                        stroke="currentColor"
                        stroke-width="3"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                      ><path d="M18 6 6 18M6 6l12 12" /></svg>
                    </span>
                    <span class="ck-text">{{ item.text }}</span>
                    <div class="seg">
                      <button
                        type="button"
                        class="done"
                        @click="setReview(item, true)"
                      >
                        완료
                      </button>
                      <button
                        type="button"
                        class="todo"
                        @click="setReview(item, false)"
                      >
                        보완
                      </button>
                    </div>
                  </div>
                  <!-- 보완 항목 — 항목별 보완 내용(선택) 개별 입력 -->
                  <textarea
                    v-if="!reviewState(item)"
                    class="ck-note-ta"
                    :value="reviewNoteOf(item)"
                    placeholder="이 항목의 보완 내용을 입력하세요 (선택)"
                    @input="setReviewNote(item, ($event.target as HTMLTextAreaElement).value)"
                  />
                </div>
              </div>
            </div>

            <!-- 수정 요청 메시지 -->
            <div class="rev-sec">
              <div
                class="rev-flag"
                :class="reviewAllDone ? 'good' : 'warn'"
              >
                <svg
                  v-if="reviewAllDone"
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  stroke-width="2.2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                ><path d="M20 6 9 17l-5-5" /></svg>
                <svg
                  v-else
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                ><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z" /><path d="M12 9v4M12 17h.01" /></svg>
                <span v-if="reviewAllDone">
                  모든 항목이 완료되었습니다. 메시지 없이 바로 승인할 수 있어요.
                </span>
                <span v-else>
                  <b>{{ reviewTodoCount }}개</b> 항목에 보완이 필요합니다. 필요하면 보완 내용을 적어 주세요.
                </span>
              </div>
              <div class="rev-label">
                수정 요청 메시지
                <span class="opt">· 선택</span>
              </div>
              <textarea
                v-model="changesNote"
                class="rev-ta"
                :disabled="reviewAllDone"
                placeholder="예) '로그인 상태 유지' 옵션이 동작하지 않습니다. 체크 후에도 세션이 유지되도록 수정해 주세요."
              />
            </div>
          </div>

          <!-- 푸터 -->
          <div class="rv-foot">
            <span class="foot-note">
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
              ><path d="M22 2 11 13M22 2l-7 20-4-9-9-4 20-7z" /></svg>
              <span>{{ assigneeNames }} 님에게 전달됩니다</span>
            </span>
            <button
              class="btn"
              type="button"
              @click="showChanges = false"
            >
              취소
            </button>
            <button
              class="btn"
              :class="reviewAllDone ? 'approve' : 'danger'"
              type="button"
              @click="submitChanges"
            >
              <svg
                v-if="reviewAllDone"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2.2"
                stroke-linecap="round"
                stroke-linejoin="round"
              ><path d="M20 6 9 17l-5-5" /></svg>
              <svg
                v-else
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
              ><path d="M22 2 11 13M22 2l-7 20-4-9-9-4 20-7z" /></svg>
              {{ reviewAllDone ? '승인 완료' : `수정 요청 보내기 (${reviewTodoCount}건)` }}
            </button>
          </div>
        </div>
      </div>
    </Teleport>

    <!-- 중간 점검 보고 열람 모달 -->
    <Teleport to="body">
      <div
        class="modal-backdrop"
        :class="{ open: !!openedReport }"
        @click.self="openedReport = null"
      >
        <div
          v-if="openedReport"
          class="cp-modal"
          role="dialog"
          aria-modal="true"
          aria-label="중간 점검 보고"
        >
          <div class="cp-modal-head">
            <div>
              <div class="cp-modal-title">
                중간 점검 보고
              </div>
              <div class="cp-modal-sub">
                {{ openedReport.reporterName }} · 점검일 {{ openedReport.dateLabel }} · {{ openedReport.timeLabel }}
              </div>
            </div>
            <button
              class="rv-x"
              type="button"
              aria-label="닫기"
              @click="openedReport = null"
            >
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
              ><path d="M18 6 6 18M6 6l12 12" /></svg>
            </button>
          </div>
          <div
            v-if="openedReport.note"
            class="cp-modal-note"
          >
            {{ openedReport.note }}
          </div>
          <!-- 카테고리 탭 -->
          <div
            class="cat-tabs"
            role="tablist"
          >
            <button
              v-for="tab in CHECKLIST_CATEGORIES"
              :key="tab"
              type="button"
              class="cat-tab"
              :class="[categoryClass(tab), { active: reportTab === tab }]"
              role="tab"
              :aria-selected="reportTab === tab"
              @click="reportTab = tab"
            >
              <span class="cat-dot" />
              {{ tab }}
              <span class="cat-count">{{ reportCatCount(tab) }}</span>
            </button>
          </div>
          <!-- 항목 목록 -->
          <div class="cp-modal-items">
            <div
              v-for="(it, i) in reportItemsByCat(reportTab)"
              :key="i"
              class="cp-modal-item"
            >
              <span
                class="ws-badge"
                :class="it.workStatus"
              >{{ WORK_STATUS_LABEL[it.workStatus] }}</span>
              <span class="cp-mi-text">{{ it.text }}</span>
            </div>
            <div
              v-if="reportItemsByCat(reportTab).length === 0"
              class="cp-modal-empty"
            >
              '{{ reportTab }}' 항목이 없습니다.
            </div>
          </div>
        </div>
      </div>
    </Teleport>
  </AppShell>
</template>

<style scoped>
.crumb {
  padding: 1rem 0 0;
}
.task-empty {
  padding: 3rem 0;
  text-align: center;
  color: var(--text-2);
}

/* 페이지 헤더 */
.pagehead {
  display: flex;
  align-items: flex-start;
  gap: 0.875rem;
  padding: 0.75rem 0 1.125rem;
}
.ph-main {
  flex: 1;
  min-width: 0;
}
.ph-titlerow {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}
.st-badge.bordered.prog {
  border: 1px solid var(--blue-border);
}
.st-badge.bordered.review {
  border: 1px solid var(--amber-border);
}
.st-badge.bordered.done {
  border: 1px solid var(--green-border);
}
.st-badge.bordered.changes {
  border: 1px solid var(--red-border);
}
.ph-tid {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--text-3);
}
.ph-title {
  font-size: 1.4375rem;
  font-weight: 700;
  letter-spacing: -0.03125rem;
  margin-top: 0.5rem;
  line-height: 1.3;
}
.pagehead .actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
  padding-top: 0.125rem;
}
.pagehead .btn {
  height: 2.125rem;
}

/* 더보기 메뉴 */
.more-wrap {
  position: relative;
}
.menu {
  display: none;
  position: absolute;
  top: calc(100% + 0.375rem);
  right: 0;
  z-index: 40;
  width: 10.5rem;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 0.5625rem;
  box-shadow: 0 12px 32px rgba(20, 24, 33, 0.16);
  padding: 0.3125rem;
}
.menu.open {
  display: block;
}
.menu-item {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  width: 100%;
  border: none;
  background: transparent;
  font-family: inherit;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--text);
  padding: 0.5rem 0.5625rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
}
.menu-item:hover {
  background: #f5f6f8;
}
.menu-item svg {
  width: 1rem;
  height: 1rem;
  color: var(--text-3);
  flex-shrink: 0;
}
.menu-item.danger {
  color: var(--red);
}
.menu-item.danger svg {
  color: var(--red);
}
.menu-item.danger:hover {
  background: var(--red-weak);
}
.menu-sep {
  height: 1px;
  background: var(--border);
  margin: 0.3125rem 0;
}

/* 레이아웃 */
.layout {
  display: grid;
  grid-template-columns: 1fr 20rem;
  gap: 1.25rem;
  align-items: start;
}
.main-card {
  padding: 1.375rem 1.625rem 1.625rem;
}
.sect + .sect {
  margin-top: 1.625rem;
  padding-top: 1.375rem;
  border-top: 1px solid var(--border);
}
.sect-label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.781rem;
  font-weight: 600;
  color: var(--text-2);
  margin-bottom: 0.75rem;
  white-space: nowrap;
}
.sect-label .pr {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.5625rem;
}
.progress-bar {
  width: 5.75rem;
  height: 0.375rem;
  border-radius: 4px;
  background: #eceef1;
  overflow: hidden;
}
.progress-bar > i {
  display: block;
  height: 100%;
  background: var(--green);
  border-radius: 4px;
}
.progress-txt {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-2);
  white-space: nowrap;
}
.content {
  font-size: 0.875rem;
  color: var(--text);
  line-height: 1.7;
}
.content p {
  margin-bottom: 0.6875rem;
}
.content p:last-child {
  margin-bottom: 0;
}
.content :deep(.mention) {
  color: var(--accent);
  font-weight: 600;
  background: var(--accent-weak);
  padding: 0 0.1875rem;
  border-radius: 3px;
}

/* ===== 할 일 완료 검토 모달(수정 요청 / 승인) ===== */
.rv-modal {
  width: 100%;
  max-width: 37.5rem;
  max-height: calc(100vh - 3rem);
  background: var(--panel);
  border-radius: 1rem;
  box-shadow: var(--shadow-pop);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* 헤더 */
.rv-head {
  position: relative;
  padding: 1.25rem 1.375rem 1.125rem;
  border-bottom: 1px solid var(--border);
}
.rv-head-top {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
}
.rv-badge {
  width: 2.375rem;
  height: 2.375rem;
  border-radius: 0.6875rem;
  flex-shrink: 0;
  background: linear-gradient(140deg, #6366f1, #4338ca);
  display: grid;
  place-items: center;
  color: #fff;
  box-shadow:
    0 4px 12px rgba(79, 70, 229, 0.4),
    inset 0 1px 0 rgba(255, 255, 255, 0.25);
}
.rv-badge svg {
  width: 1.25rem;
  height: 1.25rem;
}
.rv-htext {
  flex: 1;
  min-width: 0;
  padding-top: 0.0625rem;
}
.rv-title {
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: -0.0187rem;
}
.rv-sub {
  font-size: 0.781rem;
  color: var(--text-2);
  margin-top: 0.25rem;
  line-height: 1.55;
}
.rv-sub b {
  color: var(--text);
  font-weight: 600;
}
.rv-sub .ok {
  color: var(--green);
  font-weight: 700;
}
.rv-sub .no {
  color: var(--red);
  font-weight: 700;
}
.rv-x {
  position: absolute;
  top: 1rem;
  right: 1rem;
  width: 2rem;
  height: 2rem;
  border-radius: 0.5rem;
  border: none;
  background: transparent;
  color: var(--text-3);
  display: grid;
  place-items: center;
  cursor: pointer;
}
.rv-x:hover {
  background: rgba(20, 24, 33, 0.06);
  color: var(--text);
}
.rv-x svg {
  width: 1.125rem;
  height: 1.125rem;
}

/* 본문 */
.rv-body {
  overflow-y: auto;
  padding: 1.125rem 1.375rem 1.25rem;
  flex: 1;
}

/* 체크리스트 카드 */
.ck-card {
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  overflow: hidden;
}
.ck-top {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.875rem 1rem 0.8125rem;
  background: #fbfbfd;
  border-bottom: 1px solid var(--border);
}
.ck-top-name {
  font-size: 0.844rem;
  font-weight: 700;
  letter-spacing: -0.0125rem;
  white-space: nowrap;
}
.ck-progress {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-width: 0;
}
.pbar {
  flex: 1;
  height: 0.375rem;
  border-radius: 0.25rem;
  background: #e7e9ed;
  overflow: hidden;
  min-width: 3.75rem;
}
.pbar > i {
  display: block;
  height: 100%;
  border-radius: 0.25rem;
  background: var(--green);
  transition: width 0.25s ease;
}
.pbar > i.red {
  background: var(--red);
}
.ck-stat {
  font-size: 0.781rem;
  font-weight: 700;
  color: var(--text-2);
  white-space: nowrap;
}
.ck-stat b {
  color: var(--green);
}
.ck-group {
  padding: 0.8125rem 1rem 0.25rem;
}
.ck-group + .ck-group {
  border-top: 1px solid var(--border);
}
.ck-glabel {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--text-2);
  margin-bottom: 0.5625rem;
}
.ck-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  flex-shrink: 0;
}
.g-req .ck-dot {
  background: var(--accent);
}
.g-mng .ck-dot {
  background: #1b7a47;
}
.g-sec .ck-dot {
  background: #c0394f;
}
.g-ext .ck-dot {
  background: var(--amber);
}
.ck-item-wrap {
  margin-bottom: 0.5625rem;
}
.ck-item {
  display: flex;
  align-items: center;
  gap: 0.8125rem;
  padding: 0.6875rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 0.5625rem;
  background: #fff;
  position: relative;
  transition:
    background 0.14s,
    border-color 0.14s;
}
/* 항목별 보완 내용 입력(보완 항목 아래) */
.ck-note-ta {
  width: 100%;
  min-height: 2.75rem;
  margin-top: 0.375rem;
  border: 1px solid var(--red-border);
  border-radius: 0.5rem;
  padding: 0.5rem 0.625rem;
  font-family: inherit;
  font-size: 0.781rem;
  color: var(--text);
  line-height: 1.5;
  resize: vertical;
  background: #fffafb;
}
.ck-note-ta::placeholder {
  color: var(--text-3);
}
.ck-note-ta:focus {
  outline: none;
  border-color: var(--red);
  box-shadow: 0 0 0 3px var(--red-weak);
}
.ck-item::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0.1875rem;
  border-radius: 0.5625rem 0 0 0.5625rem;
  background: var(--red);
  opacity: 0;
  transition: opacity 0.14s;
}
.ck-state {
  width: 1.875rem;
  height: 1.875rem;
  border-radius: 0.5rem;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  color: #fff;
  transition: background 0.14s;
}
.ck-state svg {
  width: 1.0625rem;
  height: 1.0625rem;
}
.ck-text {
  flex: 1;
  font-size: 0.844rem;
  color: var(--text);
  line-height: 1.5;
  letter-spacing: -0.00625rem;
}

/* 세그먼트 토글(완료/보완) */
.seg {
  display: flex;
  background: #f1f2f4;
  border: 1px solid var(--border);
  border-radius: 0.5625rem;
  padding: 0.1875rem;
  gap: 0.125rem;
  flex-shrink: 0;
}
.seg button {
  border: none;
  background: transparent;
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--text-3);
  padding: 0.375rem 0.6875rem;
  border-radius: 0.375rem;
  cursor: pointer;
  line-height: 1;
  white-space: nowrap;
}
.seg button:hover {
  color: var(--text-2);
}

/* 완료 상태 */
.ck-item.done {
  background: #f4fbf6;
  border-color: var(--green-border);
}
.ck-item.done .ck-state {
  background: var(--green);
}
.ck-item.done .seg button.done {
  background: #fff;
  color: var(--green);
  box-shadow: var(--shadow-sm);
}
.ck-item.done .seg button.done:hover {
  color: var(--green);
}
/* 보완 상태 */
.ck-item.todo {
  background: var(--red-weak);
  border-color: var(--red-border);
}
.ck-item.todo::before {
  opacity: 1;
}
.ck-item.todo .ck-state {
  background: var(--red);
}
.ck-item.todo .seg {
  background: #f8dde1;
  border-color: var(--red-border);
}
.ck-item.todo .seg button.todo {
  background: #fff;
  color: var(--red);
  box-shadow: var(--shadow-sm);
}
.ck-item.todo .seg button.todo:hover {
  color: var(--red);
}

/* 수정 요청 메시지 */
.rev-sec {
  margin-top: 1.25rem;
}
.rev-flag {
  display: flex;
  align-items: center;
  gap: 0.5625rem;
  font-size: 0.781rem;
  font-weight: 600;
  padding: 0.625rem 0.8125rem;
  border-radius: 0.5625rem;
  margin-bottom: 0.6875rem;
  line-height: 1.5;
}
.rev-flag svg {
  width: 0.9375rem;
  height: 0.9375rem;
  flex-shrink: 0;
}
.rev-flag.warn {
  color: #a8324a;
  background: var(--red-weak);
  border: 1px solid var(--red-border);
}
.rev-flag.good {
  color: #1b7a40;
  background: var(--green-weak);
  border: 1px solid var(--green-border);
}
.rev-label {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 0.5rem;
}
.rev-label .opt {
  font-size: 0.719rem;
  font-weight: 500;
  color: var(--text-3);
}
.rev-ta {
  width: 100%;
  min-height: 6rem;
  border: 1px solid var(--border-strong);
  border-radius: 0.5625rem;
  padding: 0.6875rem 0.8125rem;
  font-family: inherit;
  font-size: 0.844rem;
  color: var(--text);
  line-height: 1.6;
  resize: vertical;
  background: #fff;
}
.rev-ta::placeholder {
  color: var(--text-3);
}
.rev-ta:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-weak);
}
.rev-ta:disabled {
  background: #f7f8fa;
  color: var(--text-3);
  cursor: not-allowed;
  resize: none;
}

/* 푸터 */
.rv-foot {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.875rem 1.375rem;
  border-top: 1px solid var(--border);
  background: #fbfbfd;
}
.foot-note {
  margin-right: auto;
  font-size: 0.75rem;
  color: var(--text-3);
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  min-width: 0;
}
.foot-note svg {
  width: 0.875rem;
  height: 0.875rem;
  flex-shrink: 0;
}
.rv-foot .btn {
  height: 2.625rem;
  padding: 0 1.25rem;
  border-radius: 0.5625rem;
  font-size: 0.875rem;
  font-weight: 700;
  border: 1px solid var(--border-strong);
  background: var(--panel);
  color: var(--text-2);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  line-height: 1;
  font-family: inherit;
  white-space: nowrap;
}
.rv-foot .btn:hover {
  background: #f7f8fa;
  color: var(--text);
}
.rv-foot .btn svg {
  width: 1rem;
  height: 1rem;
}
.rv-foot .btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.rv-foot .btn.danger {
  background: var(--red);
  border-color: var(--red);
  color: #fff;
  box-shadow: 0 1px 2px rgba(214, 69, 93, 0.4);
}
.rv-foot .btn.danger:hover {
  background: var(--red-hover, #c23950);
  border-color: var(--red-hover, #c23950);
  color: #fff;
}
.rv-foot .btn.danger:disabled:hover {
  background: var(--red);
  border-color: var(--red);
}
.rv-foot .btn.approve {
  background: var(--green);
  border-color: var(--green);
  color: #fff;
  box-shadow: 0 1px 2px rgba(31, 157, 87, 0.38);
}
.rv-foot .btn.approve:hover {
  background: #1b8a4d;
  border-color: #1b8a4d;
  color: #fff;
}

/* 체크리스트(읽기 전용) */
.checklist {
  display: flex;
  flex-direction: column;
}
/* 카테고리 그룹 — 필수/관리/보안/부가 */
.cat-group + .cat-group {
  margin-top: 0.625rem;
}
.cat-group-head {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.25rem 0;
}
.cat-group-head .cat-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  flex-shrink: 0;
}
.cat-group-head.sec-1 .cat-dot {
  background: var(--accent);
}
.cat-group-head.sec-2 .cat-dot {
  background: #1b7a47;
}
.cat-group-head.sec-3 .cat-dot {
  background: #c0394f;
}
.cat-group-head.sec-4 .cat-dot {
  background: #a96a13;
}
.cat-group-head .cat-name {
  font-size: 0.781rem;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.0125rem;
}
.cat-group-head .cat-count {
  font-size: 0.719rem;
  font-weight: 700;
  color: var(--text-2);
}
.check-empty {
  padding: 0.5rem 0;
  font-size: 0.781rem;
  color: var(--text-2);
}
.citem {
  display: flex;
  align-items: center;
  gap: 0.6875rem;
  padding: 0.5rem 0;
  border-radius: var(--radius-sm);
}
/* 항목별 보완 내용(읽기 전용) — 체크박스+간격(1.8125rem)만큼 들여쓰기 */
.citem-note {
  margin: 0 0 0.375rem 1.8125rem;
  padding: 0.5rem 0.6875rem;
  font-size: 0.781rem;
  color: var(--text-2);
  line-height: 1.55;
  background: var(--red-weak);
  border: 1px solid var(--red-border);
  border-radius: 0.5rem;
  white-space: pre-wrap;
}
.cbox {
  width: 1.125rem;
  height: 1.125rem;
  border-radius: 0.3125rem;
  border: 1.5px solid var(--border-strong);
  flex-shrink: 0;
  display: grid;
  place-items: center;
  background: #fff;
}
.cbox.done {
  background: var(--green);
  border-color: var(--green);
}
.cbox svg {
  width: 0.75rem;
  height: 0.75rem;
  color: #fff;
  opacity: 0;
}
.cbox.done svg {
  opacity: 1;
}
/* 보완 필요(수정 요청 상태의 미완료 항목) — 빨강 X */
.cbox.todo {
  background: var(--red);
  border-color: var(--red);
}
.cbox.todo svg {
  opacity: 1;
}
.citem .ctext {
  font-size: 0.844rem;
  color: var(--text);
}
.citem.done .ctext {
  color: var(--text-3);
  text-decoration: line-through;
}
.citem.todo .ctext {
  color: var(--red);
  font-weight: 500;
}
.citem-tag {
  margin-left: auto;
  flex-shrink: 0;
  font-size: 0.688rem;
  font-weight: 700;
  color: var(--red);
  background: var(--red-weak);
  border: 1px solid var(--red-border);
  border-radius: 999px;
  padding: 0.0625rem 0.4375rem;
}

/* 댓글/활동 탭 */
.thread-tabs {
  display: flex;
  gap: 0.125rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 1.125rem;
}
.thread-tab {
  position: relative;
  border: none;
  background: transparent;
  font-family: inherit;
  font-size: 0.844rem;
  font-weight: 600;
  color: var(--text-3);
  padding: 0.5rem 0.75rem 0.6875rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  line-height: 1;
  white-space: nowrap;
}
.thread-tab:hover {
  color: var(--text-2);
}
.thread-tab.active {
  color: var(--accent);
}
.thread-tab.active::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  background: var(--accent);
  border-radius: 2px;
}
.tt-count {
  font-size: 0.719rem;
  font-weight: 600;
  color: var(--text-3);
  background: #f1f2f4;
  border-radius: 20px;
  padding: 0.0625rem 0.4375rem;
  line-height: 1.55;
}
.thread-tab.active .tt-count {
  color: var(--accent);
  background: var(--accent-weak);
}

/* 댓글 */
.comment {
  display: flex;
  gap: 0.6875rem;
  padding: 0.875rem 0;
  border-top: 1px solid var(--border);
}
.comment:first-of-type {
  border-top: none;
}
.c-av {
  width: 1.875rem;
  height: 1.875rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 0.75rem;
  font-weight: 700;
  color: #fff;
  flex-shrink: 0;
}
.c-body {
  flex: 1;
  min-width: 0;
}
.c-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.c-name {
  font-size: 0.844rem;
  font-weight: 600;
  white-space: nowrap;
}
.c-role {
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--accent);
  background: var(--accent-weak);
  border-radius: 20px;
  padding: 0.0625rem 0.4375rem;
  white-space: nowrap;
}
.c-time {
  font-size: 0.75rem;
  color: var(--text-3);
  white-space: nowrap;
}
.c-text {
  font-size: 0.844rem;
  color: var(--text);
  line-height: 1.6;
  margin-top: 0.25rem;
}
.c-file {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  margin-top: 0.5rem;
  padding: 0.375rem 0.625rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 0.781rem;
  font-weight: 600;
  color: var(--text-2);
}
.c-file .fi {
  width: 1.375rem;
  height: 1.375rem;
  border-radius: 0.3125rem;
  color: #fff;
  display: grid;
  place-items: center;
  font-size: 0.5rem;
  font-weight: 800;
}
.c-actions {
  margin-top: 0.4375rem;
}
.c-reply-btn {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-3);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.125rem 0;
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  font-family: inherit;
  white-space: nowrap;
}
.c-reply-btn:hover {
  color: var(--accent);
}
.c-reply-btn svg {
  width: 0.8125rem;
  height: 0.8125rem;
}
.c-replies {
  margin-top: 0.8125rem;
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
}
.c-reply {
  display: flex;
  gap: 0.5625rem;
}
.c-reply .c-av {
  width: 1.5rem;
  height: 1.5rem;
  font-size: 0.656rem;
}
.reply-form {
  display: none;
  gap: 0.5625rem;
  margin-top: 0.75rem;
}
.reply-form.open {
  display: flex;
}
.reply-form .c-av {
  width: 1.5rem;
  height: 1.5rem;
  font-size: 0.656rem;
}
.reply-form .field {
  flex: 1;
}
.reply-form textarea {
  width: 100%;
  min-height: 2.75rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 0.5rem 0.625rem;
  font-family: inherit;
  font-size: 0.8125rem;
  line-height: 1.55;
  resize: vertical;
  color: var(--text);
}
.reply-form textarea:focus,
.composer textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-weak);
}
.reply-form textarea::placeholder,
.composer textarea::placeholder {
  color: var(--text-3);
}
.crow {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 0.5rem;
}
.composer {
  display: flex;
  gap: 0.6875rem;
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}
.composer .c-av {
  width: 1.875rem;
  height: 1.875rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 0.75rem;
  font-weight: 700;
  color: #fff;
  flex-shrink: 0;
}
.composer .field {
  flex: 1;
}
.composer textarea {
  width: 100%;
  min-height: 4rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 0.625rem 0.75rem;
  font-family: inherit;
  font-size: 0.844rem;
  resize: vertical;
  color: var(--text);
}

/* 활동 타임라인 */
.activity {
  display: flex;
  flex-direction: column;
  padding-top: 0.125rem;
}
.act {
  display: grid;
  grid-template-columns: 1.75rem 1fr;
  gap: 0.8125rem;
  position: relative;
}
.act::before {
  content: '';
  position: absolute;
  left: 0.8125rem;
  top: 1.875rem;
  bottom: -0.125rem;
  width: 2px;
  background: var(--border);
}
.act:last-child::before {
  display: none;
}
.act-ico {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  border: 1px solid var(--border);
  background: #fff;
  color: var(--text-2);
  z-index: 1;
}
.act-ico svg {
  width: 0.875rem;
  height: 0.875rem;
}
.act-ico.green {
  background: var(--green-weak);
  border-color: var(--green-border);
  color: var(--green);
}
.act-ico.amber {
  background: var(--amber-weak);
  border-color: var(--amber-border);
  color: var(--amber);
}
.act-ico.blue {
  background: var(--blue-weak);
  border-color: var(--blue-border);
  color: var(--blue);
}
.act-ico.accent {
  background: var(--accent-weak);
  border-color: var(--accent-border);
  color: var(--accent);
}
.act-body {
  padding-bottom: 1.25rem;
  min-width: 0;
}
.act:last-child .act-body {
  padding-bottom: 0;
}
.act-text {
  font-size: 0.844rem;
  color: var(--text);
  line-height: 1.5;
}
.act-text :deep(b) {
  font-weight: 600;
}
.act-file {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  margin-top: 0.4375rem;
  padding: 0.3125rem 0.5625rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-2);
}
.act-file .fi {
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 4px;
  color: #fff;
  display: grid;
  place-items: center;
  font-size: 0.4375rem;
  font-weight: 800;
}
.act-chips {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  margin-top: 0.5rem;
}
.act-chip {
  font-size: 0.719rem;
  font-weight: 600;
  padding: 0.125rem 0.5625rem;
  border-radius: 20px;
  white-space: nowrap;
}
.act-chip.from {
  color: var(--text-2);
  background: #f1f2f4;
  border: 1px solid var(--border);
}
.act-chip.to {
  color: var(--amber);
  background: var(--amber-weak);
  border: 1px solid var(--amber-border);
}
.act-chips svg {
  width: 0.875rem;
  height: 0.875rem;
  color: var(--text-3);
}
.act-time {
  font-size: 0.719rem;
  color: var(--text-3);
  margin-top: 0.1875rem;
}

/* 사이드바 */
.side {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.side-card {
  padding: 1rem 1.125rem 1.125rem;
}
.side-field + .side-field {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}
.side-field.bare {
  border: none;
}
.side-label {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-3);
  margin-bottom: 0.5625rem;
  white-space: nowrap;
}
/* 라벨 + 우측 보조 텍스트(지시 시각 등) 한 줄 배치 */
.side-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.5625rem;
}
.side-head .side-label {
  margin-bottom: 0;
}
.issued-at {
  font-size: 0.719rem;
  color: var(--text-3);
  white-space: nowrap;
}
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  height: 1.875rem;
  padding: 0 0.8125rem;
  border: 1px solid var(--blue-border);
  background: var(--blue-weak);
  border-radius: 20px;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--blue);
  white-space: nowrap;
}
.status-badge .dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--blue);
}
.status-badge.review {
  color: var(--amber);
  background: var(--amber-weak);
  border-color: var(--amber-border);
}
.status-badge.review .dot {
  background: var(--amber);
}
.status-badge.todo {
  color: var(--text-2);
  background: #f1f2f4;
  border-color: var(--border-strong);
}
.status-badge.todo .dot {
  background: var(--text-3);
}
.status-badge.changes {
  color: var(--red);
  background: var(--red-weak);
  border-color: var(--red-border);
}
.status-badge.changes .dot {
  background: var(--red);
}
.status-badge.done {
  color: var(--green);
  background: var(--green-weak);
  border-color: #cde8d8;
}
.status-badge.done .dot {
  background: var(--green);
}
.assignees {
  display: flex;
  flex-direction: column;
  gap: 0.5625rem;
}
.ass {
  display: flex;
  align-items: center;
  gap: 0.5625rem;
}
.ass .avatar {
  width: 1.625rem;
  height: 1.625rem;
  font-size: 0.6875rem;
}
.ass .nm {
  font-size: 0.8125rem;
  font-weight: 600;
  white-space: nowrap;
}
.ass .rl {
  font-size: 0.719rem;
  color: var(--text-3);
  white-space: nowrap;
}
.dl {
  display: flex;
  align-items: center;
  gap: 0.5625rem;
  font-size: 0.844rem;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}
.dl svg {
  width: 1rem;
  height: 1rem;
  color: var(--text-3);
}
.dl .dday {
  font-size: 0.719rem;
  font-weight: 700;
  color: var(--amber);
  background: var(--amber-weak);
  border: 1px solid var(--amber-border);
  border-radius: 20px;
  padding: 0.0625rem 0.4375rem;
  margin-left: auto;
  white-space: nowrap;
}
.files {
  display: flex;
  flex-direction: column;
  gap: 0.4375rem;
}
.file {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.5rem 0.5625rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.file-ico {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 0.375rem;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  font-size: 0.5625rem;
  font-weight: 800;
  color: #fff;
}
/* 파일 종류별 배지 색상 — 첨부 카드(.file-ico)·댓글(.c-file .fi)·활동(.act-file .fi) 공통 */
.fi-img {
  background: #2aa775;
}
.fi-pdf {
  background: #e25950;
}
.fi-zip {
  background: #8a64d6;
}
.fi-doc {
  background: #3d8bf2;
}
.file-info {
  min-width: 0;
  flex: 1;
}
.file-name {
  font-size: 0.781rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: block;
}
.file-size {
  font-size: 0.719rem;
  color: var(--text-3);
  display: block;
}
.issuer {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}
.issuer .avatar {
  width: 1.625rem;
  height: 1.625rem;
  font-size: 0.6875rem;
}
.issuer .nm {
  font-size: 0.8125rem;
  font-weight: 600;
  white-space: nowrap;
}
.issuer .rl {
  font-size: 0.719rem;
  color: var(--text-3);
  white-space: nowrap;
}

/* 승인 요청 카드 */
.ap-card {
  padding: 1rem 1.125rem 1.125rem;
  border-width: 1px;
  border-style: solid;
}
.ap-card.review {
  border-color: var(--amber-border);
  background: var(--amber-weak);
}
/* 담당자 액션 카드(accent 테마) — 디자인 시안의 .ap-card.request */
.ap-card.request {
  border-color: var(--accent-border);
  background: var(--accent-weak);
}
.ap-card.request .ap-card-ico {
  color: var(--accent);
  border-color: var(--accent-border);
}
.ap-card.request .ap-card-title {
  color: var(--accent-hover);
}
/* 수정 요청됨(빨강 테마) */
.ap-card.changes {
  border-color: var(--red-border);
  background: var(--red-weak);
}
.ap-card.changes .ap-card-ico {
  color: var(--red);
  border-color: var(--red-border);
}
.ap-card.changes .ap-card-title {
  color: var(--red);
}
/* 승인 완료(초록 테마) */
.ap-card.done {
  border-color: #cde8d8;
  background: var(--green-weak);
}
.ap-card.done .ap-card-ico {
  color: var(--green);
  border-color: #cde8d8;
}
.ap-card.done .ap-card-title {
  color: #167a44;
}
.ap-card-head {
  display: flex;
  align-items: center;
  gap: 0.5625rem;
}
.ap-card-ico {
  width: 1.875rem;
  height: 1.875rem;
  border-radius: 0.5rem;
  background: #fff;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  border: 1px solid var(--amber-border);
  color: var(--amber);
}
.ap-card-ico svg {
  width: 1.0625rem;
  height: 1.0625rem;
}
.ap-card-title {
  font-size: 0.875rem;
  font-weight: 700;
  white-space: nowrap;
  color: #8a5a12;
}
.ap-card-meta {
  font-size: 0.75rem;
  color: var(--text-2);
  margin-top: 0.625rem;
  line-height: 1.5;
}
.ap-card-meta b {
  color: var(--text);
  font-weight: 600;
}
.ap-card-desc {
  font-size: 0.781rem;
  color: var(--text-2);
  margin-top: 0.625rem;
  line-height: 1.55;
}
.ap-card-desc b {
  color: var(--text);
  font-weight: 600;
}
.ap-card-note {
  font-size: 0.781rem;
  color: var(--text);
  background: #fff;
  border: 1px solid var(--amber-border);
  border-radius: 0.5rem;
  padding: 0.5625rem 0.6875rem;
  margin-top: 0.625rem;
  line-height: 1.5;
}
.ap-card-actions {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 1.125rem;
}
.ap-btn {
  height: 2.375rem;
  padding: 0 0.875rem;
  border-radius: var(--radius);
  font-size: 0.844rem;
  font-weight: 600;
  cursor: pointer;
  border: 1px solid var(--border-strong);
  background: #fff;
  color: var(--text-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  font-family: inherit;
}
.ap-btn svg {
  width: 0.9375rem;
  height: 0.9375rem;
}
.ap-btn.reject:hover {
  background: var(--red-weak);
  border-color: var(--red-border);
  color: var(--red);
}
.ap-btn.approve {
  background: var(--green);
  border-color: var(--green);
  color: #fff;
  box-shadow: 0 1px 2px rgba(31, 157, 87, 0.4);
}
.ap-btn.approve:hover {
  background: #188a4b;
}
.ap-btn.request-send {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  box-shadow: 0 1px 2px rgba(79, 70, 229, 0.4);
}
.ap-btn.request-send:hover {
  background: var(--accent-hover);
}

/* ===== 5단계: 첨부파일 ===== */
.files-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
a.file-ico,
a.file-info {
  text-decoration: none;
}
/* 배지(.file-ico)는 흰 글자 유지, 파일명(.file-info)만 본문색 상속 */
a.file-ico {
  color: #fff;
}
a.file-info {
  color: inherit;
}
.files-empty {
  font-size: 0.781rem;
  color: var(--text-3);
  padding: 0.25rem 0;
}

/* ===== 5단계: 수정 요청 메모(지시자 → 담당자) ===== */
.ap-note-box {
  font-size: 0.781rem;
  color: var(--text-2);
  line-height: 1.55;
  background: rgba(20, 24, 33, 0.04);
  border-radius: var(--radius);
  padding: 0.5625rem 0.6875rem;
  margin-bottom: 0.75rem;
  white-space: pre-wrap;
}

/* ===== 중간 점검: 항목 진행상태 컨트롤 ===== */
.ws-seg {
  margin-left: auto;
  display: inline-flex;
  gap: 0.125rem;
  background: #eceef1;
  border-radius: var(--radius-sm);
  padding: 0.125rem;
  flex-shrink: 0;
}
.ws-btn {
  border: none;
  background: transparent;
  font-family: inherit;
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--text-3);
  padding: 0.1875rem 0.4375rem;
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
}
.ws-btn:hover:not(:disabled) {
  color: var(--text);
}
.ws-btn:disabled {
  cursor: default;
}
.ws-btn.active {
  background: #fff;
  box-shadow: var(--shadow-sm);
}
.ws-btn.active.doing {
  color: var(--blue);
}
.ws-btn.active.done {
  color: #1b7a3d;
}
.ws-btn.active.todo {
  color: var(--text);
}
.ws-badge {
  margin-left: auto;
  flex-shrink: 0;
  font-size: 0.6875rem;
  font-weight: 700;
  padding: 0.0625rem 0.4375rem;
  border-radius: 20px;
}
.ws-badge.doing {
  color: var(--blue);
  background: var(--blue-weak, #e8effd);
}
.ws-badge.done {
  color: #1b7a3d;
  background: var(--green-weak);
}
.ws-badge.todo {
  color: var(--text-3);
  background: #eceef1;
}

/* ===== 중간 점검: 점검일/보고 카드 ===== */
.cp-d-list {
  list-style: none;
  margin: 0 0 0.625rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6875rem;
}
.cp-d-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.125rem 0;
  font-size: 0.844rem;
  color: var(--text);
}
.cp-d-date {
  font-weight: 600;
}
/* 상태 배지(span/button 공용) */
.cp-state {
  margin-left: auto;
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 700;
  padding: 0.1875rem 0.5625rem;
  border: none;
  border-radius: 20px;
  color: var(--text-3);
  background: #eceef1;
}
.cp-state.today {
  color: var(--accent);
  background: var(--accent-weak);
}
.cp-state.reported {
  color: #1b7a3d;
  background: var(--green-weak);
}
.cp-state.missed {
  color: var(--red);
  background: var(--red-weak);
}
.cp-state.clickable {
  cursor: pointer;
}
.cp-state.reported.clickable:hover {
  background: #cfe9d8;
}
.cp-report-btn {
  margin-left: auto;
  border: none;
  background: var(--accent);
  color: #fff;
  font-family: inherit;
  font-size: 0.781rem;
  font-weight: 700;
  padding: 0.375rem 0.8125rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.cp-report-btn:hover {
  background: var(--accent-hover);
}
/* 보고서 작성 폼 */
.cp-form {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.625rem;
  margin-bottom: 0.75rem;
  background: #fafbfc;
}
.cp-form-title {
  font-size: 0.781rem;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 0.5rem;
}
.cp-form-items {
  display: flex;
  flex-direction: column;
  gap: 0.4375rem;
  margin-bottom: 0.5rem;
}
.cp-form-item {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.cp-fi-text {
  font-size: 0.75rem;
  color: var(--text);
}
.cp-form-item .ws-seg {
  margin-left: 0;
  align-self: flex-start;
}
.cp-form-empty {
  font-size: 0.75rem;
  color: var(--text-3);
  margin: 0 0 0.5rem;
}
.cp-form-actions {
  display: flex;
  justify-content: flex-end;
}
.cp-send-btn {
  border: none;
  background: var(--accent);
  color: #fff;
  font-family: inherit;
  font-size: 0.781rem;
  font-weight: 700;
  padding: 0.375rem 0.75rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.cp-send-btn:hover:not(:disabled) {
  background: var(--accent-hover);
}
.cp-send-btn:disabled {
  opacity: 0.6;
  cursor: default;
}
.cp-note-input {
  width: 100%;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 0.4375rem 0.5625rem;
  font-family: inherit;
  font-size: 0.781rem;
  color: var(--text);
  resize: vertical;
  margin-bottom: 0.5rem;
}
.cp-note-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-weak);
}
/* 보고 열람 모달 */
.cp-modal {
  width: min(34rem, calc(100vw - 2rem));
  max-height: calc(100vh - 4rem);
  display: flex;
  flex-direction: column;
  background: #fff;
  border-radius: 0.875rem;
  box-shadow: 0 1.5rem 4.375rem rgba(20, 24, 33, 0.3);
  overflow: hidden;
}
.cp-modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 1.125rem 1.25rem 0.875rem;
  border-bottom: 1px solid var(--border);
}
.cp-modal-title {
  font-size: 1rem;
  font-weight: 700;
  color: var(--text);
}
.cp-modal-sub {
  margin-top: 0.25rem;
  font-size: 0.781rem;
  color: var(--text-3);
}
.cp-modal-note {
  font-size: 0.8125rem;
  color: var(--text-2);
  line-height: 1.6;
  white-space: pre-wrap;
  padding: 0.75rem 1.25rem;
  background: rgba(20, 24, 33, 0.03);
  border-bottom: 1px solid var(--border);
}
/* 카테고리 탭 */
.cat-tabs {
  display: flex;
  gap: 0.25rem;
  padding: 0.75rem 1.25rem 0;
}
.cat-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  border: none;
  background: transparent;
  font-family: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text-2);
  padding: 0.375rem 0.625rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.cat-tab:hover {
  background: #f1f2f4;
}
.cat-tab .cat-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--text-3);
}
.cat-tab.sec-1 .cat-dot {
  background: var(--accent);
}
.cat-tab.sec-2 .cat-dot {
  background: #1b7a47;
}
.cat-tab.sec-3 .cat-dot {
  background: #c0394f;
}
.cat-tab.sec-4 .cat-dot {
  background: #a96a13;
}
.cat-tab .cat-count {
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--text-3);
}
.cat-tab.active {
  background: var(--accent-weak);
  color: var(--accent);
}
.cat-tab.active .cat-count {
  color: var(--accent);
}
/* 모달 항목 목록 */
.cp-modal-items {
  padding: 0.75rem 1.25rem 1.25rem;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.cp-modal-item {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}
.cp-modal-item .ws-badge {
  margin-left: 0;
  flex-shrink: 0;
}
.cp-mi-text {
  font-size: 0.8125rem;
  color: var(--text);
}
.cp-modal-empty {
  font-size: 0.8125rem;
  color: var(--text-3);
  padding: 1rem 0;
  text-align: center;
}
</style>

