diff --git a/docs/ADMIN_PAGE_STANDARD.md b/docs/ADMIN_PAGE_STANDARD.md new file mode 100644 index 0000000..c44239e --- /dev/null +++ b/docs/ADMIN_PAGE_STANDARD.md @@ -0,0 +1,588 @@ +# 관리 페이지 표준 가이드 (Admin Page Standard) + +> **작성일**: 2026-01-26 +> **버전**: 1.0.0 +> **용도**: 관리자 페이지(Admin Pages) 통합 개발 표준 + +--- + +## 📋 목차 + +1. [용어 정의](#1-용어-정의) +2. [페이지 유형 분류](#2-페이지-유형-분류) +3. [표준 레이아웃 구조](#3-표준-레이아웃-구조) +4. [HTML 구조 템플릿](#4-html-구조-템플릿) +5. [CSS 클래스 규칙](#5-css-클래스-규칙) +6. [JavaScript 규칙](#6-javascript-규칙) +7. [파일 네이밍 규칙](#7-파일-네이밍-규칙) +8. [체크리스트](#8-체크리스트) + +--- + +## 1. 용어 정의 + +### 1.1 관리 페이지 (Admin Page) + +**정의**: 데이터의 CRUD(Create, Read, Update, Delete) 작업을 수행하는 관리자 전용 페이지 + +**특징**: +- 2단 레이아웃 (사이드바 + 메인 콘텐츠) +- 카드/그리드 방식의 데이터 표시 +- 검색, 필터, 통계 기능 포함 +- 모달을 통한 추가/수정 기능 + +**명명 규칙**: `{엔티티}-management` 또는 `{엔티티}s.html` + +**예시**: +- ✅ 프로젝트 관리 (`projects.html`) +- ✅ 작업자 관리 (`workers.html`) +- ✅ 코드 관리 (`codes.html`) + +### 1.2 관리 페이지의 구성 요소 + +| 용어 | 영문 | 설명 | 클래스명 | +|------|------|------|----------| +| 사이드바 | Sidebar | 좌측 네비게이션 메뉴 | `.sidebar` | +| 메인 콘텐츠 | Main Content | 우측 콘텐츠 영역 | `.main-content` | +| 페이지 헤더 | Page Header | 타이틀 + 액션 버튼 | `.page-header` | +| 검색 섹션 | Search Section | 검색바 + 필터 | `.search-section` | +| 통계 카드 | Statistics | 데이터 통계 표시 | `.project-stats`, `.stat-item` | +| 데이터 그리드 | Data Grid | 카드/테이블 그리드 | `.projects-grid` | +| 데이터 카드 | Data Card | 개별 데이터 표시 카드 | `.project-card` | + +--- + +## 2. 페이지 유형 분류 + +### 2.1 Type A: 카드 그리드 관리 페이지 + +**용도**: 시각적으로 정보를 표시하고 관리하는 페이지 + +**레이아웃**: +``` +┌─────────┬────────────────────────────────┐ +│ │ 페이지 헤더 (타이틀 + 버튼) │ +│ ├────────────────────────────────┤ +│ 사이드바 │ 검색 섹션 (검색바 + 필터) │ +│ ├────────────────────────────────┤ +│ │ 통계 (활성/비활성/총) │ +│ ├────────────────────────────────┤ +│ │ [카드1] [카드2] [카드3] │ +│ │ [카드4] [카드5] [카드6] │ +│ │ 데이터 카드 그리드 │ +└─────────┴────────────────────────────────┘ +``` + +**적용 페이지**: +- 프로젝트 관리 (`projects.html`) +- 작업자 관리 (`workers.html`) + +**그리드 설정**: +- 기본: 3열 (`grid-template-columns: repeat(3, 1fr)`) +- 카드 높이: 고정 420px + +### 2.2 Type B: 테이블 관리 페이지 + +**용도**: 텍스트 중심의 데이터를 테이블 형태로 관리 + +**레이아웃**: +``` +┌─────────┬────────────────────────────────┐ +│ │ 페이지 헤더 (타이틀 + 버튼) │ +│ ├────────────────────────────────┤ +│ 사이드바 │ 검색 섹션 (검색바 + 필터) │ +│ ├────────────────────────────────┤ +│ │ ┌──────────────────────────┐ │ +│ │ │ 테이블 헤더 │ │ +│ │ ├──────────────────────────┤ │ +│ │ │ 데이터 행 1 │ │ +│ │ │ 데이터 행 2 │ │ +│ │ │ 데이터 행 3 │ │ +│ │ └──────────────────────────┘ │ +└─────────┴────────────────────────────────┘ +``` + +**적용 가능 페이지**: +- 로그 관리 +- 권한 관리 + +### 2.3 Type C: 탭 기반 관리 페이지 + +**용도**: 여러 카테고리의 데이터를 탭으로 구분하여 관리 + +**레이아웃**: +``` +┌─────────┬────────────────────────────────┐ +│ │ 페이지 헤더 (타이틀 + 버튼) │ +│ ├────────────────────────────────┤ +│ 사이드바 │ [탭1] [탭2] [탭3] [탭4] │ +│ ├────────────────────────────────┤ +│ │ 검색 섹션 │ +│ ├────────────────────────────────┤ +│ │ 현재 탭의 콘텐츠 표시 │ +│ │ (테이블 또는 카드) │ +└─────────┴────────────────────────────────┘ +``` + +**적용 페이지**: +- 코드 관리 (`codes.html`) + +--- + +## 3. 표준 레이아웃 구조 + +### 3.1 2단 레이아웃 (Sidebar + Main) + +**CSS Flexbox 구조**: +```css +.page-container { + display: flex; /* 가로 배치 */ + min-height: calc(100vh - 80px); +} + +.sidebar { + width: 240px; /* 고정 너비 */ + min-width: 240px; + flex-shrink: 0; /* 줄어들지 않음 */ +} + +.main-content { + flex: 1; /* 나머지 공간 차지 */ + padding: 2rem; +} +``` + +### 3.2 반응형 Breakpoints + +| 화면 크기 | 사이드바 | 그리드 열 | 비고 | +|----------|---------|----------|------| +| 1400px+ | 240px | 3열 | 대형 데스크탑 | +| 1200px ~ 1399px | 240px | 3열 | 일반 데스크탑 | +| 1024px ~ 1199px | 240px | 2열 | 중형 화면 | +| 768px ~ 1023px | 200px | 2열 | 태블릿 | +| 767px 이하 | 숨김 | 1열 | 모바일 | + +--- + +## 4. HTML 구조 템플릿 + +### 4.1 기본 HTML 템플릿 + +```html + + + + + + {엔티티명} 관리 | (주)테크니컬코리아 + + + + + + + + + + +
+ + + + +
+
+ + + + +
+ + +
+ + +
+
+

등록된 {엔티티}

+
+ +
+
+ + +
+ +
+
+
+
+
+ + + + + + + + + + +``` + +### 4.2 사이드바 메뉴 구조 + +```html + +``` + +--- + +## 5. CSS 클래스 규칙 + +### 5.1 필수 클래스 목록 + +| 클래스명 | 용도 | 부모 | 필수 여부 | +|---------|------|------|----------| +| `.page-container` | 전체 레이아웃 컨테이너 | `` | ✅ 필수 | +| `.sidebar` | 사이드바 | `.page-container` | ✅ 필수 | +| `.main-content` | 메인 콘텐츠 영역 | `.page-container` | ✅ 필수 | +| `.dashboard-main` | 대시보드 메인 | `.main-content` | ✅ 필수 | +| `.page-header` | 페이지 헤더 | `.dashboard-main` | ✅ 필수 | +| `.search-section` | 검색 영역 | `.dashboard-main` | ✅ 필수 | +| `.projects-section` | 데이터 섹션 | `.dashboard-main` | ✅ 필수 | +| `.projects-grid` | 데이터 그리드 | `.projects-section` | ✅ 필수 | +| `.project-card` | 개별 카드 | `.projects-grid` | ✅ 필수 | + +### 5.2 클래스 네이밍 규칙 + +**패턴**: `{영역}-{요소}-{수식어}` + +**예시**: +- ✅ `.page-header` (페이지 헤더) +- ✅ `.search-section` (검색 섹션) +- ✅ `.stat-item` (통계 항목) +- ✅ `.menu-item` (메뉴 항목) +- ✅ `.btn-primary` (주요 버튼) + +**금지**: +- ❌ `.header1`, `.section2` (숫자 사용) +- ❌ `.pageHeader` (camelCase) +- ❌ `.Page_Header` (underscore + PascalCase) + +### 5.3 카드 표준 스타일 + +```css +.project-card { + background: #ffffff; + border: 1px solid #e2e8f0; + border-radius: 0.75rem; + padding: 1.5rem; + height: 420px; /* 고정 높이 */ + min-height: 420px; + max-height: 420px; + display: flex; + flex-direction: column; + cursor: pointer; + transition: all 0.2s ease; +} + +.project-card:hover { + border-color: #2563eb; + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); + transform: translateY(-4px); +} +``` + +--- + +## 6. JavaScript 규칙 + +### 6.1 파일 구조 + +각 관리 페이지는 독립적인 JavaScript 파일을 가져야 합니다. + +**파일명 패턴**: `{entity}-management.js` + +**예시**: +- `/js/project-management.js` +- `/js/worker-management.js` +- `/js/code-management.js` + +### 6.2 필수 함수 목록 + +모든 관리 페이지는 다음 함수를 구현해야 합니다: + +```javascript +// 1. 페이지 초기화 +function initializePage() { } + +// 2. 데이터 로드 +async function load{Entities}() { } + +// 3. 데이터 렌더링 +function render{Entities}() { } + +// 4. 검색 +function search{Entities}() { } + +// 5. 필터 +function filter{Entities}() { } + +// 6. 모달 열기 +function open{Entity}Modal() { } + +// 7. 모달 닫기 +function close{Entity}Modal() { } + +// 8. 데이터 저장 +async function save{Entity}() { } + +// 9. 데이터 삭제 +async function delete{Entity}(id) { } + +// 10. 목록 새로고침 +function refresh{Entity}List() { } +``` + +### 6.3 네이밍 규칙 + +**함수명**: camelCase +- ✅ `loadProjects()`, `openWorkerModal()` +- ❌ `load_projects()`, `OpenWorkerModal()` + +**변수명**: camelCase +- ✅ `allProjects`, `currentEditingProject` +- ❌ `all_projects`, `CurrentEditingProject` + +**상수**: UPPER_SNAKE_CASE +- ✅ `API_BASE_URL`, `DEFAULT_PAGE_SIZE` +- ❌ `apiBaseUrl`, `defaultPageSize` + +--- + +## 7. 파일 네이밍 규칙 + +### 7.1 디렉터리 구조 + +``` +/pages/admin/ + ├── index.html # 관리 대시보드 + ├── projects.html # 프로젝트 관리 + ├── workers.html # 작업자 관리 + ├── codes.html # 코드 관리 + └── {entity}s.html # 새 관리 페이지 + +/css/ + ├── design-system.css # 디자인 시스템 + └── admin-pages.css # 관리 페이지 공통 CSS + +/js/ + ├── project-management.js + ├── worker-management.js + ├── code-management.js + └── {entity}-management.js +``` + +### 7.2 파일명 규칙 + +**HTML 파일**: +- 복수형 사용: `{entities}.html` +- ✅ `projects.html`, `workers.html` +- ❌ `project.html`, `worker.html` + +**JavaScript 파일**: +- 하이픈 구분: `{entity}-management.js` +- ✅ `project-management.js` +- ❌ `projectManagement.js`, `project_management.js` + +**CSS 파일**: +- 공통: `admin-pages.css` (모든 관리 페이지가 공유) +- 개별: 필요시 `{entity}-page.css` + +--- + +## 8. 체크리스트 + +새로운 관리 페이지를 생성할 때 다음 체크리스트를 확인하세요: + +### 8.1 HTML 체크리스트 + +- [ ] 2단 레이아웃 구조 (사이드바 + 메인) +- [ ] 사이드바 메뉴에 현재 페이지 active 표시 +- [ ] 페이지 헤더 (타이틀 + 설명 + 액션 버튼) +- [ ] 검색 섹션 (검색바 + 필터) +- [ ] 통계 섹션 (활성/비활성/총) +- [ ] 데이터 그리드 (3열 기본) +- [ ] 모달 (추가/수정/삭제) +- [ ] CSS 버전 쿼리스트링 (`?v=6`) +- [ ] JavaScript 모듈 로드 + +### 8.2 CSS 체크리스트 + +- [ ] `.page-container { display: flex }` 적용 +- [ ] `.sidebar` 고정 너비 (240px) +- [ ] `.main-content { flex: 1 }` 적용 +- [ ] `.projects-grid` 3열 그리드 +- [ ] 카드 고정 높이 (420px) +- [ ] 반응형 breakpoints 구현 +- [ ] 호버 효과 구현 + +### 8.3 JavaScript 체크리스트 + +- [ ] 10개 필수 함수 구현 +- [ ] API 연동 (GET, POST, PUT, DELETE) +- [ ] 에러 핸들링 +- [ ] 로딩 상태 표시 +- [ ] 검색/필터 기능 +- [ ] 모달 열기/닫기 +- [ ] 데이터 유효성 검사 + +### 8.4 접근성 체크리스트 + +- [ ] 키보드 네비게이션 지원 +- [ ] 버튼에 적절한 `title` 속성 +- [ ] 로딩 중 상태 표시 +- [ ] 에러 메시지 명확히 표시 +- [ ] 모바일 반응형 지원 + +--- + +## 9. 예시 코드 + +### 9.1 카드 렌더링 (JavaScript) + +```javascript +function renderProjects() { + const projectsHtml = filteredProjects.map(project => { + const isInactive = project.is_active === 0 || project.is_active === false; + + return ` +
+
+
+
${project.job_no || 'Job No. 없음'}
+

${project.project_name}

+
+
+ 상태 + ${project.status} +
+
+ PM + ${project.pm || '-'} +
+ +
+
+
+ + +
+
+
+ `; + }).join(''); + + document.getElementById('projectsGrid').innerHTML = projectsHtml; +} +``` + +--- + +## 10. 버전 히스토리 + +| 버전 | 날짜 | 변경 내용 | +|------|------|----------| +| 1.0.0 | 2026-01-26 | 초기 버전 작성 | + +--- + +## 11. 참고 자료 + +- [DOCUMENTATION_STANDARD.md](./DOCUMENTATION_STANDARD.md) - 문서 작성 표준 +- [TBM_DEPLOYMENT_GUIDE.md](./TBM_DEPLOYMENT_GUIDE.md) - TBM 배포 가이드 +- `/css/admin-pages.css` - 관리 페이지 공통 CSS +- `/pages/admin/projects.html` - 프로젝트 관리 참고 구현 + +--- + +**문서 작성자**: Claude Code +**최종 수정일**: 2026-01-26 diff --git a/web-ui/css/admin-pages.css b/web-ui/css/admin-pages.css new file mode 100644 index 0000000..8808247 --- /dev/null +++ b/web-ui/css/admin-pages.css @@ -0,0 +1,1536 @@ +/** + * 관리자 페이지 공통 스타일 + * 사무적이고 전문적인 디자인 시스템 + * + * 적용 페이지: + * - projects.html (프로젝트 관리) + * - workers.html (작업자 관리) + * - codes.html (코드 관리) + */ + +/* ============================================ + 1. 전역 변수 및 기본 설정 + ============================================ */ +:root { + /* 색상 시스템 - 사무적 느낌의 중성 색상 */ + --color-primary: #2563eb; + --color-primary-dark: #1e40af; + --color-primary-light: #dbeafe; + + --color-secondary: #64748b; + --color-secondary-dark: #475569; + --color-secondary-light: #f1f5f9; + + --color-success: #10b981; + --color-warning: #f59e0b; + --color-error: #ef4444; + --color-info: #3b82f6; + + /* 배경 색상 */ + --bg-body: #f8fafc; + --bg-card: #ffffff; + --bg-hover: #f1f5f9; + + /* 텍스트 색상 */ + --text-primary: #0f172a; + --text-secondary: #64748b; + --text-muted: #94a3b8; + + /* 테두리 색상 */ + --border-color: #e2e8f0; + --border-focus: #2563eb; + + /* 그림자 */ + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); + + /* 간격 */ + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 1rem; + --space-lg: 1.5rem; + --space-xl: 2rem; + --space-2xl: 3rem; + + /* 폰트 크기 */ + --font-xs: 0.75rem; + --font-sm: 0.875rem; + --font-base: 1rem; + --font-lg: 1.125rem; + --font-xl: 1.25rem; + --font-2xl: 1.5rem; + --font-3xl: 1.875rem; + + /* Border Radius */ + --radius-sm: 0.25rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; +} + +/* ============================================ + 2. 레이아웃 구조 (2단 레이아웃: 사이드바 + 메인) + ============================================ */ +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans KR', Roboto, sans-serif; + background: var(--bg-body); + color: var(--text-primary); + line-height: 1.6; + margin: 0; + padding: 0; +} + +/* 페이지 컨테이너: 사이드바 + 메인 콘텐츠 */ +.page-container { + display: flex; + min-height: calc(100vh - 80px); /* 네비바 높이 제외 */ + background: var(--bg-body); +} + +/* 사이드바 */ +.sidebar { + width: 240px; + min-width: 240px; + flex-shrink: 0; + background: #ffffff; + border-right: 1px solid var(--border-color); + box-shadow: 2px 0 4px rgba(0, 0, 0, 0.05); + overflow-y: auto; +} + +.sidebar-nav { + padding: var(--space-lg) 0; +} + +.sidebar-header { + padding: 0 var(--space-lg) var(--space-md); + border-bottom: 1px solid var(--border-color); + margin-bottom: var(--space-md); +} + +.sidebar-title { + font-size: var(--font-base); + font-weight: 700; + color: var(--text-primary); + margin: 0; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.sidebar-menu { + list-style: none; + padding: 0; + margin: 0; +} + +.menu-item a { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: var(--space-sm) var(--space-lg); + color: var(--text-secondary); + text-decoration: none; + transition: all 0.2s ease; + white-space: nowrap; +} + +.menu-item a:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.menu-item.active a { + background: var(--color-primary-light); + color: var(--color-primary); + font-weight: 600; + border-right: 3px solid var(--color-primary); +} + +.menu-icon { + font-size: var(--font-lg); + flex-shrink: 0; +} + +.menu-text { + font-size: var(--font-sm); +} + +.menu-divider { + height: 1px; + background: var(--border-color); + margin: var(--space-md) var(--space-lg); +} + +/* 메인 콘텐츠 영역 */ +.main-content { + flex: 1; + padding: var(--space-xl); + overflow-x: hidden; + max-width: 100%; + box-sizing: border-box; +} + +.dashboard-main { + width: 100%; + max-width: 1400px; + margin: 0 auto; +} + +/* 뒤로가기 버튼 - 사이드바로 대체되어 숨김 */ +.back-button { + display: none; +} + +/* ============================================ + 3. 페이지 헤더 + ============================================ */ +.page-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: var(--space-xl); + padding: var(--space-lg); + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-sm); +} + +.page-title-section { + flex: 1; +} + +.page-title { + display: flex; + align-items: center; + gap: var(--space-md); + font-size: var(--font-3xl); + font-weight: 700; + color: var(--text-primary); + margin: 0 0 var(--space-sm) 0; + white-space: nowrap; /* 텍스트 줄바꿈 방지 */ +} + +.title-icon { + font-size: var(--font-2xl); + filter: grayscale(30%); +} + +.page-description { + font-size: var(--font-sm); + color: var(--text-secondary); + margin: 0; + line-height: 1.5; + max-width: 100%; + word-wrap: break-word; +} + +.page-actions { + display: flex; + gap: var(--space-sm); + flex-shrink: 0; +} + +/* ============================================ + 4. 버튼 스타일 + ============================================ */ +.btn { + display: inline-flex; + align-items: center; + gap: var(--space-sm); + padding: 0.625rem 1.25rem; + font-size: var(--font-sm); + font-weight: 600; + border: none; + border-radius: var(--radius-md); + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; +} + +.btn-primary { + background: var(--color-primary); + color: white; + box-shadow: var(--shadow-sm); +} + +.btn-primary:hover { + background: var(--color-primary-dark); + box-shadow: var(--shadow-md); + transform: translateY(-1px); +} + +.btn-secondary { + background: var(--bg-card); + color: var(--text-secondary); + border: 1px solid var(--border-color); +} + +.btn-secondary:hover { + background: var(--bg-hover); + color: var(--text-primary); + border-color: var(--color-secondary); +} + +.btn-icon { + font-size: var(--font-base); + filter: grayscale(20%); +} + +/* ============================================ + 5. 검색 섹션 + ============================================ */ +.search-section { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: var(--space-lg); + margin-bottom: var(--space-xl); + box-shadow: var(--shadow-sm); +} + +.search-bar { + display: flex; + gap: var(--space-md); + margin-bottom: var(--space-md); +} + +.search-input { + flex: 1; + padding: 0.625rem 1rem; + font-size: var(--font-sm); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + background: var(--bg-body); + transition: all 0.2s ease; +} + +.search-input:focus { + outline: none; + border-color: var(--border-focus); + background: white; + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); +} + +.search-btn { + padding: 0.625rem 1.25rem; + background: var(--color-primary); + color: white; + border: none; + border-radius: var(--radius-md); + font-size: var(--font-sm); + font-weight: 600; + cursor: pointer; + white-space: nowrap; + transition: all 0.2s ease; +} + +.search-btn:hover { + background: var(--color-primary-dark); + transform: translateY(-1px); +} + +/* 필터 영역 */ +.filter-group { + display: flex; + flex-wrap: wrap; + gap: var(--space-md); + align-items: center; +} + +.filter-label { + font-size: var(--font-sm); + font-weight: 600; + color: var(--text-secondary); + margin-right: var(--space-sm); +} + +.filter-btn { + padding: 0.5rem 1rem; + font-size: var(--font-sm); + background: var(--bg-body); + color: var(--text-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + cursor: pointer; + transition: all 0.2s ease; +} + +.filter-btn:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.filter-btn.active { + background: var(--color-primary); + color: white; + border-color: var(--color-primary); +} + +/* ============================================ + 6. 프로젝트 섹션 + ============================================ */ +.projects-section { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: var(--space-lg); + box-shadow: var(--shadow-sm); + width: 100%; + max-width: 100%; + overflow: hidden; + box-sizing: border-box; +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: var(--space-md); + margin-bottom: var(--space-lg); + padding-bottom: var(--space-md); + border-bottom: 1px solid var(--border-color); +} + +.section-title { + font-size: var(--font-xl); + font-weight: 700; + color: var(--text-primary); + margin: 0; +} + +/* 통계 카드 */ +.project-stats { + display: flex; + gap: var(--space-sm); + flex-wrap: wrap; +} + +.stat-item { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: 0.625rem 1rem; + background: var(--bg-card); + border: 2px solid var(--border-color); + border-radius: var(--radius-md); + font-size: var(--font-sm); + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; +} + +.stat-item:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.stat-item.active { + border-width: 2px; + box-shadow: var(--shadow-md); + transform: scale(1.02); +} + +.stat-icon { + font-size: var(--font-lg); +} + +/* 상태별 통계 색상 */ +.active-stat { + background: rgba(16, 185, 129, 0.05); + color: var(--color-success); + border-color: var(--color-success); +} + +.inactive-stat { + background: rgba(239, 68, 68, 0.05); + color: var(--color-error); + border-color: var(--color-error); +} + +.total-stat { + background: rgba(37, 99, 235, 0.05); + color: var(--color-primary); + border-color: var(--color-primary); +} + +/* ============================================ + 7. 그리드 레이아웃 (중앙 정렬) + ============================================ */ +.projects-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--space-lg); + width: 100%; + max-width: 100%; +} + +/* ============================================ + 8. 카드 스타일 + ============================================ */ +.project-card, +.worker-card { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: var(--space-lg); + transition: all 0.2s ease; + cursor: pointer; + position: relative; + display: flex; + flex-direction: column; + height: 420px; + min-height: 420px; + max-height: 420px; + max-width: 100%; + overflow: hidden; + box-sizing: border-box; + word-wrap: break-word; +} + +.project-card:hover, +.worker-card:hover { + border-color: var(--color-primary); + box-shadow: var(--shadow-lg); + transform: translateY(-4px); +} + +.project-card.inactive, +.worker-card.inactive { + opacity: 0.75; + background: var(--bg-body); +} + +/* 카드 헤더 */ +.project-header { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; +} + +.project-info { + flex: 1; + display: flex; + flex-direction: column; + gap: var(--space-sm); + margin-bottom: var(--space-md); +} + +.project-job-no { + font-size: var(--font-xs); + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.project-name { + font-size: var(--font-lg); + font-weight: 700; + color: var(--text-primary); + margin: 0; + line-height: 1.3; +} + +.project-badge { + padding: 0.25rem 0.75rem; + font-size: var(--font-xs); + font-weight: 600; + border-radius: var(--radius-sm); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.badge-active { + background: rgba(16, 185, 129, 0.1); + color: var(--color-success); +} + +.badge-inactive { + background: rgba(239, 68, 68, 0.1); + color: var(--color-error); +} + +/* 카드 메타 정보 */ +.project-meta { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-xs); + font-size: var(--font-sm); + color: var(--text-secondary); + padding: var(--space-md); + background: var(--bg-body); + border-radius: var(--radius-md); + margin-bottom: var(--space-md); +} + +.project-meta > span { + display: flex; + align-items: center; + gap: var(--space-xs); + padding: var(--space-xs) 0; + line-height: 1.5; +} + +.project-meta > span:empty::after { + content: '정보 없음'; + color: var(--text-muted); + font-style: italic; +} + +.meta-row { + display: grid; + grid-template-columns: 100px 1fr; + align-items: center; + gap: var(--space-sm); + font-size: var(--font-sm); + padding: var(--space-xs) 0; +} + +.meta-label { + font-weight: 600; + color: var(--text-muted); + font-size: var(--font-xs); + text-transform: uppercase; + letter-spacing: 0.3px; +} + +.meta-value { + color: var(--text-primary); + font-weight: 500; +} + +.meta-value:empty::after { + content: '-'; + color: var(--text-muted); +} + +/* 카드 액션 */ +.project-actions, +.worker-actions { + display: flex; + gap: var(--space-sm); + margin-top: auto; + padding-top: var(--space-md); + border-top: 1px solid var(--border-color); +} + +.btn-edit, +.btn-delete, +.action-btn { + flex: 1; + padding: 0.5rem 1rem; + font-size: var(--font-sm); + font-weight: 600; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + background: white; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-xs); +} + +.btn-edit:hover { + background: var(--color-primary); + color: white; + border-color: var(--color-primary); + transform: translateY(-1px); +} + +.btn-delete:hover, +.action-btn.danger:hover { + background: var(--color-error); + color: white; + border-color: var(--color-error); + transform: translateY(-1px); +} + +/* 작업자 카드 특별 스타일 */ +.worker-card .project-info { + display: flex; + align-items: flex-start; + gap: var(--space-md); + margin-bottom: var(--space-md); +} + +.worker-avatar { + width: 60px; + height: 60px; + border-radius: 50%; + background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark)); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + box-shadow: var(--shadow-md); +} + +.avatar-initial { + color: white; + font-size: var(--font-xl); + font-weight: 700; +} + +.worker-card.inactive .worker-avatar { + background: linear-gradient(135deg, var(--color-secondary), var(--color-secondary-dark)); + opacity: 0.7; +} + +.worker-details { + flex: 1; + min-width: 0; +} + +/* 비활성화 오버레이 및 라벨 */ +.inactive-overlay { + position: absolute; + top: var(--space-md); + right: var(--space-md); + z-index: 10; +} + +.inactive-badge { + display: inline-flex; + align-items: center; + gap: var(--space-xs); + padding: 0.375rem 0.75rem; + background: rgba(239, 68, 68, 0.9); + color: white; + border-radius: var(--radius-md); + font-size: var(--font-xs); + font-weight: 700; + box-shadow: var(--shadow-md); +} + +.inactive-label { + color: var(--color-error); + font-size: var(--font-sm); + font-weight: 600; + margin-left: var(--space-xs); +} + +.inactive-notice { + color: var(--color-warning) !important; + font-weight: 600 !important; +} + +/* ============================================ + 9. 코드 관리 탭 + ============================================ */ +.code-tabs { + display: flex; + gap: var(--space-sm); + margin-bottom: var(--space-xl); + padding: var(--space-sm); + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + overflow-x: auto; +} + +.tab-btn { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: 0.75rem 1.25rem; + font-size: var(--font-sm); + font-weight: 600; + color: var(--text-secondary); + background: transparent; + border: none; + border-radius: var(--radius-md); + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; +} + +.tab-btn:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.tab-btn.active { + background: var(--color-primary); + color: white; +} + +.tab-icon { + font-size: var(--font-base); +} + +/* ============================================ + 10. 테이블 스타일 + ============================================ */ +.table-container { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + overflow: hidden; +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background: var(--bg-body); + border-bottom: 2px solid var(--border-color); +} + +th { + padding: 1rem; + font-size: var(--font-sm); + font-weight: 700; + color: var(--text-secondary); + text-align: left; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +td { + padding: 1rem; + font-size: var(--font-sm); + color: var(--text-primary); + border-bottom: 1px solid var(--border-color); +} + +tr:hover { + background: var(--bg-hover); +} + +tr:last-child td { + border-bottom: none; +} + +/* ============================================ + 11. 모달 + ============================================ */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + backdrop-filter: blur(4px); +} + +.modal-container { + background: var(--bg-card); + border-radius: var(--radius-xl); + width: 90%; + max-width: 600px; + max-height: 90vh; + overflow-y: auto; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--space-lg); + border-bottom: 1px solid var(--border-color); +} + +.modal-title { + font-size: var(--font-xl); + font-weight: 700; + color: var(--text-primary); + margin: 0; +} + +.modal-close { + padding: 0.5rem; + background: none; + border: none; + font-size: var(--font-xl); + color: var(--text-secondary); + cursor: pointer; + border-radius: var(--radius-md); + transition: all 0.2s ease; +} + +.modal-close:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.modal-body { + padding: var(--space-lg); +} + +.modal-footer { + display: flex; + gap: var(--space-md); + justify-content: flex-end; + padding: var(--space-lg); + border-top: 1px solid var(--border-color); +} + +/* ============================================ + 12. 폼 요소 + ============================================ */ +.form-group { + margin-bottom: var(--space-lg); +} + +.form-label { + display: block; + margin-bottom: var(--space-sm); + font-size: var(--font-sm); + font-weight: 600; + color: var(--text-primary); +} + +.form-label.required::after { + content: '*'; + color: var(--color-error); + margin-left: var(--space-xs); +} + +.form-input, +.form-select, +.form-textarea { + width: 100%; + padding: 0.625rem 1rem; + font-size: var(--font-sm); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + background: white; + transition: all 0.2s ease; + box-sizing: border-box; +} + +.form-input:focus, +.form-select:focus, +.form-textarea:focus { + outline: none; + border-color: var(--border-focus); + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); +} + +.form-textarea { + resize: vertical; + min-height: 100px; +} + +.form-help { + margin-top: var(--space-xs); + font-size: var(--font-xs); + color: var(--text-muted); +} + +/* ============================================ + 13. 반응형 디자인 + ============================================ */ + +/* 대형 데스크탑 (1400px 이상) */ +@media (min-width: 1400px) { + .projects-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +/* 일반 데스크탑 (1200px ~ 1399px) */ +@media (max-width: 1399px) and (min-width: 1200px) { + .projects-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +/* 중간 화면 (1024px ~ 1199px) - 2열 */ +@media (max-width: 1199px) and (min-width: 1024px) { + .projects-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +/* 태블릿 (768px ~ 1023px) */ +@media (max-width: 1023px) and (min-width: 768px) { + .sidebar { + width: 200px; + min-width: 200px; + } + + .main-content { + padding: var(--space-md); + } + + .page-header { + flex-direction: column; + align-items: flex-start; + gap: var(--space-md); + } + + .page-actions { + width: 100%; + justify-content: flex-start; + } + + .projects-grid { + grid-template-columns: repeat(2, 1fr); + } + + .search-bar { + flex-direction: column; + } + + .project-stats { + flex-wrap: wrap; + gap: var(--space-sm); + } + + .stat-item { + font-size: var(--font-xs); + padding: 0.375rem 0.625rem; + } +} + +/* 모바일 (767px 이하) */ +@media (max-width: 767px) { + .page-container { + flex-direction: column; + } + + .sidebar { + display: none; /* 모바일에서는 사이드바 숨김 */ + } + + .main-content { + padding: var(--space-sm); + } + + .page-title { + font-size: var(--font-xl); + flex-direction: column; + align-items: flex-start; + gap: var(--space-xs); + } + + .title-icon { + font-size: var(--font-xl); + } + + .page-actions { + width: 100%; + flex-direction: column; + } + + .btn { + width: 100%; + justify-content: center; + } + + .search-section, + .projects-section { + padding: var(--space-md); + } + + .search-bar { + flex-direction: column; + } + + .projects-grid { + grid-template-columns: 1fr; + gap: var(--space-md); + } + + .project-card { + min-height: auto; + } + + .project-stats { + flex-direction: column; + width: 100%; + } + + .stat-item { + width: 100%; + justify-content: space-between; + } + + .code-tabs { + flex-direction: column; + } + + .tab-btn { + justify-content: center; + } + + .meta-row { + grid-template-columns: 80px 1fr; + gap: var(--space-xs); + } + + .meta-label { + font-size: 0.625rem; + } + + .modal-container { + width: 95%; + margin: var(--space-sm); + max-height: 95vh; + } +} + +/* 초소형 모바일 (480px 이하) */ +@media (max-width: 480px) { + .work-report-main { + padding: var(--space-xs); + } + + .page-title { + font-size: var(--font-lg); + } + + .page-description { + font-size: var(--font-xs); + } + + .search-section, + .projects-section { + padding: var(--space-sm); + } + + .project-card { + padding: var(--space-md); + } + + .btn { + padding: 0.5rem 0.875rem; + font-size: var(--font-xs); + } +} + +/* ============================================ + 14. Empty State + ============================================ */ +.empty-state { + text-align: center; + padding: var(--space-2xl) var(--space-lg); + color: var(--text-secondary); +} + +.empty-icon { + font-size: 4rem; + margin-bottom: var(--space-lg); + opacity: 0.5; +} + +.empty-state h3 { + font-size: var(--font-xl); + font-weight: 600; + color: var(--text-primary); + margin: 0 0 var(--space-sm) 0; +} + +.empty-state p { + font-size: var(--font-sm); + color: var(--text-secondary); + margin-bottom: var(--space-lg); +} + +/* ============================================ + 15. 로딩 및 스켈레톤 + ============================================ */ +.skeleton { + background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); + background-size: 200% 100%; + animation: skeleton-loading 1.5s ease-in-out infinite; + border-radius: var(--radius-md); +} + +@keyframes skeleton-loading { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} + +.skeleton-card { + height: 280px; +} + +.skeleton-text { + height: 1rem; + margin-bottom: var(--space-sm); +} + +/* ============================================ + 16. 유틸리티 클래스 + ============================================ */ +.text-center { + text-align: center; +} + +.text-muted { + color: var(--text-muted); +} + +.text-success { + color: var(--color-success); +} + +.text-error { + color: var(--color-error); +} + +.mt-0 { margin-top: 0; } +.mt-1 { margin-top: var(--space-sm); } +.mt-2 { margin-top: var(--space-md); } +.mt-3 { margin-top: var(--space-lg); } + +.mb-0 { margin-bottom: 0; } +.mb-1 { margin-bottom: var(--space-sm); } +.mb-2 { margin-bottom: var(--space-md); } +.mb-3 { margin-bottom: var(--space-lg); } + +.hidden { + display: none !important; +} + +.loading { + opacity: 0.6; + pointer-events: none; +} + +/* 코드 상세 정보 */ +.code-detail { + display: flex; + flex-direction: column; + gap: var(--space-xs); +} + +.code-name { + font-size: var(--font-base); + font-weight: 600; + color: var(--text-primary); +} + +.code-description { + font-size: var(--font-sm); + color: var(--text-secondary); + line-height: 1.5; +} + +/* ============================================ + 17. 코드 관리 페이지 전용 스타일 + ============================================ */ + +/* 코드 탭 스타일 */ +.code-tabs { + display: flex; + gap: var(--space-sm); + margin-bottom: var(--space-xl); + border-bottom: 2px solid var(--border-color); + padding-bottom: 0; +} + +.tab-btn { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: var(--space-md) var(--space-lg); + background: transparent; + border: none; + border-bottom: 3px solid transparent; + color: var(--text-secondary); + font-size: var(--font-base); + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + position: relative; + margin-bottom: -2px; +} + +.tab-btn:hover { + color: var(--color-primary); + background: var(--color-primary-light); +} + +.tab-btn.active { + color: var(--color-primary); + border-bottom-color: var(--color-primary); +} + +.tab-icon { + font-size: var(--font-lg); +} + +/* 탭 콘텐츠 */ +.code-tab-content { + display: none; +} + +.code-tab-content.active { + display: block; + animation: fadeIn 0.3s ease; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* 코드 섹션 */ +.code-section { + margin-bottom: var(--space-xl); +} + +/* 코드 통계 */ +.code-stats { + display: flex; + gap: var(--space-sm); + flex-wrap: wrap; + margin-bottom: var(--space-lg); +} + +.code-stats .stat-item { + padding: 0.5rem 1rem; + font-size: var(--font-sm); +} + +/* 코드 그리드 */ +.code-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--space-lg); + width: 100%; +} + +/* 코드 카드 */ +.code-card { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: var(--space-lg); + transition: all 0.2s ease; + cursor: pointer; + position: relative; + display: flex; + flex-direction: column; + min-height: 200px; +} + +.code-card:hover { + border-color: var(--color-primary); + box-shadow: var(--shadow-lg); + transform: translateY(-2px); +} + +/* 코드 카드 헤더 */ +.code-header { + display: flex; + align-items: flex-start; + gap: var(--space-md); + margin-bottom: var(--space-md); +} + +.code-icon { + font-size: 2rem; + flex-shrink: 0; + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-body); + border-radius: var(--radius-md); +} + +.code-info { + flex: 1; + min-width: 0; +} + +.code-name { + font-size: var(--font-lg); + font-weight: 700; + color: var(--text-primary); + margin: 0 0 var(--space-xs) 0; + word-break: break-word; +} + +.code-label { + display: inline-block; + padding: 0.25rem 0.75rem; + background: var(--bg-body); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + font-size: var(--font-xs); + font-weight: 600; + color: var(--text-secondary); +} + +.code-actions { + display: flex; + gap: var(--space-xs); + flex-shrink: 0; +} + +.btn-small { + padding: 0.375rem 0.625rem; + font-size: var(--font-sm); + background: white; + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.btn-small:hover { + transform: scale(1.1); +} + +.btn-small.btn-edit:hover { + background: var(--color-primary); + border-color: var(--color-primary); + color: white; +} + +.btn-small.btn-delete:hover { + background: var(--color-error); + border-color: var(--color-error); + color: white; +} + +/* 코드 설명 */ +.code-description { + font-size: var(--font-sm); + color: var(--text-secondary); + line-height: 1.6; + margin-bottom: var(--space-md); + flex: 1; +} + +/* 해결 가이드 */ +.solution-guide { + background: #fef3c7; + border-left: 3px solid #f59e0b; + padding: var(--space-md); + border-radius: var(--radius-sm); + font-size: var(--font-sm); + margin-bottom: var(--space-md); + line-height: 1.6; +} + +.solution-guide strong { + color: #92400e; + display: block; + margin-bottom: var(--space-xs); +} + +/* 코드 메타 정보 */ +.code-meta { + display: flex; + flex-wrap: wrap; + gap: var(--space-md); + padding-top: var(--space-md); + border-top: 1px solid var(--border-color); + margin-top: auto; +} + +.code-date { + font-size: var(--font-xs); + color: var(--text-muted); +} + +/* 상태별 카드 스타일 */ +.normal-status { + border-left: 4px solid #10b981; +} + +.error-status { + border-left: 4px solid #ef4444; +} + +/* 심각도별 카드 스타일 */ +.severity-low { + border-left: 4px solid #10b981; +} + +.severity-low .code-icon { + background: #d1fae5; +} + +.severity-medium { + border-left: 4px solid #f59e0b; +} + +.severity-medium .code-icon { + background: #fef3c7; +} + +.severity-high { + border-left: 4px solid #f97316; +} + +.severity-high .code-icon { + background: #ffedd5; +} + +.severity-critical { + border-left: 4px solid #ef4444; +} + +.severity-critical .code-icon { + background: #fee2e2; +} + +/* 작업 유형 카드 */ +.work-type-card { + border-left: 4px solid #3b82f6; +} + +.work-type-card .code-icon { + background: #dbeafe; +} + +/* 반응형 - 코드 그리드 */ +@media (max-width: 1199px) { + .code-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 767px) { + .code-grid { + grid-template-columns: 1fr; + } + + .code-tabs { + overflow-x: auto; + flex-wrap: nowrap; + } + + .tab-btn { + white-space: nowrap; + padding: var(--space-sm) var(--space-md); + font-size: var(--font-sm); + } +} diff --git a/web-ui/pages/admin/codes.html b/web-ui/pages/admin/codes.html index 4b7bca2..8d00f93 100644 --- a/web-ui/pages/admin/codes.html +++ b/web-ui/pages/admin/codes.html @@ -5,24 +5,55 @@ 코드 관리 | (주)테크니컬코리아 - - + -
- - + + + + +
+ + -
- - - ← 작업관리로 돌아가기 - - +
@@ -170,6 +202,7 @@
+ @@ -242,8 +275,7 @@ - - + diff --git a/web-ui/pages/admin/projects.html b/web-ui/pages/admin/projects.html index cdb1b66..e4f80ea 100644 --- a/web-ui/pages/admin/projects.html +++ b/web-ui/pages/admin/projects.html @@ -5,24 +5,56 @@ 프로젝트 관리 | (주)테크니컬코리아 - - + -
- - + + + + +
+ + -
- - - ← 작업관리로 돌아가기 - - +
+ @@ -203,6 +235,6 @@ - + diff --git a/web-ui/pages/admin/workers.html b/web-ui/pages/admin/workers.html index cadc0e2..f708e60 100644 --- a/web-ui/pages/admin/workers.html +++ b/web-ui/pages/admin/workers.html @@ -5,24 +5,55 @@ 작업자 관리 | (주)테크니컬코리아 - - + -
- - + + + + +
+ + -
- - - ← 작업관리로 돌아가기 - - +