# 14. WTM Frontend 레이아웃 표준 및 디자인 시스템 > 작성일: 2026-03-25 > 목적: 하드코딩 최소화, 반응형 웹 대응, 개발자 간 일관된 UI 생산 --- ## 1. 디자인 토큰 (하드코딩 제거) 모든 수치/색상은 CSS 변수 + TS 상수로 관리. 컴포넌트에 직접 px, #색상 금지. ### 1.1 _variables.scss ```scss // src/assets/styles/_variables.scss // ===== Breakpoints ===== $bp-mobile: 576px; $bp-tablet: 768px; $bp-desktop: 992px; $bp-wide: 1200px; $bp-ultra: 1400px; // ===== Layout Dimensions ===== $sidebar-width: 260px; $sidebar-collapsed-width: 64px; $topbar-height: 56px; $page-padding-x: 1.5rem; $page-padding-y: 1.25rem; // ===== Spacing Scale (8px base) ===== $space-xs: 0.25rem; // 4px $space-sm: 0.5rem; // 8px $space-md: 1rem; // 16px $space-lg: 1.5rem; // 24px $space-xl: 2rem; // 32px $space-2xl: 3rem; // 48px // ===== Typography Scale ===== $font-size-xs: 0.75rem; // 12px — 보조 텍스트 $font-size-sm: 0.875rem; // 14px — 본문 (DataTable) $font-size-base: 1rem; // 16px — 기본 $font-size-lg: 1.125rem; // 18px — 소제목 $font-size-xl: 1.25rem; // 20px — 제목 $font-size-2xl: 1.5rem; // 24px — 페이지 타이틀 // ===== Border Radius ===== $radius-sm: 6px; $radius-md: 8px; $radius-lg: 12px; $radius-full: 9999px; // ===== Z-Index Scale ===== $z-sidebar: 100; $z-topbar: 110; $z-overlay: 200; $z-dialog: 300; $z-toast: 400; // ===== Semantic Colors (PrimeVue 테마 토큰 참조) ===== $color-surface: var(--p-surface-0); $color-surface-card: var(--p-surface-0); $color-surface-hover: var(--p-surface-100); $color-border: var(--p-surface-200); $color-text: var(--p-text-color); $color-text-muted: var(--p-text-muted-color); $color-primary: var(--p-primary-color); $color-danger: var(--p-red-500); $color-success: var(--p-green-500); $color-warning: var(--p-yellow-500); ``` ### 1.2 app.constants.ts ```typescript // src/core/constants/app.constants.ts // Breakpoints (JS에서 사용 — useBreakpoints 등) export const BREAKPOINTS = { mobile: 576, tablet: 768, desktop: 992, wide: 1200, ultra: 1400, } as const; // Layout export const LAYOUT = { sidebarWidth: 260, sidebarCollapsedWidth: 64, topbarHeight: 56, } as const; // Pagination export const PAGINATION = { defaultPageSize: 20, pageSizeOptions: [10, 20, 50, 100], } as const; // Toast export const TOAST = { defaultLife: 3000, errorLife: 5000, } as const; // Date formats export const DATE_FORMAT = { display: 'YYYY-MM-DD', api: 'YYYY-MM-DD', datetime: 'YYYY-MM-DD HH:mm', weekStart: 1, // Monday } as const; // Timesheet 규칙 export const TIMESHEET_RULES = { maxDailyHours: 24, warnDailyHours: 10, defaultDailyHours: 8, maxWeeklyHours: 52, } as const; // 역할 코드 export const ROLES = { SA: 'SA', PM: 'PM', PCM: 'PCM', PTK: 'PTK', DL: 'DL', USER: 'USER', } as const; // 시수 상태 export const TIMESHEET_STATUS = { DRAFT: { label: '작성중', severity: 'secondary' }, SUBMITTED: { label: '제출됨', severity: 'info' }, DL_APPROVED: { label: 'DL승인', severity: 'warn' }, APPROVED: { label: '승인', severity: 'success' }, REJECTED: { label: '반려', severity: 'danger' }, } as const; // 프로젝트 상태 export const PROJECT_STATUS = { ACTIVE: { label: '진행중', severity: 'success' }, CLOSED: { label: '종료', severity: 'secondary' }, HOLD: { label: '보류', severity: 'warn' }, } as const; // 시수 입력 유형 export const ENTRY_TYPES = { NON_PROJECT: { label: 'Non-Project', icon: 'pi pi-calendar' }, OTHER_PROJECT: { label: 'Other Project', icon: 'pi pi-briefcase' }, EPC: { label: 'EPC Project', icon: 'pi pi-building' }, } as const; // Non-Project 카테고리 export const NP_CATEGORIES = [ { value: 'ANNUAL_LEAVE', label: '연차' }, { value: 'SICK_LEAVE', label: '병가' }, { value: 'TRAINING', label: '교육' }, { value: 'ADMIN', label: '행정' }, { value: 'PUBLIC_HOLIDAY', label: '공휴일' }, { value: 'OTHER', label: '기타' }, ] as const; // 사이드바 메뉴 정의 export const MENU_ITEMS = [ { label: '대시보드', icon: 'pi pi-home', to: '/dashboard', roles: ['SA', 'PM', 'PCM', 'PTK', 'DL', 'USER'], }, { label: '시수 관리', icon: 'pi pi-clock', roles: ['SA', 'PM', 'DL', 'USER'], items: [ { label: '시수 입력', to: '/timesheets', roles: ['USER', 'DL', 'PM', 'SA'] }, { label: '시수 이력', to: '/timesheets/history', roles: ['USER', 'DL', 'PM', 'SA'] }, { label: 'Excel 업로드', to: '/timesheets/upload', roles: ['USER'] }, ], }, { label: '결재', icon: 'pi pi-check-square', roles: ['DL', 'PM', 'SA'], items: [ { label: '결재 대기', to: '/approvals', roles: ['DL', 'PM', 'SA'] }, { label: '결재 이력', to: '/approvals/history', roles: ['DL', 'PM', 'SA'] }, ], }, { label: '프로젝트', icon: 'pi pi-briefcase', roles: ['SA', 'PM', 'PCM'], items: [ { label: '프로젝트 목록', to: '/projects', roles: ['SA', 'PM', 'PCM'] }, { label: 'WBS 관리', to: '/wbs', roles: ['SA', 'PM', 'PCM'] }, { label: 'TEAL 관리', to: '/teal', roles: ['SA', 'PM', 'PCM'] }, ], }, { label: '리포트', icon: 'pi pi-chart-bar', to: '/reports', roles: ['SA', 'PM', 'PCM', 'DL'], }, { label: '사용자 관리', icon: 'pi pi-users', to: '/users', roles: ['SA', 'PTK'], }, { label: '시스템 설정', icon: 'pi pi-cog', to: '/settings', roles: ['SA'], }, ] as const; ``` --- ## 2. 반응형 레이아웃 시스템 ### 2.1 Breakpoint 전략 ``` ┌─────────────────────────────────────────────────────────┐ │ < 576px Mobile 사이드바 숨김, 햄버거 메뉴 │ │ 576~767px Mobile+ 사이드바 오버레이 │ │ 768~991px Tablet 사이드바 접힘 (아이콘만) │ │ 992~1199px Desktop 사이드바 펼침 │ │ ≥ 1200px Wide 사이드바 펼침 + 여유 공간 │ └─────────────────────────────────────────────────────────┘ ``` ### 2.2 AppLayout.vue ```vue ``` ### 2.3 AppSidebar.vue ```vue ``` ### 2.4 AppTopbar.vue ```vue {{ authStore.currentUser?.fullName }} ``` --- ## 3. 페이지 표준 레이아웃 ### 3.1 BasePageHeader.vue 모든 페이지 상단에 사용되는 제목 + 액션 바. ```vue {{ title }} {{ subtitle }} ``` ### 3.2 BaseCrudTable.vue CRUD 테이블의 공통 셸. 모든 목록 화면이 이것을 사용. ```vue emit('row-select', e.data)" @page="(e: any) => emit('page', e)" > {{ emptyMessage }} ``` ### 3.3 BaseFormDialog.vue 모든 생성/수정 다이얼로그의 공통 셸. ```vue ``` --- ## 4. 반응형 폼 그리드 ### 4.1 FormGrid 유틸리티 클래스 ```scss // src/assets/styles/_form-grid.scss @use 'variables' as *; // 폼 필드 컨테이너 — CSS Grid 기반 .form-grid { display: grid; gap: $space-md; grid-template-columns: repeat(12, 1fr); // 기본: 1열 @media (max-width: $bp-mobile) { grid-template-columns: 1fr; } } // 컬럼 span 클래스 .col-1 { grid-column: span 1; } .col-2 { grid-column: span 2; } .col-3 { grid-column: span 3; } .col-4 { grid-column: span 4; } .col-6 { grid-column: span 6; } .col-8 { grid-column: span 8; } .col-12 { grid-column: span 12; } // 모바일에서는 전부 full-width @media (max-width: $bp-mobile) { [class^="col-"] { grid-column: span 1 !important; } } // 폼 필드 단위 .form-field { display: flex; flex-direction: column; gap: $space-xs; &__label { font-size: $font-size-sm; font-weight: 600; color: $color-text; &--required::after { content: ' *'; color: $color-danger; } } &__error { font-size: $font-size-xs; color: $color-danger; } &__hint { font-size: $font-size-xs; color: $color-text-muted; } } ``` ### 4.2 사용 예시: UserFormDialog ```vue 이름 {{ errors.fullName }} 이메일 사번 부서 Discipline 역할 ``` 반응형 동작: - **Desktop (≥992px)**: 12열 그리드 → 이름(6) + 이메일(6) 한 줄, 사번(4) + 부서(4) + Discipline(4) 한 줄 - **Mobile (<576px)**: 모두 1열로 스택 --- ## 5. 반응형 DataTable 전략 ```vue ``` ```scss // DataTable 반응형 헬퍼 @media (max-width: $bp-tablet) { .hidden-mobile { display: none !important; } } ``` --- ## 6. 대시보드 카드 그리드 ```vue ``` --- ## 7. main.scss 통합 ```scss // src/assets/styles/main.scss // 1. 변수 (최상위) @use 'variables'; // 2. 폼 그리드 유틸리티 @use 'form-grid'; // 3. PrimeVue 테마 오버라이드 @use 'overrides'; // 4. 글로벌 리셋/베이스 *, *::before, *::after { box-sizing: border-box; } html { font-size: 16px; -webkit-font-smoothing: antialiased; } body { margin: 0; font-family: var(--p-font-family); color: var(--p-text-color); background: var(--p-surface-ground); } // 5. 유틸리티 클래스 (최소한) .text-center { text-align: center; } .text-right { text-align: right; } .text-muted { color: var(--p-text-muted-color); } .text-sm { font-size: variables.$font-size-sm; } .text-xs { font-size: variables.$font-size-xs; } .mt-0 { margin-top: 0; } .mb-0 { margin-bottom: 0; } // 6. 카드 표준 (페이지 내 섹션) .card { background: var(--p-surface-0); border: 1px solid var(--p-surface-200); border-radius: variables.$radius-md; padding: variables.$space-lg; @media (max-width: variables.$bp-mobile) { padding: variables.$space-md; border-radius: variables.$radius-sm; } } ``` --- ## 8. 표준 페이지 작성 템플릿 모든 목록 페이지는 아래 구조를 따릅니다: ```vue <{Module}FormDialog v-model:visible="showDialog" :item="selected" @saved="onSaved" /> ``` --- ## 9. 요약: 하드코딩 제거 체크리스트 | 하드코딩 대상 | 표준 출처 | 사용 방법 | |-------------|----------|----------| | 색상 (#hex) | PrimeVue CSS 변수 `var(--p-*)` | SCSS `$color-*` 변수 경유 | | px 수치 | `_variables.scss` `$space-*` | 직접 px 금지 | | 브레이크포인트 | `BREAKPOINTS` 상수 + `$bp-*` | `useBreakpoints()` 또는 SCSS `@media` | | 사이드바/탑바 크기 | `LAYOUT` 상수 + `$sidebar-*` | AppLayout에서 집중 관리 | | 페이지 크기 | `PAGINATION` 상수 | BaseCrudTable 기본값 | | 토스트 시간 | `TOAST` 상수 | useAppToast 래퍼 | | 날짜 포맷 | `DATE_FORMAT` 상수 | date.utils.ts | | 상태 레이블/색상 | `*_STATUS` 상수 | Tag severity 자동 매핑 | | 역할 코드 | `ROLES` 상수 | auth.guard, usePermission | | 메뉴 구조 | `MENU_ITEMS` 상수 | AppSidebar (역할 필터링 자동) | | 시수 규칙 수치 | `TIMESHEET_RULES` 상수 | TimesheetGrid 검증 | | API base URL | `.env` `VITE_API_BASE_URL` | axios.ts |
{{ subtitle }}
{{ emptyMessage }}