Skip to content

gonnabe88/it_frontend

Repository files navigation

IT Portal 프론트엔드 개발 노트

1. 시스템 개요

IT Portal(IT 정보화 포탈)은 정보화 예산, 사업, 인력을 관리하는 사내 포털 시스템입니다.
약 3,000명의 임직원이 정보화사업 신청, 예산 현황 조회, 전자결재 등의 기능을 이용합니다.

주요 모듈:

  • 정보화사업 관리(프로젝트 등록/조회/결재)
  • 예산 관리(전산예산/전산업무비/예산현황/예산 작업)
  • 요구사항 정의서 및 사전협의(문서 검토/코멘트)
  • 전자결재 프로세스
  • 정보화실무협의회(타당성검토/위원선정/평가/결과)
  • 시스템 관리(사용자/권한/조직/코드 관리)

디자인 및 스타일링

  • PrimeVue Aura 테마 기반 UI
  • Tailwind CSS로 추가 스타일링 및 커스텀 유틸리티(kdb-tag-* 등)
  • app/assets/css/main.css에 전역 색상 정의 및 다크모드 스타일
  • StyledDataTable.vue로 모든 DataTable 통일(파란 헤더, gridlines, resizable)

2. 기술 스택

구분 기술 버전 용도
프레임워크 Nuxt 4 v4.0.0 Composition API + <script setup>, CSR(SPA) 모드
UI 라이브러리 PrimeVue v4.5.4 Aura 테마, 한국어 로케일, DataTable/Dialog/Dropdown 등
스타일링 Tailwind CSS v3.4.17 유틸리티 기반 스타일, main.css에 커스텀 색상(kdb-tag-*)
상태관리 Pinia v3.0.4 인증 상태(stores/auth.ts), 사전협의 세션(stores/review.ts)
리치 텍스트 Tiptap v3.22.2 요구사항 정의서/가이드 문서 에디터 (20+ 확장)
다이어그램 Excalidraw v0.17.6 Tiptap 커스텀 노드로 통합, 다이어그램 삽입/재편집
PDF 생성 pdfmake v0.3.3 정보화사업 보고서 PDF 생성, 한글 폰트 지원
Excel 내보내기 ExcelJS v4.2.0 예산/비용 데이터 Excel 내보내기
한글(HWPX) JSZip v3.10.1 HTML→HWPX 변환 (클라이언트 전용 변환, utils/hwpx.ts)
XSS 방어 isomorphic-dompurify v3.0.0 v-html 사용 시 필수 새니타이징
이미지 캡처 html2canvas v1.4.1 화면 스크린샷 생성
수식 입력 mathlive v0.109.0 인라인/블록 LaTeX 수식 노드
코드 하이라이팅 lowlight v3.3.0 Tiptap 코드 블록 문법 강조
차트 Chart.js v4.5.1 대시보드 KPI, 월별 통계 시각화
AI 연동 Gemini API - GeminiChat.vue 플로팅 채팅 패널

3. 프로젝트 구조

소스 루트: app/ (Nuxt 4 convention)

app/
├── components/                      # 재사용 컴포넌트 (63개 .vue 파일)
│   ├── AppHeader.vue                # 상단 헤더 (메가메뉴, 탭바, 사용자 메뉴, 테마 토글)
│   ├── AppSidebar.vue               # 좌측 사이드바 (동적 메뉴, 결재 대기 배지)
│   ├── PageHeader.vue               # 페이지 제목/경로 표시
│   ├── GlobalSearchBar.vue          # 전역 검색바
│   ├── TiptapEditor.vue             # Tiptap 통합 리치 텍스트 에디터 (20+ 확장)
│   ├── TiptapToolbar.vue            # Tiptap 포맷팅 툴바
│   ├── TiptapTableFloatingToolbar.vue # Tiptap 표 편집 플로팅 툴바
│   ├── GeminiChat.vue               # AI 채팅 플로팅 패널 (Gemini API 연동)
│   ├── ExcalidrawWrapper.vue        # Excalidraw React 라이브러리 브리지
│   ├── ExcalidrawNodeView.vue       # Tiptap 다이어그램 노드 뷰
│   ├── ResizableImageNodeView.vue   # Tiptap 크기 조절 이미지 노드
│   ├── AttachmentNodeView.vue       # Tiptap 첨부파일 노드
│   ├── InlineMathNodeView.vue       # Tiptap 인라인 LaTeX 수식 노드
│   ├── BlockMathNodeView.vue        # Tiptap 블록 LaTeX 수식 노드
│   ├── ApplicationViewerDialog.vue  # 신청서 뷰어 다이얼로그 (결재용)
│   ├── AppDialogHeader.vue          # 다이얼로그 헤더 (공통)
│   ├── AppDialogFooter.vue          # 다이얼로그 푸터 (공통)
│   ├── TableCard.vue                # 테이블 카드 래퍼
│   ├── approval/                    # 전자결재 컴포넌트 (2개)
│   │   ├── ApprovalTimeline.vue     # 결재 프로세스 타임라인
│   │   └── ApplicationViewerDialog.vue
│   ├── budget/                      # 예산 컴포넌트 (5개)
│   │   ├── BudgetSummaryCards.vue   # 예산 현황 4개 카드
│   │   ├── BudgetTableActions.vue   # 목록 행동 컴포넌트
│   │   ├── BudgetSummaryResultTable.vue # 예산 편성 결과 테이블
│   │   ├── BudgetProjectSummaryTable.vue # 예산 사업 요약 테이블
│   │   └── BudgetTargetTable.vue    # 예산 목표 테이블
│   ├── cost/                        # 전산업무비 컴포넌트 (3개)
│   │   ├── CostFormTableSection.vue # 비용 폼 섹션
│   │   ├── TerminalTableSection.vue # 단말기 테이블 섹션
│   │   └── TerminalFormDialog.vue   # 단말기 폼 다이얼로그
│   ├── council/                     # 정보화실무협의회 컴포넌트 (15개)
│   │   ├── CouncilStatusBadge.vue   # 협의회 상태 배지
│   │   ├── committee/               # 위원 선정 (CommitteeSelector, CommitteeList)
│   │   ├── evaluation/              # 평가 (EvaluationForm, EvalSummaryPanel)
│   │   ├── feasibility/             # 타당성검토 (4개)
│   │   ├── schedule/                # 일정 (ScheduleInput, ScheduleStatus)
│   │   ├── qna/                     # 사전질의 (CouncilQna)
│   │   ├── notice/                  # 공지 (CouncilNotice)
│   │   └── result/                  # 결과 (ResultForm, ResultReview)
│   ├── plan/                        # 정보기술부문 계획 컴포넌트 (3개)
│   │   ├── PlanCapitalBudgetCard.vue
│   │   ├── PlanExpenseCostCard.vue
│   │   └── PlanBudgetAllocationCard.vue
│   ├── projects/                    # 정보화사업 컴포넌트 (3개)
│   │   ├── ProjectOverviewSection.vue # 프로젝트 개요
│   │   ├── ProjectProgressSection.vue # 진행 상황
│   │   └── ResourceTableSection.vue   # 소요자원 테이블
│   ├── review/                      # 사전협의/문서 검토 컴포넌트 (5개)
│   │   ├── ReviewEditor.vue         # 협의 코멘트 에디터
│   │   ├── ReviewToolbar.vue        # 협의 제목/상태 툴바
│   │   ├── ReviewMessenger.vue      # 협의 코멘트 메시지
│   │   ├── ReviewCommentPopover.vue # 인라인 코멘트 팝오버
│   │   └── ReviewVersionHistory.vue # 버전 이력 뷰
│   ├── icons/                       # 커스텀 아이콘 (1개)
│   │   └── IconCrown.vue
│   ├── extensions/                  # Tiptap 커스텀 확장
│   └── common/                      # 공용 컴포넌트
│       ├── StyledDataTable.vue      # PrimeVue DataTable 래퍼 (파란 헤더, gridlines)
│       ├── EmployeeSearchDialog.vue # 직원 검색 다이얼로그 (조직 트리 + 사용자 목록)
│       ├── EmployeeInfoDialog.vue   # 직원 정보 상세 다이얼로그
│       ├── VersionHistoryDialog.vue # 버전 이력 다이얼로그
│       ├── TableSearchInput.vue     # 테이블 검색 입력
│       ├── InlineEditCell.vue       # 테이블 인라인 편집 셀
├── composables/                      # 비즈니스 로직 및 API 래퍼 (41개 .ts 파일)
│   │
│   ├── 인증 및 API 래퍼
│   │   ├── useAuth.ts                # 인증 상태 접근 (Pinia 래퍼, $apiFetch 제공)
│   │   ├── useApiFetch.ts            # GET 요청 래퍼 (useFetch 기반, httpOnly 쿠키 자동 전송)
│   │   ├── useAdminApi.ts            # 관리자 CRUD API (공통코드/사용자/역할/조직/파일/토큰/이력)
│   │   └── useAdminTableEdit.ts      # 관리자 테이블 인라인 편집
│   │
│   ├── 정보화사업 및 프로젝트
│   │   ├── useProjects.ts            # 정보화사업 CRUD (/api/projects)
│   │   ├── useProjectOptions.ts      # 프로젝트 옵션 (연도/분류/상태 코드)
│   │
│   ├── 예산 관리
│   │   ├── useBudgetPeriod.ts        # 예산 기준연도/기간 미들웨어 보조
│   │   ├── useBudgetStatus.ts        # 예산현황 3탭(정보화사업/전산업무비/경상사업) 조회
│   │   ├── useBudgetStatusCostTab.ts # 예산현황 전산업무비 탭 상세
│   │   ├── useBudgetAllocationSummary.ts # 예산 할당 요약
│   │
│   ├── 전산업무비
│   │   ├── useCost.ts                # 전산업무비 CRUD (/api/costs)
│   │   ├── useCostListPage.ts        # 전산업무비 목록 페이지 상태
│   │   ├── useCostDuplicateCheck.ts  # 비용 중복 검사
│   │   └── usePrevYearCostPicker.ts  # 전년도 비용 선택기
│   │
│   ├── 요구사항 정의서 및 사전협의
│   │   ├── useDocuments.ts           # 요구사항 정의서 CRUD (/api/documents)
│   │   ├── useDocumentDashboard.ts   # 사전협의 대시보드 (KPI + 월별 통계 + 배지 카운트)
│   │   ├── useReview.ts              # 사전협의 세션 관리 (Pinia 스토어 래퍼)
│   │   └── useReviewCommentApi.ts    # 사전협의 코멘트 서버 동기화
│   │
│   ├── 정보기술부문 계획
│   │   └── usePlan.ts                # 정보기술부문 계획 CRUD (/api/plans)
│   │
│   ├── 정보화실무협의회
│   │   ├── useCouncil.ts             # 협의회 CRUD 및 상태 전이 (/api/council)
│   │   └── useCouncilCodes.ts        # 협의회 상태/심의유형 코드 조회
│   │
│   ├── 전자결재
│   │   ├── useApprovals.ts           # 전자결재 CRUD (/api/applications)
│   │   ├── useApprovalDashboard.ts   # 전자결재 대시보드 (KPI + 월별 통계 + 배지)
│   │   └── usePendingApprovalCount.ts # 결재 대기 배지 카운트
│   │
│   ├── 공통 데이터 및 조직
│   │   ├── useCodeOptions.ts         # 공통코드 옵션 조회 (/api/ccodem)
│   │   ├── useOrganization.ts        # 조직도 및 사용자 조회
│   │   ├── useEmployeeSearch.ts      # 직원 검색 (AutoComplete + 다이얼로그)
│   │   ├── useCurrencyRates.ts       # 환율 조회 및 원화 환산
│   │
│   ├── 파일 및 내보내기
│   │   ├── useFiles.ts               # 첨부파일 업로드/다운로드/미리보기
│   │   ├── useHwpxExport.ts          # HWPX(한글) 파일 내보내기
│   │   └── usePdfReport.ts           # PDF 보고서 생성 (정보화사업)
│   │
│   ├── Tiptap 에디터 관련
│   │   ├── useExcalidrawDialog.ts    # Excalidraw 편집 다이얼로그 상태
│   │   ├── useExcalidrawAttachment.ts # Excalidraw 첨부파일 임시 ID 추적
│   │   ├── useTiptapImageInsertion.ts # Tiptap 이미지 삽입 및 업로드
│   │   └── useTiptapTableTools.ts    # Tiptap 표 편집 유틸리티
│   │
│   ├── UI/UX 유틸리티
│   │   ├── useGlobalSearch.ts        # 전역 검색 상태 및 API
│   │   ├── useTabs.ts                # 멀티탭 네비게이션 관리
│   │   ├── useTableCellSelection.ts  # 엑셀 스타일 셀 선택/복사
│   │   ├── useTableColumnResize.ts   # 테이블 열 너비 조정
│   │   ├── useDateRangeValidation.ts # 날짜 범위 유효성 검사
│   └── useGuideDocuments.ts          # 가이드 문서 CRUD
├── layouts/                          # 페이지 레이아웃 (3개)
│   ├── default.vue                  # 기본 레이아웃 (AppHeader + AppSidebar + 콘텐츠)
│   ├── admin.vue                    # 관리자 전용 레이아웃 (default와 동일, /admin 경로용)
│   └── login.vue                    # 로그인 페이지 전용 레이아웃
│
├── middleware/                       # 라우트 가드 및 인증 미들웨어 (3개)
│   ├── auth.global.ts               # 전역 인증 미들웨어 (미인증 사용자 /login 리다이렉트)
│   ├── admin.ts                     # 관리자 접근 제어 (ROLE.ADMIN 검증)
│   └── budget-period.ts             # 예산 기준기간 라우트 가드
│
├── pages/                            # 파일 기반 라우팅 (46개 .vue 파일)
│   ├── index.vue                    # 루트 (/ → /info 리다이렉트)
│   ├── login.vue                    # 로그인 페이지
│   │
│   ├── admin/                       # 시스템 관리 (ROLE.ADMIN 전용, 10개 페이지)
│   │   ├── index.vue                # 관리자 대시보드
│   │   ├── codes.vue                # 공통코드 관리
│   │   ├── users.vue                # 사용자 관리
│   │   ├── roles.vue                # 역할 관리
│   │   ├── grades.vue               # 자격등급 관리
│   │   ├── organizations.vue        # 조직 관리
│   │   ├── files.vue                # 파일 관리
│   │   ├── tokens.vue               # 토큰 관리
│   │   ├── audit-logs.vue           # 감사로그 조회
│   │   └── health.vue               # 시스템 상태
│   │
│   ├── approval/                    # 전자결재 (2개)
│   │   ├── index.vue                # 전자결재 목록 + 대시보드 (KPI 카드)
│   │   └── [id].vue                 # 결재 상세 뷰 + 처리 폼
│   │
│   ├── audit/                       # IT 자체감사 (1개)
│   │   └── index.vue                # 감사 대시보드
│   │
│   ├── budget/                      # 예산 관리 (5개)
│   │   ├── index.vue                # 예산 작성 유형 선택 페이지
│   │   ├── list.vue                 # 예산 통합 목록 (전체/정보화사업/전산업무비/경상사업 탭)
│   │   ├── status.vue               # 예산현황 (예산액/집행율 조회, 3탭)
│   │   ├── work.vue                 # 예산 작업 (편성률 일괄 적용)
│   │   └── detail-[type]-[id].vue   # 예산 상세 및 편성
│   │
│   ├── diagnosis/                   # 사전진단 (1개)
│   │   └── index.vue                # 사전진단 설문
│   │
│   ├── guide/                       # 가이드 문서 (3개)
│   │   ├── index.vue                # 가이드 문서 목록
│   │   ├── [id].vue                 # 가이드 문서 상세 (Tiptap 뷰)
│   │   └── form.vue                 # 가이드 문서 등록/수정 (Tiptap 에디터)
│   │
│   ├── info/                        # 주요 정보 관리
│   │   ├── index.vue                # 대시보드 (홈)
│   │   │
│   │   ├── cost/                    # 전산업무비 (4개)
│   │   │   ├── index.vue            # 전산업무비 목록
│   │   │   ├── [id].vue             # 전산업무비 상세
│   │   │   ├── form.vue             # 전산업무비 등록/수정 폼
│   │   │   └── terminal.vue         # 단말기 관리 페이지
│   │   │
│   │   ├── documents/               # 요구사항 정의서 (5개)
│   │   │   ├── index.vue            # 요구사항 정의서 목록
│   │   │   ├── [id].vue             # 요구사항 정의서 상세 뷰
│   │   │   ├── form.vue             # 요구사항 정의서 등록/수정 폼 (Tiptap)
│   │   │   ├── review-[id].vue      # 요구사항 정의서 사전협의 페이지
│   │   │   └── council-request/     # 협의회 신청 (서브 페이지)
│   │   │
│   │   ├── plan/                    # 정보기술부문 계획 (3개)
│   │   │   ├── index.vue            # 정보기술부문 계획 목록
│   │   │   ├── [id].vue             # 정보기술부문 계획 상세
│   │   │   └── form.vue             # 정보기술부문 계획 등록/수정 폼
│   │   │
│   │   └── projects/                # 정보화사업 (4개)
│   │       ├── index.vue            # 정보화사업 목록
│   │       ├── [id].vue             # 정보화사업 상세 (프로젝트 개요/진행/자원)
│   │       ├── form.vue             # 정보화사업 등록/수정 폼 (Tiptap)
│   │       └── report.vue           # 정보화사업 보고서 조회/PDF 생성
├── plugins/                          # Nuxt 플러그인 (1개)
│   └── auth.ts                      # $apiFetch 전역 제공 (인증 인터셉터, 401 갱신 처리)
│
├── stores/                           # Pinia 상태관리 (2개)
│   ├── auth.ts                      # 인증 상태 (로그인/로그아웃/갱신/세션 복원, it-portal-user 쿠키 기반)
│   └── review.ts                    # 사전협의 세션 상태 (현재 문서/버전/코멘트/검토자, 메모리 저장)
│
├── types/                            # TypeScript 타입 정의 (5개)
│   ├── auth.ts                      # User/UserRole/ROLE 상수 (ADMIN/USER/DEPT_MANAGER)
│   ├── budget-work.ts               # 예산 작업 타입 (편성비목/편성결과)
│   ├── budgetStatus.ts              # 예산현황 조회 응답 타입
│   ├── council.ts                   # 정보화실무협의회 타입 (CouncilItem/위원/평가 등)
│   └── review.ts                    # 사전협의 타입 (ReviewSession/Comment/Reviewer 등)
│
└── utils/                            # 유틸리티 함수 (3개)
    ├── common.ts                    # 공통 유틸 (formatBudget, getApprovalTagClass, getProjectTagClass, 전결권 계산)
    ├── excel.ts                     # Excel 내보내기 (ExcelJS 기반)
    └── hwpx.ts                      # HTML→HWPX 변환 (HWP 파일 구조 분석, 이미지 패키징)

4. 핵심 설계 결정 (Design Decisions)

4.0 CSR(SPA) 모드로 SSG/SSR 회피

설정: nuxt.config.tsssr: false

배경:

  • 초기 SSG(npm run generate) + nginx에서 SSO 무한 리다이렉트 발생
  • 원인: 빌드 시점 Pinia 스토어 user=null_payload.json에 직렬화 → 클라이언트 하이드레이션 시 it-portal-user 쿠키 기반 복원을 null로 덮어쓰는 타이밍 이슈
  • 사내 포털이라 SEO 불필요 → CSR(완전 클라이언트 렌더링)이 최적

결과:

  • 모든 라우트가 동일 index.html에서 클라이언트 라우팅
  • 서버 미들웨어/페이로드 생성 불필요

4.1 이중 API 호출 패턴

패턴 구현체 용도 인증/401 처리
useApiFetch<T> Nuxt useFetch 래핑 GET 요청 (조회) credentials: 'include' + 401 시 refresh 후 재호출
$apiFetch ofetch 래핑 (plugin/auth.ts) POST/PUT/DELETE 요청 (변경) 인터셉터로 credentials: 'include' + 401 처리

설계 이유:

  • useFetch: Nuxt 내장이므로 SSR/CSR 양쪽 호환, 캐싱과 중복 호출 방지가 자동. GET 요청에 최적화
  • $apiFetch: ofetch 기반으로 간단한 POST/PUT/DELETE 변경 요청 처리, 플러그인으로 인증 로직 중앙화

주의: stores/auth.ts 내부에서는 $apiFetch 사용 불가 (순환 참조). 대신 Nuxt 내장 $fetch를 직접 사용.

4.2 httpOnly 쿠키 인증 전략

토큰 저장:

  • 서버가 Set-Cookie 헤더로 httpOnly 쿠키에 JWT(accessToken, refreshToken) 저장
  • JavaScript에서 직접 접근 불가 → XSS 시 토큰 탈취 방지
  • 프론트는 credentials: 'include'만 설정하면 브라우저가 자동 전송

인증 상태 관리:

  • 사용자 정보는 별도 쿠키(it-portal-user)에 JSON 문자열로 저장
  • 로그인 후: useCookie('it-portal-user')로 상태 복원
  • 새로고침/탭 전환: 쿠키 기반으로 자동 재인증
  • 401 응답: plugin/auth.ts의 인터셉터에서 refresh 호출 → 새 토큰 쿠키 세팅 → 원요청 재시도

4.3 다크모드 FOUC(Flash of Unstyled Content) 방지

문제:

  • Nuxt 하이드레이션 중 테마가 바뀌면 화면 깜빡임 발생

해결:

  • nuxt.config.tsapp.head.script 인라인 스크립트가 하이드레이션 이전에 실행
  • theme-dark 쿠키 읽기 → <html class="dark"> + color-scheme 즉시 적용
  • SSR 없으므로 클라이언트 초기 렌더링부터 올바른 테마 적용

4.4 직원 검색 다이얼로그 공유 패턴

문제: 6개 폼 필드(주관부서/IT부서/팀장/담당자 등)에서 같은 직원 검색 다이얼로그를 사용하되, 선택 시 각 필드에 맞게 바인딩해야 함

해결:

// 1. 어떤 필드에서 열었는지 추적
const activeDialogField = ref<'svnDpm' | 'itDpm' | ...>('svnDpm');

// 2. 필드별 헤더 derived from activeDialogField
const FIELD_HEADERS = { svnDpm: '주관부서 검색', svnDpmTlr: '팀장 검색', ... } as const;
const dialogHeader = computed(() => FIELD_HEADERS[activeDialogField.value]);

// 3. 선택 시 FIELD_CONFIG 매핑으로 폼 데이터에 세팅
const FIELD_CONFIG = {
  svnDpm:    { valueKey: 'orgCode',  labelKey: 'bbrNm' },   // 부서코드/부서명
  svnDpmTlr: { valueKey: 'eno',      labelKey: 'usrNm' },   // 직원번호/이름
  itDpm:     { valueKey: 'orgCode',  labelKey: 'bbrNm' },   // IT 부서코드
  itDpmTlr:  { valueKey: 'eno',      labelKey: 'usrNm' },   // IT 팀장 직원번호
};

// 4. 선택 후
const onEmployeeSelected = (employee) => {
  const config = FIELD_CONFIG[activeDialogField.value];
  formData[activeDialogField.value] = employee[config.valueKey];
  formData[activeDialogField.value + 'Nm'] = employee[config.labelKey];
};

구현: composables/useEmployeeSearch.ts + EmployeeSearchDialog.vue

4.5 DOMPurify XSS 방지 (필수)

규칙: 모든 v-html 사용 전에 isomorphic-dompurify로 새니타이징

<!-- 금지 -->
<div v-html="richTextContent" />

<!-- 필수 -->
<div v-html="DOMPurify.sanitize(richTextContent)" />

적용 대상:

  • prjDes, prjRng (정보화사업 설명/범위)
  • 요구사항 정의서/가이드 문서의 HTML 콘텐츠
  • 검토의견 렌더링

4.6 Tiptap 에디터 통합 아키텍처

20개 이상의 확장으로 완전한 리치 텍스트 편집 지원:

범주 확장 용도
기본 StarterKit 문단, 제목, 굵음, 기울임, 코드 블록 등
Table, TableCell, TableRow, TableHeader 표 삽입/편집 + 너비 저장 커스텀
이미지 Image, ResizableImage (커스텀) 이미지 삽입 + 크기 조절 + base64/API 업로드
다이어그램 ExcalidrawExtension (커스텀) Excalidraw 다이어그램 노드로 통합
수식 InlineMath, BlockMath (커스텀 + mathlive) LaTeX 수식 렌더링
파일 AttachmentNode (커스텀) 첨부파일 노드 및 다운로드
포맷 TextStyle, Color, Highlight, TextAlign 색상, 배경색, 정렬
유틸 CharacterCount, Placeholder 글자 수 제한, 플레이스홀더
Task TaskList, TaskItem 체크리스트

상태 관리:

  • useExcalidrawDialog() composable로 NodeView와 Editor 간 통신
  • 이미지/파일 업로드는 useFiles.ts + useTiptapImageInsertion.ts로 처리

목차(TOC) 생성:

  • Heading 노드에 자동 id 부여
  • @update:toc 이벤트로 부모 페이지에 전달
  • 가이드 문서/요구사항 정의서에서 자동 생성

4.7 HWPX 내보내기 (클라이언트 전용 한글 문서 변환)

구현: utils/hwpx.ts (대규모 유틸리티)

프로세스:

HTML 문자열 (Tiptap 에디터 콘텐츠)
  ↓ HTML 파싱 및 DocNode로 변환
  ↓ ParagraphNode (텍스트/포맷), TableNode (셀/병합) 생성
  ↓ 이미지를 Base64 또는 파일에서 로드 (utils/hwpx-images.ts)
  ↓ HWP XML 구조 생성 (utils/hwpx-package-xml.ts)
  ↓ JSZip으로 압축
  ↓ HWPX 파일 다운로드

포맷:

  • 폰트: 맑은 고딕 (본문 10pt, H1 15pt, H3 12pt)
  • 표: th/td 구분, 셀 병합, 파란 배경 헤더, 균등 열배분
  • 이미지: 크기 조절 적용

테스트: tests/unit/utils/hwpx.test.ts에서 HTML 파싱, 노드 변환, XML 생성 검증

4.8 관리자 권한 제어 (3계층 RBAC)

1. 클라이언트 라우트 가드 (UX):

// middleware/admin.ts
export default defineRouteMiddleware((to, from) => {
  const { user } = useAuth();
  if (!user.value?.athIds?.includes(ROLE.ADMIN)) {
    return navigateTo('/');
  }
});

2. 페이지 레이아웃 (렌더링):

// pages/admin/*.vue
definePageMeta({ layout: 'admin' });

3. 백엔드 API 보안 (진정한 보호):

// it_backend/SecurityConfig
.requestMatchers("/api/admin/**").hasRole("ADMIN")
// 각 컨트롤러
@PreAuthorize("hasRole('ADMIN')")
@RestController
@RequestMapping("/api/admin")
public class AdminController { ... }

관리 대상: 공통코드, 사용자, 역할, 자격등급, 조직, 파일, 토큰, 감사로그 (8개 도메인)

4.9 사전협의(문서 검토) 프로세스

상태 관리: stores/review.ts (Pinia, 메모리 저장)

구조:

  • 세션: 현재 열린 문서 ID, 검토자, 작성자
  • 버전: v0.0(초안) → v0.1, v0.2... (검토요청마다 스냅샷)
  • 코멘트: 인라인 코멘트 (Tiptap Mark 기반, 텍스트 구간에 부착)
  • 검토자: 역할 구분 (author, reviewer)

주의점 (TASK.md):

  • 세션/코멘트 서버 영속화 필요 (현재 메모리만 저장, 새로고침 시 손실)
  • 프로젝트별 검토자 목록을 서버에서 조회 (현재 하드코딩)
  • 검토의견 DTO에 작성자 팀명/첨부파일 필드 추가 필요

4.10 정보기술부문 계획 (JSON 스냅샷 기반)

용도: 특정 시점의 정보화사업 계획서 관리

구조:

  • plans 테이블에 CRUD 지원
  • planDetails JSON 필드에 사업 목록 직렬화 (JSON 배열)
  • 계획 상세 조회 시 프로젝트 목록 함께 반환

구현: composables/usePlan.ts + pages/info/plan/

4.11 예산 작업 (편성률 일괄 적용)

워크플로우:

  1. 결재 완료된 예산 → 편성 대상
  2. 편성비목별로 편성률(0~100%) 입력
  3. pages/budget/work.vue에서 일괄 계산
  4. BBUGTM 테이블 upsert

타입: types/budget-work.ts (편성비목, 편성결과)

4.12 정보화실무협의회 (멀티 단계 프로세스)

프로세스: 신청 → 타당성검토 → 위원선정 → 일정확정 → 사전질의 → 평가 → 결과서

컴포넌트 그룹:

  • committee: 위원 선정 (CommitteeSelector, CommitteeList)
  • evaluation: 평가 (EvaluationForm, EvalSummaryPanel)
  • feasibility: 타당성검토 (4개: 개요, 체크리스트, 성과, 폼)
  • schedule: 일정 (ScheduleInput, ScheduleStatus)
  • qna: 사전질의 (CouncilQna)
  • notice: 공지 (CouncilNotice)
  • result: 결과서 (ResultForm, ResultReview)

상태 관리: useCouncil.ts + useCouncilCodes.ts (상태/심의 유형 코드)

5. 인증 흐름

1. 로그인   → stores/auth.ts login()  → 서버가 httpOnly JWT 쿠키 세팅 + user 정보는 it-portal-user 쿠키 저장
2. API 요청 → useApiFetch / $apiFetch → credentials:'include'로 httpOnly 쿠키 자동 전송
3. 401 응답 → onResponseError         → refresh() 호출 → 서버가 새 쿠키 세팅 → tokenRefreshSignal++로 재요청
4. 갱신 실패 → logout()                → user 쿠키 및 구버전 localStorage 잔존 데이터 정리 → /login 리다이렉트
5. 새로고침  → useCookie               → SSR/클라이언트에서 it-portal-user 쿠키 기반 인증 상태 복원
6. 구버전 데이터 → restoreSession()     → localStorage user가 남아 있으면 user 쿠키로 1회 마이그레이션

5. 환경변수

변수명 설명 기본값 설정 방식
NUXT_PUBLIC_API_BASE 백엔드 API 서버 주소 http://localhost:18080 .env 파일 또는 환경변수

6. 개발 환경 설정 및 실행

6.1 초기 설정

# 의존성 설치
npm install

# Nuxt 타입 정의 생성 (.nuxt/ 디렉토리)
npm run postinstall
# 또는
npx nuxt prepare

타입 오류 발생 시:

  • IDE에서 .nuxt/ 디렉토리 체크
  • npm run postinstall 다시 실행
  • node_modules/.nuxt 삭제 후 재설치

6.2 개발 서버 실행

# 개발 서버 (기본 포트 3000, HMR 활성화)
npm run dev

# 브라우저 자동 열기
http://localhost:3000

백엔드 서버 연동:

  • 동시에 cd ../it_backend && ./gradlew bootRun 실행 (포트 8080)
  • 로그인 페이지에서 정상 작동 확인

6.3 운영 빌드

# CSR(SPA) 정적 생성
npm run generate

# 결과: dist/ 디렉토리
# nginx 또는 Apache에서 index.html 기본 페이지로 설정하여 라우팅 처리

7. 테스트

목표: 80% 이상의 코드 커버리지 유지 (단위 + 통합 + E2E)

7.1 테스트 실행 명령어

단위 테스트 (Vitest)

# 전체 단위 테스트 실행
npm test

# 전체 단위 테스트 실행 (UI 모드)
npm run test:ui

# 파일 변경 감지 + 자동 재실행 (개발 중 사용)
npm run test:watch

# 코드 커버리지 리포트 생성 (coverage/ 디렉토리)
npm run test:coverage

E2E 테스트 (Playwright)

# Playwright 브라우저 설치 (최초 1회)
npx playwright install

# E2E 테스트 실행 (dev 서버 자동 기동)
npm run test:e2e

# Playwright 인터랙티브 UI 모드 (테스트 디버깅)
npm run test:e2e:ui

7.2 테스트 구조 및 현황 (2026-05-06 기준)

tests/
├── unit/                            # Vitest 단위 테스트 (12파일, 248+ 케이스)
│   ├── utils/
│   │   └── common.test.ts           # formatBudget, 상태 태그, API 오류 메시지 등
│   ├── stores/
│   │   ├── auth.test.ts             # login, logout, restoreSession, refresh, 쿠키 처리
│   │   └── review.test.ts           # 사전협의 세션/버전/코멘트 상태 관리
│   └── composables/
│       ├── useApiFetch.test.ts      # GET 래퍼, 인증 처리
│       ├── useAuth.test.ts          # Pinia 래퍼 composable
│       ├── useCost.test.ts          # 전산업무비 CRUD
│       ├── useProjects.test.ts      # 정보화사업 CRUD
│       ├── useBudgetStatus.test.ts  # 예산현황 3탭 조회
│       ├── useCouncil.test.ts       # 협의회 CRUD
│       ├── useApprovalDashboard.test.ts # KPI 대시보드
│       ├── useDocumentDashboard.test.ts # 사전협의 대시보드
│       ├── usePendingApprovalCount.test.ts # 배지 카운트
│       ├── useDateRangeValidation.test.ts # 날짜 범위 검증
│       └── (추가 작성 필요)
│
└── e2e/                             # Playwright E2E 테스트
    ├── auth.setup.ts                # 로그인 인증 설정 (모든 E2E의 사전 조건)
    ├── helpers/
    │   └── mockApi.ts               # 공통 Mock 헬퍼 (mockLoginApi, mockApi, setLoggedIn)
    ├── auth.spec.ts                 # 로그인 성공/실패 플로우
    ├── projects.spec.ts             # 정보화사업 목록 조회
    ├── cost.spec.ts                 # 전산업무비 목록 조회
    └── approval.spec.ts             # 전자결재 목록 조회

7.3 Mock 전략

단위 테스트 (Vitest):

// $fetch 전역 Mock
const mockFetch = vi.fn();
vi.stubGlobal('$fetch', mockFetch);

// 클라이언트 환경 시뮬레이션 (SSR 없음)
Object.assign(process, { client: true });

// Nuxt auto-import은 지원 안 함 → 명시적 import
import { ref, computed } from 'vue';
import { defineStore } from 'pinia';
import { useRuntimeConfig } from '#app'; // Mock 필요

E2E 테스트 (Playwright):

// API 응답 Mock
await page.route('**/api/projects', (route) =>
  route.fulfill({
    status: 200,
    body: JSON.stringify(mockData)
  })
);

// 사전 인증 쿠키 주입 (백엔드 없이 실행)
await page.addInitScript(() => {
  document.cookie = 'it-portal-user=' + JSON.stringify({
    eno: '123456',
    empNm: 'Test User',
    athIds: ['ITPZZ001']
  });
});

7.4 주의사항

Nuxt auto-import 미지원:

  • Vitest에서 #app, #imports 경로 해석 불가
  • ref, computed, defineStore 등은 'vue'/'pinia'에서 직접 import
  • useRuntimeConfig, navigateTo, useNuxtApp 등은 vi.stubGlobal()로 Mock

E2E 테스트 안정성:

  • waitForURL(), waitForLoadState('networkidle') 사용
  • setTimeout 대신 deterministic 대기 (요소 가시성, 네트워크 완료)
  • 테스트 순서 독립성 보장 (각 테스트마다 신선한 컨텍스트)

8. 백엔드 API 통합 맵

백엔드(it_backend)는 도메인 기반 레이어드 아키텍처(Spring Boot)를 사용합니다.
프론트엔드는 아래 API 경로를 기준으로 composable을 분리합니다.

도메인 백엔드 패키지 프론트 Composable API 경로
인증 common/system useAuth.ts /api/auth/**
사용자/조직 common/iam useEmployeeSearch.ts, useOrganization.ts /api/users/**, /api/organizations
신청서/결재 common/approval useApprovals.ts, useApprovalDashboard.ts /api/applications/**
공통코드 common/code useCodeOptions.ts /api/ccodem/**
시스템 관리 common/admin useAdminApi.ts /api/admin/**
정보화사업 budget/project useProjects.ts, useProjectOptions.ts /api/projects/**
전산업무비 budget/cost useCost.ts /api/costs/**
가이드 문서 budget/document useGuideDocuments.ts /api/guide-documents/**
정보기술부문 계획 budget/plan usePlan.ts /api/plans/**
예산 작업 budget/work - (직접 호출) /api/budget/work/**
예산현황 budget/status useBudgetStatus.ts /api/budget/status/**
요구사항 정의서 budget/document useDocuments.ts, useDocumentDashboard.ts /api/documents/**
검토의견(코멘트) budget/document useReviewCommentApi.ts /api/documents/{id}/review-comments/**
정보화실무협의회 council useCouncil.ts, useCouncilCodes.ts /api/council/**
파일 관리 infra/file useFiles.ts /api/files/**
Gemini AI infra/ai - (직접 호출) /api/gemini/generate

상세 정보:

  • 모든 인증된 API 요청은 httpOnly 쿠키 기반 (credentials: 'include')
  • 백엔드 SoT: ../it_backend/CLAUDE.md (인증 정책, 보안 설정)
  • API 응답 포맷: 표준 JSON (성공/실패 필드, 데이터, 메타데이터 포함)

9. 코딩 컨벤션

9.1 주석 및 문서화

  • 모든 코드 주석은 한국어로 작성
  • public API, service 메서드, composable 반환 함수에는 입력값과 실패 조건 기록
  • 간단한 할당/트리비얼 메서드에는 주석 불필요
  • TODO/FIXME는 후속 조치 가능한 문장으로 작성 (장기 과제는 TASK.md에 등록)

9.2 컴포넌트 및 상태

  • Composition API + <script setup lang="ts"> 필수
  • 반응형: ref(), computed(), reactive() 적절히 선택
  • Props/Emits는 명시적 타입 정의
  • v-html 사용 시 반드시 DOMPurify.sanitize() 적용

9.3 API 호출

메서드 사용처 예시
useApiFetch<T> GET 조회 const { data, pending } = useApiFetch('/api/projects')
$apiFetch POST/PUT/DELETE await $apiFetch('/api/projects', { method: 'POST', body })
$fetch 인증 API 내부 (stores/auth.ts) await $fetch('/api/auth/login', { credentials: 'include' })

9.4 UI 컴포넌트

  • PrimeVue 우선: Dialog, Dropdown, DataTable, Button 등
  • Tailwind CSS: 여백, 텍스트 정렬, 기타 레이아웃
  • 공통 컴포넌트 활용: StyledDataTable.vue, EmployeeSearchDialog.vue 등
  • components/common/ 자동 등록 시 Common 접두사 생략, 명시적 import 사용

9.5 타입 안전성

// ✓ 명시적 타입 정의
interface ProjectFormData {
  prjNm: string;
  prjDes: string;
  startDt: Date;
}

const formData = ref<ProjectFormData>({ ... });

// ✓ 함수 매개변수/반환 타입
function formatBudget(value: number): string { ... }

// ✗ 금지
const data: any = { ... };

9.6 배치 및 구조

  • 최대 파일 크기: 800줄 (초과 시 기능별로 분할)
  • 함수 최대 크기: 50줄 (초과 시 헬퍼 함수로 추출)
  • 깊은 중첩: 4 레벨 초과 금지 (early return 사용)
  • 불변성: 객체/배열 직접 수정 금지, 스프레드 연산자 사용

9.7 에러 처리

try {
  const data = await useApiFetch<Project[]>('/api/projects');
  // 처리
} catch (error: unknown) {
  const message = error instanceof Error ? error.message : 'Unknown error';
  // 로깅 및 사용자 피드백
}

9.8 참조 문서

  • 전체 규칙: ../CLAUDE.md (한글 주석, 인증 정책 등)
  • CLAUDE.md 이외 규칙: CLAUDE.md §4 (기술 스택, API 패턴, 타입 충돌 등)
  • 테스트 작성: CLAUDE.md §4.10

10. 최근 구현 이력 (2026년)

날짜 항목 비고
2026-04-29 README 최신화 인증 흐름(useCookie/httpOnly), 다크모드 쿠키, 테스트 현황, 모듈 목록, 백엔드 API 기준일 보정
2026-04-25 전체 프로젝트 문서/주석 리프레시 console.log 제거(usePdfReport, report.vue), README/CLAUDE/TASK.md 최신화
2026-04-24 대시보드 홈 카드 스타일 개선 /info/documents, /info, /approval, /admin/dashboard — Narrative·Segmented 스타일 적용
2026-04-24 대시보드 Composable 신규 구현 useApprovalDashboard, useDocumentDashboard (KPI + 월별 통계 + 배지 카운트)
2026-04-24 예산현황·검토의견 백엔드 도메인 추가 budget/status (BudgetStatusService 3탭), budget/document (Brivgm 엔티티, ReviewCommentService)
2026-04-10 전체 프로젝트 문서/주석 리프레시 Task 1~4 기반 전수 점검: 주석 보강(11개 파일), README/CLAUDE/TASK.md 최신화
2026-04-09 프로젝트 문서 및 주석 리프레시 워크플로우 기반 전체 소스 코드 주석 보강 및 문서 최신화
2026-04-05 정보화실무협의회 모듈 구현 15개 컴포넌트, CouncilController(23 엔드포인트), 8개 서비스
2026-04-04 시스템 관리자 모듈 구현 admin 레이아웃/미들웨어, 10개 관리 페이지, useAdminApi(24 CRUD)
2026-04-04 예산 작업(편성률 적용) 구현 budget/work.vue, BudgetWorkController(3 API)
2026-04-02 사전협의(문서 검토) 모듈 구현 review 스토어/composable/5개 컴포넌트, Tiptap Mark 기반 인라인 코멘트
2026-04-02 정보기술부문 계획 모듈 구현 plan CRUD(목록/상세/등록), PlanController/Service
2026-04-02 Playwright E2E 테스트 안정화 E2E 테스트 시간 초과 및 인증 문제 해결, API 모킹 개선
2026-03-30 Tiptap 수식 및 컴포넌트 고도화 LaTeX 수식 삽입, 표 정렬, 파일 첨부 컴포넌트 안정성 개선
2026-03-28 프로젝트 화면 UX 개선 Date 유효성 검사 추가 및 다크 모드 테이블 색상 렌더링 수정
2026-03-27 백엔드 domain-refactor TypeScript 동기화 백엔드 패키지 구조 domain 기반 전환에 따른 타입 정의 확인
2026-03-25 전체 프로젝트 문서화 리프레시 소스 코드 주석 전수 점검(60개 파일), README.md 최신화
2026-03-09 httpOnly 쿠키 인증 전환 stores/auth.ts, plugins/auth.ts, useApiFetch.ts 전면 개편
2026-03-08 프론트엔드 테스트 환경 구축 Vitest + Playwright 기반 테스트 환경 도입
2026-03-04 직원 검색 다이얼로그 연동 form.vue 6개 필드(주관/IT 부서·팀장·담당자)
2026-03-02 예산 통합 목록 "전체" 탭 budget/list.vue UnifiedBudgetItem 구현

11. 주요 파일 위치 및 참조

주제 파일 용도
공통 규약 ../CLAUDE.md 인증 정책(SoT), 한글 주석 원칙, 팀 규칙
프론트 가이드 ./CLAUDE.md 기술 스택, API 패턴, 타입 정의, 테스트 작성 기준
기술 부채 ../TASK.md 미구현 항목, 보안 강화 과제, 회귀 테스트 범위
설정 nuxt.config.ts Nuxt 4 설정 (ssr: false, runtimeConfig, 보안 헤더)
환경변수 .env, .env.local NUXT_PUBLIC_API_BASE (백엔드 주소)
전역 스타일 app/assets/css/main.css 다크모드, 색상 유틸리티, 커스텀 클래스
공통 컴포넌트 app/components/common/ StyledDataTable, EmployeeSearchDialog 등

12. 자주 하는 작업

신규 페이지 추가

# 1. pages/ 디렉토리에 .vue 파일 생성
# 2. Nuxt가 자동으로 라우팅 생성 (파일명 = 경로)
# 3. 필요한 composable import
# 4. 미들웨어가 필요하면 definePageMeta에 등록
definePageMeta({ middleware: 'admin' });

# 5. 테스트 파일 생성 (tests/unit/composables/ 또는 tests/e2e/)

신규 API 래퍼 (Composable) 추가

// composables/useNewFeature.ts
import { useApiFetch } from './useApiFetch';

export const useNewFeature = () => {
  const { data, pending } = useApiFetch<NewFeatureDto>('/api/new-feature');
  
  const create = async (payload) => {
    const { $apiFetch } = useNuxtApp();
    return await $apiFetch('/api/new-feature', { 
      method: 'POST', 
      body: payload 
    });
  };
  
  return { data, pending, create };
};

새 Pinia 스토어 추가

// stores/newStore.ts
import { defineStore } from 'pinia';

export const useNewStore = defineStore('newStore', () => {
  const state = ref<State>(initialState);
  
  const action = () => {
    // 로직
  };
  
  return { state, action };
});

컴포넌트 테스트 작성

// tests/unit/components/MyComponent.test.ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import MyComponent from '~/components/MyComponent.vue';

describe('MyComponent', () => {
  it('renders props correctly', () => {
    const wrapper = mount(MyComponent, { props: { title: 'Test' } });
    expect(wrapper.text()).toContain('Test');
  });
});

13. 문제 해결

TypeScript 타입 오류

# 1. .nuxt/ 디렉토리 재생성
npm run postinstall

# 2. IDE 재시작

# 3. 여전히 오류 시
rm -rf node_modules/.nuxt
npm run postinstall

API 401 Unauthorized

# 1. 백엔드 서버 실행 여부 확인
curl http://localhost:8080/api/auth/me

# 2. 로그인 재시도
# 브라우저 개발자 도구 > Application > Cookies 확인

# 3. 환경변수 확인
echo $NUXT_PUBLIC_API_BASE  # 기본값: http://localhost:8080

빌드 메모리 초과

# 빌드 메모리 증가
NODE_OPTIONS=--max-old-space-size=8192 npm run build

# 또는 package.json에 설정되어 있음
npm run build  # cross-env NODE_OPTIONS 자동 적용

E2E 테스트 timeout

# timeout 값 확가 (tests/e2e/)
await page.waitForLoadState('networkidle');
await page.waitForURL('**/projects');

# 또는 명시적 대기
await page.waitForSelector('table');

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors